From 589a6219d0c389418d2dc1f4f5fda78f0d67e1a1 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 19 Aug 2023 20:28:00 +0000 Subject: [PATCH 001/500] MAINT: Update to the new layout --- scipy/optimize/_highs/meson.build | 126 +++++++++++++++--------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index 8d701e5e3f67..39d8c16226e9 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -17,39 +17,39 @@ highs_define_macros = [ basiclu_lib = static_library('basiclu', [ - '../../_lib/highs/src/ipm/basiclu/src/basiclu_factorize.c', - '../../_lib/highs/src/ipm/basiclu/src/basiclu_get_factors.c', - '../../_lib/highs/src/ipm/basiclu/src/basiclu_initialize.c', - '../../_lib/highs/src/ipm/basiclu/src/basiclu_object.c', - '../../_lib/highs/src/ipm/basiclu/src/basiclu_solve_dense.c', - '../../_lib/highs/src/ipm/basiclu/src/basiclu_solve_for_update.c', - '../../_lib/highs/src/ipm/basiclu/src/basiclu_solve_sparse.c', - '../../_lib/highs/src/ipm/basiclu/src/basiclu_update.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_build_factors.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_condest.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_dfs.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_factorize_bump.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_file.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_garbage_perm.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_initialize.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_internal.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_markowitz.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_matrix_norm.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_pivot.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_residual_test.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_setup_bump.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_singletons.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_solve_dense.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_solve_for_update.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_solve_sparse.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_solve_symbolic.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_solve_triangular.c', - '../../_lib/highs/src/ipm/basiclu/src/lu_update.c' + '../../_lib/highs/src/ipm/basiclu/basiclu_factorize.c', + '../../_lib/highs/src/ipm/basiclu/basiclu_get_factors.c', + '../../_lib/highs/src/ipm/basiclu/basiclu_initialize.c', + '../../_lib/highs/src/ipm/basiclu/basiclu_object.c', + '../../_lib/highs/src/ipm/basiclu/basiclu_solve_dense.c', + '../../_lib/highs/src/ipm/basiclu/basiclu_solve_for_update.c', + '../../_lib/highs/src/ipm/basiclu/basiclu_solve_sparse.c', + '../../_lib/highs/src/ipm/basiclu/basiclu_update.c', + '../../_lib/highs/src/ipm/basiclu/lu_build_factors.c', + '../../_lib/highs/src/ipm/basiclu/lu_condest.c', + '../../_lib/highs/src/ipm/basiclu/lu_dfs.c', + '../../_lib/highs/src/ipm/basiclu/lu_factorize_bump.c', + '../../_lib/highs/src/ipm/basiclu/lu_file.c', + '../../_lib/highs/src/ipm/basiclu/lu_garbage_perm.c', + '../../_lib/highs/src/ipm/basiclu/lu_initialize.c', + '../../_lib/highs/src/ipm/basiclu/lu_internal.c', + '../../_lib/highs/src/ipm/basiclu/lu_markowitz.c', + '../../_lib/highs/src/ipm/basiclu/lu_matrix_norm.c', + '../../_lib/highs/src/ipm/basiclu/lu_pivot.c', + '../../_lib/highs/src/ipm/basiclu/lu_residual_test.c', + '../../_lib/highs/src/ipm/basiclu/lu_setup_bump.c', + '../../_lib/highs/src/ipm/basiclu/lu_singletons.c', + '../../_lib/highs/src/ipm/basiclu/lu_solve_dense.c', + '../../_lib/highs/src/ipm/basiclu/lu_solve_for_update.c', + '../../_lib/highs/src/ipm/basiclu/lu_solve_sparse.c', + '../../_lib/highs/src/ipm/basiclu/lu_solve_symbolic.c', + '../../_lib/highs/src/ipm/basiclu/lu_solve_triangular.c', + '../../_lib/highs/src/ipm/basiclu/lu_update.c' ], include_directories: [ 'src', '../../_lib/highs/src', - '../../_lib/highs/src/ipm/basiclu/include' + '../../_lib/highs/src/ipm/basiclu' ], c_args: [Wno_unused_variable, highs_define_macros] ) @@ -66,41 +66,41 @@ highs_flags = [ ipx_lib = static_library('ipx', [ - '../../_lib/highs/src/ipm/ipx/src/basiclu_kernel.cc', - '../../_lib/highs/src/ipm/ipx/src/basiclu_wrapper.cc', - '../../_lib/highs/src/ipm/ipx/src/basis.cc', - '../../_lib/highs/src/ipm/ipx/src/conjugate_residuals.cc', - '../../_lib/highs/src/ipm/ipx/src/control.cc', - '../../_lib/highs/src/ipm/ipx/src/crossover.cc', - '../../_lib/highs/src/ipm/ipx/src/diagonal_precond.cc', - '../../_lib/highs/src/ipm/ipx/src/forrest_tomlin.cc', - '../../_lib/highs/src/ipm/ipx/src/guess_basis.cc', - '../../_lib/highs/src/ipm/ipx/src/indexed_vector.cc', - '../../_lib/highs/src/ipm/ipx/src/info.cc', - '../../_lib/highs/src/ipm/ipx/src/ipm.cc', - '../../_lib/highs/src/ipm/ipx/src/ipx_c.cc', - '../../_lib/highs/src/ipm/ipx/src/iterate.cc', - '../../_lib/highs/src/ipm/ipx/src/kkt_solver.cc', - '../../_lib/highs/src/ipm/ipx/src/kkt_solver_basis.cc', - '../../_lib/highs/src/ipm/ipx/src/kkt_solver_diag.cc', - '../../_lib/highs/src/ipm/ipx/src/linear_operator.cc', - '../../_lib/highs/src/ipm/ipx/src/lp_solver.cc', - '../../_lib/highs/src/ipm/ipx/src/lu_factorization.cc', - '../../_lib/highs/src/ipm/ipx/src/lu_update.cc', - '../../_lib/highs/src/ipm/ipx/src/maxvolume.cc', - '../../_lib/highs/src/ipm/ipx/src/model.cc', - '../../_lib/highs/src/ipm/ipx/src/normal_matrix.cc', - '../../_lib/highs/src/ipm/ipx/src/sparse_matrix.cc', - '../../_lib/highs/src/ipm/ipx/src/sparse_utils.cc', - '../../_lib/highs/src/ipm/ipx/src/splitted_normal_matrix.cc', - '../../_lib/highs/src/ipm/ipx/src/starting_basis.cc', - '../../_lib/highs/src/ipm/ipx/src/symbolic_invert.cc', - '../../_lib/highs/src/ipm/ipx/src/timer.cc', - '../../_lib/highs/src/ipm/ipx/src/utils.cc' + '../../_lib/highs/src/ipm/ipx/basiclu_kernel.cc', + '../../_lib/highs/src/ipm/ipx/basiclu_wrapper.cc', + '../../_lib/highs/src/ipm/ipx/basis.cc', + '../../_lib/highs/src/ipm/ipx/conjugate_residuals.cc', + '../../_lib/highs/src/ipm/ipx/control.cc', + '../../_lib/highs/src/ipm/ipx/crossover.cc', + '../../_lib/highs/src/ipm/ipx/diagonal_precond.cc', + '../../_lib/highs/src/ipm/ipx/forrest_tomlin.cc', + '../../_lib/highs/src/ipm/ipx/guess_basis.cc', + '../../_lib/highs/src/ipm/ipx/indexed_vector.cc', + '../../_lib/highs/src/ipm/ipx/info.cc', + '../../_lib/highs/src/ipm/ipx/ipm.cc', + '../../_lib/highs/src/ipm/ipx/ipx_c.cc', + '../../_lib/highs/src/ipm/ipx/iterate.cc', + '../../_lib/highs/src/ipm/ipx/kkt_solver.cc', + '../../_lib/highs/src/ipm/ipx/kkt_solver_basis.cc', + '../../_lib/highs/src/ipm/ipx/kkt_solver_diag.cc', + '../../_lib/highs/src/ipm/ipx/linear_operator.cc', + '../../_lib/highs/src/ipm/ipx/lp_solver.cc', + '../../_lib/highs/src/ipm/ipx/lu_factorization.cc', + '../../_lib/highs/src/ipm/ipx/lu_update.cc', + '../../_lib/highs/src/ipm/ipx/maxvolume.cc', + '../../_lib/highs/src/ipm/ipx/model.cc', + '../../_lib/highs/src/ipm/ipx/normal_matrix.cc', + '../../_lib/highs/src/ipm/ipx/sparse_matrix.cc', + '../../_lib/highs/src/ipm/ipx/sparse_utils.cc', + '../../_lib/highs/src/ipm/ipx/splitted_normal_matrix.cc', + '../../_lib/highs/src/ipm/ipx/starting_basis.cc', + '../../_lib/highs/src/ipm/ipx/symbolic_invert.cc', + '../../_lib/highs/src/ipm/ipx/timer.cc', + '../../_lib/highs/src/ipm/ipx/utils.cc' ], include_directories: [ - '../../_lib/highs/src/ipm/ipx/include/', - '../../_lib/highs/src/ipm/basiclu/include/', + '../../_lib/highs/src/ipm/ipx/', + '../../_lib/highs/src/ipm/basiclu/', '../../_lib/highs/src/', '../../_lib/highs/extern/', 'cython/src/' @@ -218,7 +218,7 @@ highs_lib = static_library('highs', '../../_lib/highs/extern/', '../../_lib/highs/src/', '../../_lib/highs/src/io/', - '../../_lib/highs/src/ipm/ipx/include/', + '../../_lib/highs/src/ipm/ipx/', '../../_lib/highs/src/lp_data/', '../../_lib/highs/src/util/', ], From 5d5fe61b01f00ec0db624edf5dbeac998ef024b1 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 3 Sep 2023 15:12:15 +0000 Subject: [PATCH 002/500] MAINT: Delete all cython related cruft [highs] ENH: Rework _linprog_highs to use highspy Still needs a wrapper around Highs() MAINT: Refactor slightly and add commit credit Co-authored-by: mckib2 ENH: Use highspy exclusively TST: Almost pass everything MAINT,TMP: Use my highs until merge --- .gitmodules | 2 +- scipy/_lib/highs | 2 +- scipy/optimize/_highs/_highs_wrapper.py | 212 +++++ scipy/optimize/_highs/cython/__init__.py | 0 scipy/optimize/_highs/cython/src/HConfig.h | 0 scipy/optimize/_highs/cython/src/HConst.pxd | 106 --- scipy/optimize/_highs/cython/src/Highs.pxd | 56 -- scipy/optimize/_highs/cython/src/HighsIO.pxd | 20 - .../optimize/_highs/cython/src/HighsInfo.pxd | 22 - scipy/optimize/_highs/cython/src/HighsLp.pxd | 46 -- .../_highs/cython/src/HighsLpUtils.pxd | 9 - .../_highs/cython/src/HighsModelUtils.pxd | 10 - .../_highs/cython/src/HighsOptions.pxd | 110 --- .../_highs/cython/src/HighsRuntimeOptions.pxd | 9 - .../_highs/cython/src/HighsSparseMatrix.pxd | 15 - .../_highs/cython/src/HighsStatus.pxd | 12 - .../_highs/cython/src/SimplexConst.pxd | 95 --- scipy/optimize/_highs/cython/src/__init__.py | 0 .../_highs/cython/src/_highs_constants.pyx | 117 --- .../_highs/cython/src/_highs_wrapper.pyx | 736 ------------------ .../_highs/cython/src/highs_c_api.pxd | 7 - scipy/optimize/_highs/meson.build | 564 +++++++------- scipy/optimize/_highs/src/libhighs_export.h | 50 -- scipy/optimize/_linprog_highs.py | 184 ++--- scipy/optimize/_milp.py | 7 +- scipy/optimize/tests/test_linprog.py | 50 +- scipy/optimize/tests/test_milp.py | 26 +- 27 files changed, 649 insertions(+), 1818 deletions(-) create mode 100644 scipy/optimize/_highs/_highs_wrapper.py delete mode 100644 scipy/optimize/_highs/cython/__init__.py delete mode 100644 scipy/optimize/_highs/cython/src/HConfig.h delete mode 100644 scipy/optimize/_highs/cython/src/HConst.pxd delete mode 100644 scipy/optimize/_highs/cython/src/Highs.pxd delete mode 100644 scipy/optimize/_highs/cython/src/HighsIO.pxd delete mode 100644 scipy/optimize/_highs/cython/src/HighsInfo.pxd delete mode 100644 scipy/optimize/_highs/cython/src/HighsLp.pxd delete mode 100644 scipy/optimize/_highs/cython/src/HighsLpUtils.pxd delete mode 100644 scipy/optimize/_highs/cython/src/HighsModelUtils.pxd delete mode 100644 scipy/optimize/_highs/cython/src/HighsOptions.pxd delete mode 100644 scipy/optimize/_highs/cython/src/HighsRuntimeOptions.pxd delete mode 100644 scipy/optimize/_highs/cython/src/HighsSparseMatrix.pxd delete mode 100644 scipy/optimize/_highs/cython/src/HighsStatus.pxd delete mode 100644 scipy/optimize/_highs/cython/src/SimplexConst.pxd delete mode 100644 scipy/optimize/_highs/cython/src/__init__.py delete mode 100644 scipy/optimize/_highs/cython/src/_highs_constants.pyx delete mode 100644 scipy/optimize/_highs/cython/src/_highs_wrapper.pyx delete mode 100644 scipy/optimize/_highs/cython/src/highs_c_api.pxd delete mode 100644 scipy/optimize/_highs/src/libhighs_export.h diff --git a/.gitmodules b/.gitmodules index b6920b8714b2..6bcfddf6bc02 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,7 +11,7 @@ shallow = true [submodule "HiGHS"] path = scipy/_lib/highs - url = https://github.com/scipy/highs + url = https://github.com/HaoZeke/highs shallow = true [submodule "scipy/_lib/boost_math"] path = scipy/_lib/boost_math diff --git a/scipy/_lib/highs b/scipy/_lib/highs index 4a122958a82e..090453608adb 160000 --- a/scipy/_lib/highs +++ b/scipy/_lib/highs @@ -1 +1 @@ -Subproject commit 4a122958a82e67e725d08153e099efe4dad099a2 +Subproject commit 090453608adb6a1f14dbee01dfd117b80aa8938a diff --git a/scipy/optimize/_highs/_highs_wrapper.py b/scipy/optimize/_highs/_highs_wrapper.py new file mode 100644 index 000000000000..a67e124911e2 --- /dev/null +++ b/scipy/optimize/_highs/_highs_wrapper.py @@ -0,0 +1,212 @@ +from warnings import warn + +import numpy as np +from scipy.optimize._highs import highs_bindings as hspy # type: ignore[attr-defined] +from scipy.optimize._highs import _highs_options as hopt # type: ignore[attr-defined] +from scipy.optimize import OptimizeWarning + + +def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, options): + numcol = c.size + numrow = rhs.size + isMip = integrality is not None and np.sum(integrality) > 0 + + # default "null" return values + res = { + "x": None, + "fun": None, + } + + # Fill up a HighsLp object + lp = hspy.HighsLp() + lp.num_col_ = numcol + lp.num_row_ = numrow + lp.a_matrix_.num_col_ = numcol + lp.a_matrix_.num_row_ = numrow + lp.a_matrix_.format_ = hspy.MatrixFormat.kColwise + lp.col_cost_ = c + lp.col_lower_ = lb + lp.col_upper_ = ub + lp.row_lower_ = lhs + lp.row_upper_ = rhs + lp.a_matrix_.start_ = indptr + lp.a_matrix_.index_ = indices + lp.a_matrix_.value_ = data + if integrality.size > 0: + lp.integrality_ = [hspy.HighsVarType(i) for i in integrality] + + # Make a Highs object and pass it everything + highs = hspy.Highs() + highs_options = hspy.HighsOptions() + for key, val in options.items(): + # handle filtering of unsupported and default options + if val is None or key in ("sense",): + continue + + # ask for the option type + opt_type = hopt.get_option_type(key) + if -1 == opt_type: + warn(f"Unrecognized options detected: {dict({key: val})}", OptimizeWarning) + continue + else: + if key in ("presolve", "parallel"): + # handle fake bools (require bool -> str conversions) + if isinstance(val, bool): + val = "on" if val else "off" + else: + warn(f'Option f"{key}" is "{val}", but only True or False is ' + f'allowed. Using default.', OptimizeWarning) + continue + opt_type = hspy.HighsOptionType(opt_type) + status, msg = check_option(highs, key, val) + # { + # hspy.HighsOptionType.kBool: lambda _x, _y: (0, ""), + # hspy.HighsOptionType.kInt: hopt.check_int_option, + # hspy.HighsOptionType.kDouble: hopt.check_double_option, + # hspy.HighsOptionType.kString: hopt.check_string_option, + # }[opt_type](key, val) + + # have to do bool checking here because HiGHS doesn't have API + if opt_type == hspy.HighsOptionType.kBool: + if not isinstance(val, bool): + warn(f'Option f"{key}" is "{val}", but only True or False is ' + f'allowed. Using default.', OptimizeWarning) + continue + + # warn or set option + if status != 0: + warn(msg, OptimizeWarning) + else: + setattr(highs_options, key, val) + + opt_status = highs.passOptions(highs_options) + if opt_status == hspy.HighsStatus.kError: + res.update({ + "status": highs.getModelStatus(), + "message": highs.modelStatusToString(highs.getModelStatus()), + }) + return res + + init_status = highs.passModel(lp) + if init_status == hspy.HighsStatus.kError: + # if model fails to load, highs.getModelStatus() will be NOT_SET + err_model_status = hspy.HighsModelStatus.kModelError + res.update({ + "status": err_model_status, + "message": highs.modelStatusToString(err_model_status), + }) + return res + + # Solve the LP + run_status = highs.run() + if run_status == hspy.HighsStatus.kError: + res.update({ + "status": highs.getModelStatus(), + "message": highs.modelStatusToString(highs.getModelStatus()), + }) + return res + + # Extract what we need from the solution + model_status = highs.getModelStatus() + + # it should always be safe to get the info object + info = highs.getInfo() + + # Failure modes: + # LP: if we have anything other than an Optimal status, it + # is unsafe (and unhelpful) to read any results + # MIP: has a non-Optimal status or has timed out/reached max iterations + # 1) If not Optimal/TimedOut/MaxIter status, there is no solution + # 2) If TimedOut/MaxIter status, there may be a feasible solution. + # if the objective function value is not Infinity, then the + # current solution is feasible and can be returned. Else, there + # is no solution. + mipFailCondition = model_status not in ( + hspy.HighsModelStatus.kOptimal, + hspy.HighsModelStatus.kTimeLimit, + hspy.HighsModelStatus.kIterationLimit, + hspy.HighsModelStatus.kSolutionLimit, + ) or (model_status in { + hspy.HighsModelStatus.kTimeLimit, + hspy.HighsModelStatus.kIterationLimit, + hspy.HighsModelStatus.kSolutionLimit, + } and (info.objective_function_value == hspy.kHighsInf)) + lpFailCondition = model_status != hspy.HighsModelStatus.kOptimal + if (isMip and mipFailCondition) or (not isMip and lpFailCondition): + res.update({ + "status": model_status, + "message": f"model_status is {highs.modelStatusToString(model_status)}; " + f"primal_status is " + f"{highs.solutionStatusToString(info.primal_solution_status)}", + "simplex_nit": info.simplex_iteration_count, + "ipm_nit": info.ipm_iteration_count, + "crossover_nit": info.crossover_iteration_count, + }) + return res + + # Should be safe to read the solution: + solution = highs.getSolution() + basis = highs.getBasis() + + # Lagrangians for bounds based on column statuses + marg_bnds = np.zeros((2, numcol)) + for ii in range(numcol): + if basis.col_status[ii] == hspy.HighsBasisStatus.kLower: + marg_bnds[0, ii] = solution.col_dual[ii] + elif basis.col_status[ii] == hspy.HighsBasisStatus.kUpper: + marg_bnds[1, ii] = solution.col_dual[ii] + + res.update({ + "status": model_status, + "message": highs.modelStatusToString(model_status), + + # Primal solution + "x": np.array(solution.col_value), + + # Ax + s = b => Ax = b - s + # Note: this is for all constraints (A_ub and A_eq) + "slack": rhs - solution.row_value, + + # lambda are the lagrange multipliers associated with Ax=b + "lambda": np.array(solution.row_dual), + "marg_bnds": marg_bnds, + + "fun": info.objective_function_value, + "simplex_nit": info.simplex_iteration_count, + "ipm_nit": info.ipm_iteration_count, + "crossover_nit": info.crossover_iteration_count, + }) + + if isMip: + res.update({ + "mip_node_count": info.mip_node_count, + "mip_dual_bound": info.mip_dual_bound, + "mip_gap": info.mip_gap, + }) + + return res + +def check_option(highs_inst, option, value): + status, option_type = highs_inst.getOptionType(option) + + if status != HighsStatus.kOk: + return 1, "Invalid option name." + + valid_types = { + HighsOptionType.kBool: bool, + HighsOptionType.kInt: int, + HighsOptionType.kDouble: float, + HighsOptionType.kString: str + } + + expected_type = valid_types.get(option_type, None) + if expected_type is None: + return 3, "Unknown option type." + + if not isinstance(value, expected_type): + return 2, "Invalid option value." + + status, current_value = highs_inst.getOptionValue(option) + if status != HighsStatus.kOk: + return 4, "Failed to validate option value." + return 0, "Check option succeeded." diff --git a/scipy/optimize/_highs/cython/__init__.py b/scipy/optimize/_highs/cython/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/scipy/optimize/_highs/cython/src/HConfig.h b/scipy/optimize/_highs/cython/src/HConfig.h deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/scipy/optimize/_highs/cython/src/HConst.pxd b/scipy/optimize/_highs/cython/src/HConst.pxd deleted file mode 100644 index 503d9e74a263..000000000000 --- a/scipy/optimize/_highs/cython/src/HConst.pxd +++ /dev/null @@ -1,106 +0,0 @@ -# cython: language_level=3 - -from libcpp cimport bool -from libcpp.string cimport string - -cdef extern from "HConst.h" nogil: - - const int HIGHS_CONST_I_INF "kHighsIInf" - const double HIGHS_CONST_INF "kHighsInf" - const double kHighsTiny - const double kHighsZero - const int kHighsThreadLimit - - cdef enum HighsDebugLevel: - HighsDebugLevel_kHighsDebugLevelNone "kHighsDebugLevelNone" = 0 - HighsDebugLevel_kHighsDebugLevelCheap "kHighsDebugLevelCheap" - HighsDebugLevel_kHighsDebugLevelCostly "kHighsDebugLevelCostly" - HighsDebugLevel_kHighsDebugLevelExpensive "kHighsDebugLevelExpensive" - HighsDebugLevel_kHighsDebugLevelMin "kHighsDebugLevelMin" = HighsDebugLevel_kHighsDebugLevelNone - HighsDebugLevel_kHighsDebugLevelMax "kHighsDebugLevelMax" = HighsDebugLevel_kHighsDebugLevelExpensive - - ctypedef enum HighsModelStatus: - HighsModelStatusNOTSET "HighsModelStatus::kNotset" = 0 - HighsModelStatusLOAD_ERROR "HighsModelStatus::kLoadError" - HighsModelStatusMODEL_ERROR "HighsModelStatus::kModelError" - HighsModelStatusPRESOLVE_ERROR "HighsModelStatus::kPresolveError" - HighsModelStatusSOLVE_ERROR "HighsModelStatus::kSolveError" - HighsModelStatusPOSTSOLVE_ERROR "HighsModelStatus::kPostsolveError" - HighsModelStatusMODEL_EMPTY "HighsModelStatus::kModelEmpty" - HighsModelStatusOPTIMAL "HighsModelStatus::kOptimal" - HighsModelStatusINFEASIBLE "HighsModelStatus::kInfeasible" - HighsModelStatus_UNBOUNDED_OR_INFEASIBLE "HighsModelStatus::kUnboundedOrInfeasible" - HighsModelStatusUNBOUNDED "HighsModelStatus::kUnbounded" - HighsModelStatusREACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND "HighsModelStatus::kObjectiveBound" - HighsModelStatusREACHED_OBJECTIVE_TARGET "HighsModelStatus::kObjectiveTarget" - HighsModelStatusREACHED_TIME_LIMIT "HighsModelStatus::kTimeLimit" - HighsModelStatusREACHED_ITERATION_LIMIT "HighsModelStatus::kIterationLimit" - HighsModelStatusUNKNOWN "HighsModelStatus::kUnknown" - HighsModelStatusHIGHS_MODEL_STATUS_MIN "HighsModelStatus::kMin" = HighsModelStatusNOTSET - HighsModelStatusHIGHS_MODEL_STATUS_MAX "HighsModelStatus::kMax" = HighsModelStatusUNKNOWN - - cdef enum HighsBasisStatus: - HighsBasisStatusLOWER "HighsBasisStatus::kLower" = 0, # (slack) variable is at its lower bound [including fixed variables] - HighsBasisStatusBASIC "HighsBasisStatus::kBasic" # (slack) variable is basic - HighsBasisStatusUPPER "HighsBasisStatus::kUpper" # (slack) variable is at its upper bound - HighsBasisStatusZERO "HighsBasisStatus::kZero" # free variable is non-basic and set to zero - HighsBasisStatusNONBASIC "HighsBasisStatus::kNonbasic" # nonbasic with no specific bound information - useful for users and postsolve - - cdef enum SolverOption: - SOLVER_OPTION_SIMPLEX "SolverOption::SOLVER_OPTION_SIMPLEX" = -1 - SOLVER_OPTION_CHOOSE "SolverOption::SOLVER_OPTION_CHOOSE" - SOLVER_OPTION_IPM "SolverOption::SOLVER_OPTION_IPM" - - cdef enum PrimalDualStatus: - PrimalDualStatusSTATUS_NOT_SET "PrimalDualStatus::STATUS_NOT_SET" = -1 - PrimalDualStatusSTATUS_MIN "PrimalDualStatus::STATUS_MIN" = PrimalDualStatusSTATUS_NOT_SET - PrimalDualStatusSTATUS_NO_SOLUTION "PrimalDualStatus::STATUS_NO_SOLUTION" - PrimalDualStatusSTATUS_UNKNOWN "PrimalDualStatus::STATUS_UNKNOWN" - PrimalDualStatusSTATUS_INFEASIBLE_POINT "PrimalDualStatus::STATUS_INFEASIBLE_POINT" - PrimalDualStatusSTATUS_FEASIBLE_POINT "PrimalDualStatus::STATUS_FEASIBLE_POINT" - PrimalDualStatusSTATUS_MAX "PrimalDualStatus::STATUS_MAX" = PrimalDualStatusSTATUS_FEASIBLE_POINT - - cdef enum HighsOptionType: - HighsOptionTypeBOOL "HighsOptionType::kBool" = 0 - HighsOptionTypeINT "HighsOptionType::kInt" - HighsOptionTypeDOUBLE "HighsOptionType::kDouble" - HighsOptionTypeSTRING "HighsOptionType::kString" - - # workaround for lack of enum class support in Cython < 3.x - # cdef enum class ObjSense(int): - # ObjSenseMINIMIZE "ObjSense::kMinimize" = 1 - # ObjSenseMAXIMIZE "ObjSense::kMaximize" = -1 - - cdef cppclass ObjSense: - pass - - cdef ObjSense ObjSenseMINIMIZE "ObjSense::kMinimize" - cdef ObjSense ObjSenseMAXIMIZE "ObjSense::kMaximize" - - # cdef enum class MatrixFormat(int): - # MatrixFormatkColwise "MatrixFormat::kColwise" = 1 - # MatrixFormatkRowwise "MatrixFormat::kRowwise" - # MatrixFormatkRowwisePartitioned "MatrixFormat::kRowwisePartitioned" - - cdef cppclass MatrixFormat: - pass - - cdef MatrixFormat MatrixFormatkColwise "MatrixFormat::kColwise" - cdef MatrixFormat MatrixFormatkRowwise "MatrixFormat::kRowwise" - cdef MatrixFormat MatrixFormatkRowwisePartitioned "MatrixFormat::kRowwisePartitioned" - - # cdef enum class HighsVarType(int): - # kContinuous "HighsVarType::kContinuous" - # kInteger "HighsVarType::kInteger" - # kSemiContinuous "HighsVarType::kSemiContinuous" - # kSemiInteger "HighsVarType::kSemiInteger" - # kImplicitInteger "HighsVarType::kImplicitInteger" - - cdef cppclass HighsVarType: - pass - - cdef HighsVarType kContinuous "HighsVarType::kContinuous" - cdef HighsVarType kInteger "HighsVarType::kInteger" - cdef HighsVarType kSemiContinuous "HighsVarType::kSemiContinuous" - cdef HighsVarType kSemiInteger "HighsVarType::kSemiInteger" - cdef HighsVarType kImplicitInteger "HighsVarType::kImplicitInteger" diff --git a/scipy/optimize/_highs/cython/src/Highs.pxd b/scipy/optimize/_highs/cython/src/Highs.pxd deleted file mode 100644 index 7139908d0341..000000000000 --- a/scipy/optimize/_highs/cython/src/Highs.pxd +++ /dev/null @@ -1,56 +0,0 @@ -# cython: language_level=3 - -from libc.stdio cimport FILE - -from libcpp cimport bool -from libcpp.string cimport string - -from .HighsStatus cimport HighsStatus -from .HighsOptions cimport HighsOptions -from .HighsInfo cimport HighsInfo -from .HighsLp cimport ( - HighsLp, - HighsSolution, - HighsBasis, - ObjSense, -) -from .HConst cimport HighsModelStatus - -cdef extern from "Highs.h": - # From HiGHS/src/Highs.h - cdef cppclass Highs: - HighsStatus passHighsOptions(const HighsOptions& options) - HighsStatus passModel(const HighsLp& lp) - HighsStatus run() - HighsStatus setHighsLogfile(FILE* logfile) - HighsStatus setHighsOutput(FILE* output) - HighsStatus writeHighsOptions(const string filename, const bool report_only_non_default_values = true) - - # split up for cython below - #const HighsModelStatus& getModelStatus(const bool scaled_model = False) const - const HighsModelStatus & getModelStatus() const - - const HighsInfo& getHighsInfo "getInfo" () const - string modelStatusToString(const HighsModelStatus model_status) const - #HighsStatus getHighsInfoValue(const string& info, int& value) - HighsStatus getHighsInfoValue(const string& info, double& value) const - const HighsOptions& getHighsOptions() const - - const HighsLp& getLp() const - - HighsStatus writeSolution(const string filename, const bool pretty) const - - HighsStatus setBasis() - const HighsSolution& getSolution() const - const HighsBasis& getBasis() const - - bool changeObjectiveSense(const ObjSense sense) - - HighsStatus setHighsOptionValueBool "setOptionValue" (const string & option, const bool value) - HighsStatus setHighsOptionValueInt "setOptionValue" (const string & option, const int value) - HighsStatus setHighsOptionValueStr "setOptionValue" (const string & option, const string & value) - HighsStatus setHighsOptionValueDbl "setOptionValue" (const string & option, const double value) - - string primalDualStatusToString(const int primal_dual_status) - - void resetGlobalScheduler(bool blocking) diff --git a/scipy/optimize/_highs/cython/src/HighsIO.pxd b/scipy/optimize/_highs/cython/src/HighsIO.pxd deleted file mode 100644 index 82b80ae643f1..000000000000 --- a/scipy/optimize/_highs/cython/src/HighsIO.pxd +++ /dev/null @@ -1,20 +0,0 @@ -# cython: language_level=3 - - -cdef extern from "HighsIO.h" nogil: - # workaround for lack of enum class support in Cython < 3.x - # cdef enum class HighsLogType(int): - # kInfo "HighsLogType::kInfo" = 1 - # kDetailed "HighsLogType::kDetailed" - # kVerbose "HighsLogType::kVerbose" - # kWarning "HighsLogType::kWarning" - # kError "HighsLogType::kError" - - cdef cppclass HighsLogType: - pass - - cdef HighsLogType kInfo "HighsLogType::kInfo" - cdef HighsLogType kDetailed "HighsLogType::kDetailed" - cdef HighsLogType kVerbose "HighsLogType::kVerbose" - cdef HighsLogType kWarning "HighsLogType::kWarning" - cdef HighsLogType kError "HighsLogType::kError" diff --git a/scipy/optimize/_highs/cython/src/HighsInfo.pxd b/scipy/optimize/_highs/cython/src/HighsInfo.pxd deleted file mode 100644 index 789b51089896..000000000000 --- a/scipy/optimize/_highs/cython/src/HighsInfo.pxd +++ /dev/null @@ -1,22 +0,0 @@ -# cython: language_level=3 - -cdef extern from "HighsInfo.h" nogil: - # From HiGHS/src/lp_data/HighsInfo.h - cdef cppclass HighsInfo: - # Inherited from HighsInfoStruct: - int mip_node_count - int simplex_iteration_count - int ipm_iteration_count - int crossover_iteration_count - int primal_solution_status - int dual_solution_status - int basis_validity - double objective_function_value - double mip_dual_bound - double mip_gap - int num_primal_infeasibilities - double max_primal_infeasibility - double sum_primal_infeasibilities - int num_dual_infeasibilities - double max_dual_infeasibility - double sum_dual_infeasibilities diff --git a/scipy/optimize/_highs/cython/src/HighsLp.pxd b/scipy/optimize/_highs/cython/src/HighsLp.pxd deleted file mode 100644 index 0944f083743f..000000000000 --- a/scipy/optimize/_highs/cython/src/HighsLp.pxd +++ /dev/null @@ -1,46 +0,0 @@ -# cython: language_level=3 - -from libcpp cimport bool -from libcpp.string cimport string -from libcpp.vector cimport vector - -from .HConst cimport HighsBasisStatus, ObjSense, HighsVarType -from .HighsSparseMatrix cimport HighsSparseMatrix - - -cdef extern from "HighsLp.h" nogil: - # From HiGHS/src/lp_data/HighsLp.h - cdef cppclass HighsLp: - int num_col_ - int num_row_ - - vector[double] col_cost_ - vector[double] col_lower_ - vector[double] col_upper_ - vector[double] row_lower_ - vector[double] row_upper_ - - HighsSparseMatrix a_matrix_ - - ObjSense sense_ - double offset_ - - string model_name_ - - vector[string] row_names_ - vector[string] col_names_ - - vector[HighsVarType] integrality_ - - bool isMip() const - - cdef cppclass HighsSolution: - vector[double] col_value - vector[double] col_dual - vector[double] row_value - vector[double] row_dual - - cdef cppclass HighsBasis: - bool valid_ - vector[HighsBasisStatus] col_status - vector[HighsBasisStatus] row_status diff --git a/scipy/optimize/_highs/cython/src/HighsLpUtils.pxd b/scipy/optimize/_highs/cython/src/HighsLpUtils.pxd deleted file mode 100644 index 18ede36c146a..000000000000 --- a/scipy/optimize/_highs/cython/src/HighsLpUtils.pxd +++ /dev/null @@ -1,9 +0,0 @@ -# cython: language_level=3 - -from .HighsStatus cimport HighsStatus -from .HighsLp cimport HighsLp -from .HighsOptions cimport HighsOptions - -cdef extern from "HighsLpUtils.h" nogil: - # From HiGHS/src/lp_data/HighsLpUtils.h - HighsStatus assessLp(HighsLp& lp, const HighsOptions& options) diff --git a/scipy/optimize/_highs/cython/src/HighsModelUtils.pxd b/scipy/optimize/_highs/cython/src/HighsModelUtils.pxd deleted file mode 100644 index 4fccc2e80046..000000000000 --- a/scipy/optimize/_highs/cython/src/HighsModelUtils.pxd +++ /dev/null @@ -1,10 +0,0 @@ -# cython: language_level=3 - -from libcpp.string cimport string - -from .HConst cimport HighsModelStatus - -cdef extern from "HighsModelUtils.h" nogil: - # From HiGHS/src/lp_data/HighsModelUtils.h - string utilHighsModelStatusToString(const HighsModelStatus model_status) - string utilBasisStatusToString(const int primal_dual_status) diff --git a/scipy/optimize/_highs/cython/src/HighsOptions.pxd b/scipy/optimize/_highs/cython/src/HighsOptions.pxd deleted file mode 100644 index 920c10c19e30..000000000000 --- a/scipy/optimize/_highs/cython/src/HighsOptions.pxd +++ /dev/null @@ -1,110 +0,0 @@ -# cython: language_level=3 - -from libc.stdio cimport FILE - -from libcpp cimport bool -from libcpp.string cimport string -from libcpp.vector cimport vector - -from .HConst cimport HighsOptionType - -cdef extern from "HighsOptions.h" nogil: - - cdef cppclass OptionRecord: - HighsOptionType type - string name - string description - bool advanced - - cdef cppclass OptionRecordBool(OptionRecord): - bool* value - bool default_value - - cdef cppclass OptionRecordInt(OptionRecord): - int* value - int lower_bound - int default_value - int upper_bound - - cdef cppclass OptionRecordDouble(OptionRecord): - double* value - double lower_bound - double default_value - double upper_bound - - cdef cppclass OptionRecordString(OptionRecord): - string* value - string default_value - - cdef cppclass HighsOptions: - # From HighsOptionsStruct: - - # Options read from the command line - string model_file - string presolve - string solver - string parallel - double time_limit - string options_file - - # Options read from the file - double infinite_cost - double infinite_bound - double small_matrix_value - double large_matrix_value - double primal_feasibility_tolerance - double dual_feasibility_tolerance - double ipm_optimality_tolerance - double dual_objective_value_upper_bound - int highs_debug_level - int simplex_strategy - int simplex_scale_strategy - int simplex_crash_strategy - int simplex_dual_edge_weight_strategy - int simplex_primal_edge_weight_strategy - int simplex_iteration_limit - int simplex_update_limit - int ipm_iteration_limit - int highs_min_threads - int highs_max_threads - int message_level - string solution_file - bool write_solution_to_file - bool write_solution_pretty - - # Advanced options - bool run_crossover - bool mps_parser_type_free - int keep_n_rows - int allowed_simplex_matrix_scale_factor - int allowed_simplex_cost_scale_factor - int simplex_dualise_strategy - int simplex_permute_strategy - int dual_simplex_cleanup_strategy - int simplex_price_strategy - int dual_chuzc_sort_strategy - bool simplex_initial_condition_check - double simplex_initial_condition_tolerance - double dual_steepest_edge_weight_log_error_threshhold - double dual_simplex_cost_perturbation_multiplier - double start_crossover_tolerance - bool less_infeasible_DSE_check - bool less_infeasible_DSE_choose_row - bool use_original_HFactor_logic - - # Options for MIP solver - int mip_max_nodes - int mip_report_level - - # Switch for MIP solver - bool mip - - # Options for HighsPrintMessage and HighsLogMessage - FILE* logfile - FILE* output - int message_level - string solution_file - bool write_solution_to_file - bool write_solution_pretty - - vector[OptionRecord*] records diff --git a/scipy/optimize/_highs/cython/src/HighsRuntimeOptions.pxd b/scipy/optimize/_highs/cython/src/HighsRuntimeOptions.pxd deleted file mode 100644 index 3e227b7a44f7..000000000000 --- a/scipy/optimize/_highs/cython/src/HighsRuntimeOptions.pxd +++ /dev/null @@ -1,9 +0,0 @@ -# cython: language_level=3 - -from libcpp cimport bool - -from .HighsOptions cimport HighsOptions - -cdef extern from "HighsRuntimeOptions.h" nogil: - # From HiGHS/src/lp_data/HighsRuntimeOptions.h - bool loadOptions(int argc, char** argv, HighsOptions& options) diff --git a/scipy/optimize/_highs/cython/src/HighsSparseMatrix.pxd b/scipy/optimize/_highs/cython/src/HighsSparseMatrix.pxd deleted file mode 100644 index 7eaa9ef79eee..000000000000 --- a/scipy/optimize/_highs/cython/src/HighsSparseMatrix.pxd +++ /dev/null @@ -1,15 +0,0 @@ -# cython: language_level=3 - -from libcpp.vector cimport vector - -from .HConst cimport MatrixFormat - - -cdef extern from "HighsSparseMatrix.h" nogil: - cdef cppclass HighsSparseMatrix: - MatrixFormat format_ - int num_col_ - int num_row_ - vector[int] start_ - vector[int] index_ - vector[double] value_ diff --git a/scipy/optimize/_highs/cython/src/HighsStatus.pxd b/scipy/optimize/_highs/cython/src/HighsStatus.pxd deleted file mode 100644 index b47813b5d391..000000000000 --- a/scipy/optimize/_highs/cython/src/HighsStatus.pxd +++ /dev/null @@ -1,12 +0,0 @@ -# cython: language_level=3 - -from libcpp.string cimport string - -cdef extern from "HighsStatus.h" nogil: - ctypedef enum HighsStatus: - HighsStatusError "HighsStatus::kError" = -1 - HighsStatusOK "HighsStatus::kOk" = 0 - HighsStatusWarning "HighsStatus::kWarning" = 1 - - - string highsStatusToString(HighsStatus status) diff --git a/scipy/optimize/_highs/cython/src/SimplexConst.pxd b/scipy/optimize/_highs/cython/src/SimplexConst.pxd deleted file mode 100644 index 77e7b96320d6..000000000000 --- a/scipy/optimize/_highs/cython/src/SimplexConst.pxd +++ /dev/null @@ -1,95 +0,0 @@ -# cython: language_level=3 - -from libcpp cimport bool - -cdef extern from "SimplexConst.h" nogil: - - cdef enum SimplexAlgorithm: - PRIMAL "SimplexAlgorithm::kPrimal" = 0 - DUAL "SimplexAlgorithm::kDual" - - cdef enum SimplexStrategy: - SIMPLEX_STRATEGY_MIN "SimplexStrategy::kSimplexStrategyMin" = 0 - SIMPLEX_STRATEGY_CHOOSE "SimplexStrategy::kSimplexStrategyChoose" = SIMPLEX_STRATEGY_MIN - SIMPLEX_STRATEGY_DUAL "SimplexStrategy::kSimplexStrategyDual" - SIMPLEX_STRATEGY_DUAL_PLAIN "SimplexStrategy::kSimplexStrategyDualPlain" = SIMPLEX_STRATEGY_DUAL - SIMPLEX_STRATEGY_DUAL_TASKS "SimplexStrategy::kSimplexStrategyDualTasks" - SIMPLEX_STRATEGY_DUAL_MULTI "SimplexStrategy::kSimplexStrategyDualMulti" - SIMPLEX_STRATEGY_PRIMAL "SimplexStrategy::kSimplexStrategyPrimal" - SIMPLEX_STRATEGY_MAX "SimplexStrategy::kSimplexStrategyMax" = SIMPLEX_STRATEGY_PRIMAL - SIMPLEX_STRATEGY_NUM "SimplexStrategy::kSimplexStrategyNum" - - cdef enum SimplexCrashStrategy: - SIMPLEX_CRASH_STRATEGY_MIN "SimplexCrashStrategy::kSimplexCrashStrategyMin" = 0 - SIMPLEX_CRASH_STRATEGY_OFF "SimplexCrashStrategy::kSimplexCrashStrategyOff" = SIMPLEX_CRASH_STRATEGY_MIN - SIMPLEX_CRASH_STRATEGY_LTSSF_K "SimplexCrashStrategy::kSimplexCrashStrategyLtssfK" - SIMPLEX_CRASH_STRATEGY_LTSSF "SimplexCrashStrategy::kSimplexCrashStrategyLtssf" = SIMPLEX_CRASH_STRATEGY_LTSSF_K - SIMPLEX_CRASH_STRATEGY_BIXBY "SimplexCrashStrategy::kSimplexCrashStrategyBixby" - SIMPLEX_CRASH_STRATEGY_LTSSF_PRI "SimplexCrashStrategy::kSimplexCrashStrategyLtssfPri" - SIMPLEX_CRASH_STRATEGY_LTSF_K "SimplexCrashStrategy::kSimplexCrashStrategyLtsfK" - SIMPLEX_CRASH_STRATEGY_LTSF_PRI "SimplexCrashStrategy::kSimplexCrashStrategyLtsfPri" - SIMPLEX_CRASH_STRATEGY_LTSF "SimplexCrashStrategy::kSimplexCrashStrategyLtsf" - SIMPLEX_CRASH_STRATEGY_BIXBY_NO_NONZERO_COL_COSTS "SimplexCrashStrategy::kSimplexCrashStrategyBixbyNoNonzeroColCosts" - SIMPLEX_CRASH_STRATEGY_BASIC "SimplexCrashStrategy::kSimplexCrashStrategyBasic" - SIMPLEX_CRASH_STRATEGY_TEST_SING "SimplexCrashStrategy::kSimplexCrashStrategyTestSing" - SIMPLEX_CRASH_STRATEGY_MAX "SimplexCrashStrategy::kSimplexCrashStrategyMax" = SIMPLEX_CRASH_STRATEGY_TEST_SING - - cdef enum SimplexEdgeWeightStrategy: - SIMPLEX_EDGE_WEIGHT_STRATEGY_MIN "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyMin" = -1 - SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyChoose" = SIMPLEX_EDGE_WEIGHT_STRATEGY_MIN - SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyDantzig" - SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyDevex" - SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategySteepestEdge" - SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_UNIT_INITIAL "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategySteepestEdgeUnitInitial" - SIMPLEX_EDGE_WEIGHT_STRATEGY_MAX "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyMax" = SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_UNIT_INITIAL - - cdef enum SimplexPriceStrategy: - SIMPLEX_PRICE_STRATEGY_MIN = 0 - SIMPLEX_PRICE_STRATEGY_COL = SIMPLEX_PRICE_STRATEGY_MIN - SIMPLEX_PRICE_STRATEGY_ROW - SIMPLEX_PRICE_STRATEGY_ROW_SWITCH - SIMPLEX_PRICE_STRATEGY_ROW_SWITCH_COL_SWITCH - SIMPLEX_PRICE_STRATEGY_MAX = SIMPLEX_PRICE_STRATEGY_ROW_SWITCH_COL_SWITCH - - cdef enum SimplexDualChuzcStrategy: - SIMPLEX_DUAL_CHUZC_STRATEGY_MIN = 0 - SIMPLEX_DUAL_CHUZC_STRATEGY_CHOOSE = SIMPLEX_DUAL_CHUZC_STRATEGY_MIN - SIMPLEX_DUAL_CHUZC_STRATEGY_QUAD - SIMPLEX_DUAL_CHUZC_STRATEGY_HEAP - SIMPLEX_DUAL_CHUZC_STRATEGY_BOTH - SIMPLEX_DUAL_CHUZC_STRATEGY_MAX = SIMPLEX_DUAL_CHUZC_STRATEGY_BOTH - - cdef enum InvertHint: - INVERT_HINT_NO = 0 - INVERT_HINT_UPDATE_LIMIT_REACHED - INVERT_HINT_SYNTHETIC_CLOCK_SAYS_INVERT - INVERT_HINT_POSSIBLY_OPTIMAL - INVERT_HINT_POSSIBLY_PRIMAL_UNBOUNDED - INVERT_HINT_POSSIBLY_DUAL_UNBOUNDED - INVERT_HINT_POSSIBLY_SINGULAR_BASIS - INVERT_HINT_PRIMAL_INFEASIBLE_IN_PRIMAL_SIMPLEX - INVERT_HINT_CHOOSE_COLUMN_FAIL - INVERT_HINT_Count - - cdef enum DualEdgeWeightMode: - DANTZIG "DualEdgeWeightMode::DANTZIG" = 0 - DEVEX "DualEdgeWeightMode::DEVEX" - STEEPEST_EDGE "DualEdgeWeightMode::STEEPEST_EDGE" - Count "DualEdgeWeightMode::Count" - - cdef enum PriceMode: - ROW "PriceMode::ROW" = 0 - COL "PriceMode::COL" - - const int PARALLEL_THREADS_DEFAULT - const int DUAL_TASKS_MIN_THREADS - const int DUAL_MULTI_MIN_THREADS - - const bool invert_if_row_out_negative - - const int NONBASIC_FLAG_TRUE - const int NONBASIC_FLAG_FALSE - - const int NONBASIC_MOVE_UP - const int NONBASIC_MOVE_DN - const int NONBASIC_MOVE_ZE diff --git a/scipy/optimize/_highs/cython/src/__init__.py b/scipy/optimize/_highs/cython/src/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/scipy/optimize/_highs/cython/src/_highs_constants.pyx b/scipy/optimize/_highs/cython/src/_highs_constants.pyx deleted file mode 100644 index 815e7d79e9e1..000000000000 --- a/scipy/optimize/_highs/cython/src/_highs_constants.pyx +++ /dev/null @@ -1,117 +0,0 @@ -# cython: language_level=3 - -'''Export enum values and constants from HiGHS.''' - -from .HConst cimport ( - HIGHS_CONST_I_INF, - HIGHS_CONST_INF, - - HighsDebugLevel_kHighsDebugLevelNone, - HighsDebugLevel_kHighsDebugLevelCheap, - - HighsModelStatusNOTSET, - HighsModelStatusLOAD_ERROR, - HighsModelStatusMODEL_ERROR, - HighsModelStatusMODEL_EMPTY, - HighsModelStatusPRESOLVE_ERROR, - HighsModelStatusSOLVE_ERROR, - HighsModelStatusPOSTSOLVE_ERROR, - HighsModelStatusINFEASIBLE, - HighsModelStatus_UNBOUNDED_OR_INFEASIBLE, - HighsModelStatusUNBOUNDED, - HighsModelStatusOPTIMAL, - HighsModelStatusREACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND, - HighsModelStatusREACHED_OBJECTIVE_TARGET, - HighsModelStatusREACHED_TIME_LIMIT, - HighsModelStatusREACHED_ITERATION_LIMIT, - - ObjSenseMINIMIZE, - kContinuous, - kInteger, - kSemiContinuous, - kSemiInteger, - kImplicitInteger, -) -from .HighsIO cimport ( - kInfo, - kDetailed, - kVerbose, - kWarning, - kError, -) -from .SimplexConst cimport ( - # Simplex strategy - SIMPLEX_STRATEGY_CHOOSE, - SIMPLEX_STRATEGY_DUAL, - SIMPLEX_STRATEGY_PRIMAL, - - # Crash strategy - SIMPLEX_CRASH_STRATEGY_OFF, - SIMPLEX_CRASH_STRATEGY_BIXBY, - SIMPLEX_CRASH_STRATEGY_LTSF, - - # Edge weight strategy - SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE, - SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG, - SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX, - SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE, -) - -# HConst -CONST_I_INF = HIGHS_CONST_I_INF -CONST_INF = HIGHS_CONST_INF - -# Debug level -MESSAGE_LEVEL_NONE = HighsDebugLevel_kHighsDebugLevelNone -MESSAGE_LEVEL_MINIMAL = HighsDebugLevel_kHighsDebugLevelCheap - -# HighsIO -LOG_TYPE_INFO = kInfo -LOG_TYPE_DETAILED = kDetailed -LOG_TYPE_VERBOSE = kVerbose -LOG_TYPE_WARNING = kWarning -LOG_TYPE_ERROR = kError - -# HighsLp -MODEL_STATUS_NOTSET = HighsModelStatusNOTSET -MODEL_STATUS_LOAD_ERROR = HighsModelStatusLOAD_ERROR -MODEL_STATUS_MODEL_ERROR = HighsModelStatusMODEL_ERROR -MODEL_STATUS_PRESOLVE_ERROR = HighsModelStatusPRESOLVE_ERROR -MODEL_STATUS_SOLVE_ERROR = HighsModelStatusSOLVE_ERROR -MODEL_STATUS_POSTSOLVE_ERROR = HighsModelStatusPOSTSOLVE_ERROR -MODEL_STATUS_MODEL_EMPTY = HighsModelStatusMODEL_EMPTY -MODEL_STATUS_INFEASIBLE = HighsModelStatusINFEASIBLE -MODEL_STATUS_UNBOUNDED_OR_INFEASIBLE = HighsModelStatus_UNBOUNDED_OR_INFEASIBLE -MODEL_STATUS_UNBOUNDED = HighsModelStatusUNBOUNDED -MODEL_STATUS_OPTIMAL = HighsModelStatusOPTIMAL -MODEL_STATUS_REACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND = HighsModelStatusREACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND -MODEL_STATUS_REACHED_OBJECTIVE_TARGET = HighsModelStatusREACHED_OBJECTIVE_TARGET -MODEL_STATUS_REACHED_TIME_LIMIT = HighsModelStatusREACHED_TIME_LIMIT -MODEL_STATUS_REACHED_ITERATION_LIMIT = HighsModelStatusREACHED_ITERATION_LIMIT - -# Simplex strategy -HIGHS_SIMPLEX_STRATEGY_CHOOSE = SIMPLEX_STRATEGY_CHOOSE -HIGHS_SIMPLEX_STRATEGY_DUAL = SIMPLEX_STRATEGY_DUAL -HIGHS_SIMPLEX_STRATEGY_PRIMAL = SIMPLEX_STRATEGY_PRIMAL - -# Crash strategy -HIGHS_SIMPLEX_CRASH_STRATEGY_OFF = SIMPLEX_CRASH_STRATEGY_OFF -HIGHS_SIMPLEX_CRASH_STRATEGY_BIXBY = SIMPLEX_CRASH_STRATEGY_BIXBY -HIGHS_SIMPLEX_CRASH_STRATEGY_LTSF = SIMPLEX_CRASH_STRATEGY_LTSF - -# Edge weight strategy -HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE = SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE -HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG = SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG -HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX = SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX -HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE = SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE -# HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_UNIT_INITIAL = SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_UNIT_INITIAL - -# Objective sense -HIGHS_OBJECTIVE_SENSE_MINIMIZE = ObjSenseMINIMIZE - -# Variable types -HIGHS_VAR_TYPE_CONTINUOUS = kContinuous -HIGHS_VAR_TYPE_INTEGER = kInteger -HIGHS_VAR_TYPE_SEMI_CONTINUOUS = kSemiContinuous -HIGHS_VAR_TYPE_SEMI_INTEGER = kSemiInteger -HIGHS_VAR_TYPE_IMPLICIT_INTEGER = kImplicitInteger diff --git a/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx b/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx deleted file mode 100644 index d5da4e4fea25..000000000000 --- a/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx +++ /dev/null @@ -1,736 +0,0 @@ -# cython: language_level=3 - -import numpy as np -cimport numpy as np -from scipy.optimize import OptimizeWarning -from warnings import warn -import numbers - -from libcpp.string cimport string -from libcpp.map cimport map as cppmap -from libcpp.cast cimport reinterpret_cast - -from .HConst cimport ( - HIGHS_CONST_INF, - - HighsModelStatus, - HighsModelStatusNOTSET, - HighsModelStatusMODEL_ERROR, - HighsModelStatusOPTIMAL, - HighsModelStatusREACHED_TIME_LIMIT, - HighsModelStatusREACHED_ITERATION_LIMIT, - - HighsOptionTypeBOOL, - HighsOptionTypeINT, - HighsOptionTypeDOUBLE, - HighsOptionTypeSTRING, - - HighsBasisStatus, - HighsBasisStatusLOWER, - HighsBasisStatusUPPER, - - MatrixFormatkColwise, - HighsVarType, -) -from .Highs cimport Highs -from .HighsStatus cimport ( - HighsStatus, - highsStatusToString, - HighsStatusError, - HighsStatusWarning, - HighsStatusOK, -) -from .HighsLp cimport ( - HighsLp, - HighsSolution, - HighsBasis, -) -from .HighsInfo cimport HighsInfo -from .HighsOptions cimport ( - HighsOptions, - OptionRecord, - OptionRecordBool, - OptionRecordInt, - OptionRecordDouble, - OptionRecordString, -) -from .HighsModelUtils cimport utilBasisStatusToString - -np.import_array() - -# options to reference for default values and bounds; -# make a map to quickly lookup -cdef HighsOptions _ref_opts -cdef cppmap[string, OptionRecord*] _ref_opt_lookup -cdef OptionRecord * _r = NULL -for _r in _ref_opts.records: - _ref_opt_lookup[_r.name] = _r - - -cdef str _opt_warning(string name, val, valid_set=None) noexcept: - cdef OptionRecord * r = _ref_opt_lookup[name] - - # BOOL - if r.type == HighsOptionTypeBOOL: - default_value = ( r).default_value - return ('Option "%s" is "%s", but only True or False is allowed. ' - 'Using default: %s.' % (name.decode(), str(val), default_value)) - - # INT - if r.type == HighsOptionTypeINT: - lower_bound = int(( r).lower_bound) - upper_bound = int(( r).upper_bound) - default_value = int(( r).default_value) - if upper_bound - lower_bound < 10: - int_range = str(set(range(lower_bound, upper_bound + 1))) - else: - int_range = '[%d, %d]' % (lower_bound, upper_bound) - return ('Option "%s" is "%s", but only values in %s are allowed. ' - 'Using default: %d.' % (name.decode(), str(val), int_range, default_value)) - - # DOUBLE - if r.type == HighsOptionTypeDOUBLE: - lower_bound = ( r).lower_bound - upper_bound = ( r).upper_bound - default_value = ( r).default_value - return ('Option "%s" is "%s", but only values in (%g, %g) are allowed. ' - 'Using default: %g.' % (name.decode(), str(val), lower_bound, upper_bound, default_value)) - - # STRING - if r.type == HighsOptionTypeSTRING: - if valid_set is not None: - descr = 'but only values in %s are allowed. ' % str(set(valid_set)) - else: - descr = 'but this is an invalid value. %s. ' % r.description.decode() - default_value = ( r).default_value.decode() - return ('Option "%s" is "%s", ' - '%s' - 'Using default: %s.' % (name.decode(), str(val), descr, default_value)) - - # We don't know what type (should be unreachable)? - return('Option "%s" is "%s", but this is not a valid value. ' - 'See documentation for valid options. ' - 'Using default.' % (name.decode(), str(val))) - -cdef apply_options(dict options, Highs & highs) noexcept: - '''Take options from dictionary and apply to HiGHS object.''' - - # Initialize for error checking - cdef HighsStatus opt_status = HighsStatusOK - - # Do all the ints - for opt in set([ - 'allowed_simplex_cost_scale_factor', - 'allowed_simplex_matrix_scale_factor', - 'dual_simplex_cleanup_strategy', - 'ipm_iteration_limit', - 'keep_n_rows', - 'threads', - 'mip_max_nodes', - 'highs_debug_level', - 'simplex_crash_strategy', - 'simplex_dual_edge_weight_strategy', - 'simplex_dualise_strategy', - 'simplex_iteration_limit', - 'simplex_permute_strategy', - 'simplex_price_strategy', - 'simplex_primal_edge_weight_strategy', - 'simplex_scale_strategy', - 'simplex_strategy', - 'simplex_update_limit', - 'small_matrix_value', - ]): - val = options.get(opt, None) - if val is not None: - if not isinstance(val, int): - warn(_opt_warning(opt.encode(), val), OptimizeWarning) - else: - opt_status = highs.setHighsOptionValueInt(opt.encode(), val) - if opt_status != HighsStatusOK: - warn(_opt_warning(opt.encode(), val), OptimizeWarning) - else: - if opt == "threads": - highs.resetGlobalScheduler(blocking=True) - - # Do all the doubles - for opt in set([ - 'dual_feasibility_tolerance', - 'dual_objective_value_upper_bound', - 'dual_simplex_cost_perturbation_multiplier', - 'dual_steepest_edge_weight_log_error_threshhold', - 'infinite_bound', - 'infinite_cost', - 'ipm_optimality_tolerance', - 'large_matrix_value', - 'primal_feasibility_tolerance', - 'simplex_initial_condition_tolerance', - 'small_matrix_value', - 'start_crossover_tolerance', - 'time_limit', - 'mip_rel_gap' - ]): - val = options.get(opt, None) - if val is not None: - if not isinstance(val, numbers.Number): - warn(_opt_warning(opt.encode(), val), OptimizeWarning) - else: - opt_status = highs.setHighsOptionValueDbl(opt.encode(), val) - if opt_status != HighsStatusOK: - warn(_opt_warning(opt.encode(), val), OptimizeWarning) - - - # Do all the strings - for opt in set(['solver']): - val = options.get(opt, None) - if val is not None: - if not isinstance(val, str): - warn(_opt_warning(opt.encode(), val), OptimizeWarning) - else: - opt_status = highs.setHighsOptionValueStr(opt.encode(), val.encode()) - if opt_status != HighsStatusOK: - warn(_opt_warning(opt.encode(), val), OptimizeWarning) - - - # Do all the bool to strings - for opt in set([ - 'parallel', - 'presolve', - ]): - val = options.get(opt, None) - if val is not None: - if isinstance(val, bool): - if val: - val0 = b'on' - else: - val0 = b'off' - opt_status = highs.setHighsOptionValueStr(opt.encode(), val0) - if opt_status != HighsStatusOK: - warn(_opt_warning(opt.encode(), val, valid_set=[True, False]), OptimizeWarning) - else: - warn(_opt_warning(opt.encode(), val, valid_set=[True, False]), OptimizeWarning) - - - # Do the actual bools - for opt in set([ - 'less_infeasible_DSE_check', - 'less_infeasible_DSE_choose_row', - 'log_to_console', - 'mps_parser_type_free', - 'output_flag', - 'run_as_hsol', - 'run_crossover', - 'simplex_initial_condition_check', - 'use_original_HFactor_logic', - ]): - val = options.get(opt, None) - if val is not None: - if val in [True, False]: - opt_status = highs.setHighsOptionValueBool(opt.encode(), val) - if opt_status != HighsStatusOK: - warn(_opt_warning(opt.encode(), val), OptimizeWarning) - else: - warn(_opt_warning(opt.encode(), val), OptimizeWarning) - - -ctypedef HighsVarType* HighsVarType_ptr - - -def _highs_wrapper( - double[::1] c, - int[::1] astart, - int[::1] aindex, - double[::1] avalue, - double[::1] lhs, - double[::1] rhs, - double[::1] lb, - double[::1] ub, - np.uint8_t[::1] integrality, - dict options): - '''Solve linear programs using HiGHS [1]_. - - Assume problems of the form: - - MIN c.T @ x - s.t. lhs <= A @ x <= rhs - lb <= x <= ub - - Parameters - ---------- - c : 1-D array, (n,) - Array of objective value coefficients. - astart : 1-D array - CSC format index array. - aindex : 1-D array - CSC format index array. - avalue : 1-D array - Data array of the matrix. - lhs : 1-D array (or None), (m,) - Array of left hand side values of the inequality constraints. - If ``lhs=None``, then an array of ``-inf`` is assumed. - rhs : 1-D array, (m,) - Array of right hand side values of the inequality constraints. - lb : 1-D array (or None), (n,) - Lower bounds on solution variables x. If ``lb=None``, then an - array of all `0` is assumed. - ub : 1-D array (or None), (n,) - Upper bounds on solution variables x. If ``ub=None``, then an - array of ``inf`` is assumed. - options : dict - A dictionary of solver options with the following fields: - - - allowed_simplex_cost_scale_factor : int - Undocumented advanced option. - - - allowed_simplex_matrix_scale_factor : int - Undocumented advanced option. - - - dual_feasibility_tolerance : double - Dual feasibility tolerance for simplex. - ``min(dual_feasibility_tolerance, - primal_feasibility_tolerance)`` will be used for - ipm feasibility tolerance. - - - dual_objective_value_upper_bound : double - Upper bound on objective value for dual simplex: - algorithm terminates if reached - - - dual_simplex_cleanup_strategy : int - Undocumented advanced option. - - - dual_simplex_cost_perturbation_multiplier : double - Undocumented advanced option. - - - dual_steepest_edge_weight_log_error_threshhold : double - Undocumented advanced option. - - - infinite_bound : double - Limit on abs(constraint bound): values larger than - this will be treated as infinite - - - infinite_cost : double - Limit on cost coefficient: values larger than this - will be treated as infinite. - - - ipm_iteration_limit : int - Iteration limit for interior-point solver. - - - ipm_optimality_tolerance : double - Optimality tolerance for IPM. - - - keep_n_rows : int {-1, 0, 1} - Undocumented advanced option. - - - ``-1``: ``KEEP_N_ROWS_DELETE_ROWS`` - - ``0``: ``KEEP_N_ROWS_DELETE_ENTRIES`` - - ``1``: ``KEEP_N_ROWS_KEEP_ROWS`` - - - large_matrix_value : double - Upper limit on abs(matrix entries): values larger than - this will be treated as infinite - - - less_infeasible_DSE_check : bool - Undocumented advanced option. - - - less_infeasible_DSE_choose_row : bool - Undocumented advanced option. - - - threads : int - Maximum number of threads in parallel execution. - - - message_level : int {0, 1, 2, 4, 7} - Verbosity level, corresponds to: - - - ``0``: ``ML_NONE`` - All messaging to stdout is suppressed. - - - ``1``: ``ML_VERBOSE`` - Includes a once-per-iteration report on simplex/ipm - progress and information about each nonzero row and - column. - - - ``2``: ``ML_DETAILED`` - Includes technical information about progress and - events in applying the simplex method. - - - ``4``: ``ML_MINIMAL`` - Once-per-solve information about progress as well as a - once-per-basis-matrix-reinversion report on progress in - simplex or a once-per-iteration report on progress in IPX. - - ``message_level`` behaves like a bitmask, i.e., any - combination of levels is possible using the bit-or - operator. - - - mps_parser_type_free : bool - Use free format MPS parsing. - - - parallel : bool - Run the solver in serial (False) or parallel (True). - - - presolve : bool - Run the presolve or not (or if ``None``, then choose). - - - primal_feasibility_tolerance : double - Primal feasibility tolerance. - ``min(dual_feasibility_tolerance, - primal_feasibility_tolerance)`` will be used for - ipm feasibility tolerance. - - - run_as_hsol : bool - Undocumented advanced option. - - - run_crossover : bool - Advanced option. Toggles running the crossover routine - for IPX. - - - sense : int {1, -1} - ``sense=1`` corresponds to the MIN problem, ``sense=-1`` - corresponds to the MAX problem. TODO: NOT IMPLEMENTED - - - simplex_crash_strategy : int {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - Strategy for simplex crash: off / LTSSF / Bixby (0/1/2). - Default is ``0``. Corresponds to the following: - - - ``0``: ``SIMPLEX_CRASH_STRATEGY_OFF`` - - ``1``: ``SIMPLEX_CRASH_STRATEGY_LTSSF_K`` - - ``2``: ``SIMPLEX_CRASH_STRATEGY_BIXBY`` - - ``3``: ``SIMPLEX_CRASH_STRATEGY_LTSSF_PRI`` - - ``4``: ``SIMPLEX_CRASH_STRATEGY_LTSF_K`` - - ``5``: ``SIMPLEX_CRASH_STRATEGY_LTSF_PRI`` - - ``6``: ``SIMPLEX_CRASH_STRATEGY_LTSF`` - - ``7``: ``SIMPLEX_CRASH_STRATEGY_BIXBY_NO_NONZERO_COL_COSTS`` - - ``8``: ``SIMPLEX_CRASH_STRATEGY_BASIC`` - - ``9``: ``SIMPLE_CRASH_STRATEGY_TEST_SING`` - - - simplex_dualise_strategy : int - Undocumented advanced option. - - - simplex_dual_edge_weight_strategy : int {0, 1, 2, 3, 4} - Strategy for simplex dual edge weights: - Dantzig / Devex / Steepest Edge. Corresponds - to the following: - - - ``0``: ``SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_DANTZIG`` - - ``1``: ``SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_DEVEX`` - - ``2``: ``SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_TO_DEVEX_SWITCH`` - - ``3``: ``SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE`` - - ``4``: ``SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_UNIT_INITIAL`` - - - simplex_initial_condition_check : bool - Undocumented advanced option. - - - simplex_initial_condition_tolerance : double - Undocumented advanced option. - - - simplex_iteration_limit : int - Iteration limit for simplex solver. - - - simplex_permute_strategy : int - Undocumented advanced option. - - - simplex_price_strategy : int - Undocumented advanced option. - - - simplex_primal_edge_weight_strategy : int {0, 1} - Strategy for simplex primal edge weights: - Dantzig / Devex. Corresponds to the following: - - - ``0``: ``SIMPLEX_PRIMAL_EDGE_WEIGHT_STRATEGY_DANTZIG`` - - ``1``: ``SIMPLEX_PRIMAL_EDGE_WEIGHT_STRATEGY_DEVEX`` - - - simplex_scale_strategy : int {0, 1, 2, 3, 4, 5} - Strategy for scaling before simplex solver: - off / on (0/1) - - - ``0``: ``SIMPLEX_SCALE_STRATEGY_OFF`` - - ``1``: ``SIMPLEX_SCALE_STRATEGY_HIGHS`` - - ``2``: ``SIMPLEX_SCALE_STRATEGY_HIGHS_FORCED`` - - ``3``: ``SIMPLEX_SCALE_STRATEGY_HIGHS_015`` - - ``4``: ``SIMPLEX_SCALE_STRATEGY_HIGHS_0157`` - - ``5``: ``SIMPLEX_SCALE_STRATEGY_HSOL`` - - - simplex_strategy : int {0, 1, 2, 3, 4} - Strategy for simplex solver. Default: 1. Corresponds - to the following: - - - ``0``: ``SIMPLEX_STRATEGY_MIN`` - - ``1``: ``SIMPLEX_STRATEGY_DUAL`` - - ``2``: ``SIMPLEX_STRATEGY_DUAL_TASKS`` - - ``3``: ``SIMPLEX_STRATEGY_DUAL_MULTI`` - - ``4``: ``SIMPLEX_STRATEGY_PRIMAL`` - - - simplex_update_limit : int - Limit on the number of simplex UPDATE operations. - - - small_matrix_value : double - Lower limit on abs(matrix entries): values smaller - than this will be treated as zero. - - - solution_file : str - Solution file - - - solver : str {'simplex', 'ipm'} - Choose which solver to use. If ``solver='simplex'`` - and ``parallel=True`` then PAMI will be used. - - - start_crossover_tolerance : double - Tolerance to be satisfied before IPM crossover will - start. - - - time_limit : double - Max number of seconds to run the solver for. - - - use_original_HFactor_logic : bool - Undocumented advanced option. - - - write_solution_to_file : bool - Write the primal and dual solution to a file - - - write_solution_pretty : bool - Write the primal and dual solution in a pretty - (human-readable) format - - See [2]_ for a list of all non-advanced options. - - Returns - ------- - res : dict - - If model_status is one of OPTIMAL, - REACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND, REACHED_TIME_LIMIT, - REACHED_ITERATION_LIMIT: - - - ``status`` : int - Model status code. - - - ``message`` : str - Message corresponding to model status code. - - - ``x`` : list - Solution variables. - - - ``slack`` : list - Slack variables. - - - ``lambda`` : list - Lagrange multipliers associated with the constraints - Ax = b. - - - ``s`` : list - Lagrange multipliers associated with the constraints - x >= 0. - - - ``fun`` - Final objective value. - - - ``simplex_nit`` : int - Number of iterations accomplished by the simplex - solver. - - - ``ipm_nit`` : int - Number of iterations accomplished by the interior- - point solver. - - If model_status is not one of the above: - - - ``status`` : int - Model status code. - - - ``message`` : str - Message corresponding to model status code. - - Notes - ----- - If ``options['write_solution_to_file']`` is ``True`` but - ``options['solution_file']`` is unset or ``''``, then the solution - will be printed to ``stdout``. - - If any iteration limit is reached, no solution will be - available. - - ``OptimizeWarning`` will be raised if any option value set by - the user is found to be incorrect. - - References - ---------- - .. [1] https://highs.dev/ - .. [2] https://www.maths.ed.ac.uk/hall/HiGHS/HighsOptions.html - ''' - - cdef int numcol = c.size - cdef int numrow = rhs.size - cdef int numnz = avalue.size - cdef int numintegrality = integrality.size - - # Fill up a HighsLp object - cdef HighsLp lp - lp.num_col_ = numcol - lp.num_row_ = numrow - lp.a_matrix_.num_col_ = numcol - lp.a_matrix_.num_row_ = numrow - lp.a_matrix_.format_ = MatrixFormatkColwise - - lp.col_cost_.resize(numcol) - lp.col_lower_.resize(numcol) - lp.col_upper_.resize(numcol) - - lp.row_lower_.resize(numrow) - lp.row_upper_.resize(numrow) - lp.a_matrix_.start_.resize(numcol + 1) - lp.a_matrix_.index_.resize(numnz) - lp.a_matrix_.value_.resize(numnz) - - # only need to set integrality if it's not's empty - cdef HighsVarType * integrality_ptr = NULL - if numintegrality > 0: - lp.integrality_.resize(numintegrality) - integrality_ptr = reinterpret_cast[HighsVarType_ptr](&integrality[0]) - lp.integrality_.assign(integrality_ptr, integrality_ptr + numcol) - - # Explicitly create pointers to pass to HiGHS C++ API; - # do checking to make sure null memory-views are not - # accessed (e.g., &lhs[0] raises exception when lhs is - # empty!) - cdef: - double * colcost_ptr = NULL - double * collower_ptr = NULL - double * colupper_ptr = NULL - double * rowlower_ptr = NULL - double * rowupper_ptr = NULL - int * astart_ptr = NULL - int * aindex_ptr = NULL - double * avalue_ptr = NULL - if numrow > 0: - rowlower_ptr = &lhs[0] - rowupper_ptr = &rhs[0] - lp.row_lower_.assign(rowlower_ptr, rowlower_ptr + numrow) - lp.row_upper_.assign(rowupper_ptr, rowupper_ptr + numrow) - else: - lp.row_lower_.empty() - lp.row_upper_.empty() - if numcol > 0: - colcost_ptr = &c[0] - collower_ptr = &lb[0] - colupper_ptr = &ub[0] - lp.col_cost_.assign(colcost_ptr, colcost_ptr + numcol) - lp.col_lower_.assign(collower_ptr, collower_ptr + numcol) - lp.col_upper_.assign(colupper_ptr, colupper_ptr + numcol) - else: - lp.col_cost_.empty() - lp.col_lower_.empty() - lp.col_upper_.empty() - lp.integrality_.empty() - if numnz > 0: - astart_ptr = &astart[0] - aindex_ptr = &aindex[0] - avalue_ptr = &avalue[0] - lp.a_matrix_.start_.assign(astart_ptr, astart_ptr + numcol + 1) - lp.a_matrix_.index_.assign(aindex_ptr, aindex_ptr + numnz) - lp.a_matrix_.value_.assign(avalue_ptr, avalue_ptr + numnz) - else: - lp.a_matrix_.start_.empty() - lp.a_matrix_.index_.empty() - lp.a_matrix_.value_.empty() - - # Create the options - cdef Highs highs - apply_options(options, highs) - - # Make a Highs object and pass it everything - cdef HighsModelStatus err_model_status = HighsModelStatusNOTSET - cdef HighsStatus init_status = highs.passModel(lp) - if init_status != HighsStatusOK: - if init_status != HighsStatusWarning: - err_model_status = HighsModelStatusMODEL_ERROR - return { - 'status': err_model_status, - 'message': highs.modelStatusToString(err_model_status).decode(), - } - - # Solve the LP - cdef HighsStatus run_status = highs.run() - if run_status == HighsStatusError: - return { - 'status': highs.getModelStatus(), - 'message': highsStatusToString(run_status).decode(), - } - - # Extract what we need from the solution - cdef HighsModelStatus model_status = highs.getModelStatus() - - # We might need an info object if we can look up the solution and a place to put solution - cdef HighsInfo info = highs.getHighsInfo() # it should always be safe to get the info object - cdef HighsSolution solution - cdef HighsBasis basis - cdef double[:, ::1] marg_bnds = np.zeros((2, numcol)) # marg_bnds[0, :]: lower - - # Failure modes: - # LP: if we have anything other than an Optimal status, it - # is unsafe (and unhelpful) to read any results - # MIP: has a non-Optimal status or has timed out/reached max iterations - # 1) If not Optimal/TimedOut/MaxIter status, there is no solution - # 2) If TimedOut/MaxIter status, there may be a feasible solution. - # if the objective function value is not Infinity, then the - # current solution is feasible and can be returned. Else, there - # is no solution. - mipFailCondition = model_status not in { - HighsModelStatusOPTIMAL, - HighsModelStatusREACHED_TIME_LIMIT, - HighsModelStatusREACHED_ITERATION_LIMIT, - } or (model_status in { - HighsModelStatusREACHED_TIME_LIMIT, - HighsModelStatusREACHED_ITERATION_LIMIT, - } and (info.objective_function_value == HIGHS_CONST_INF)) - lpFailCondition = model_status != HighsModelStatusOPTIMAL - if (highs.getLp().isMip() and mipFailCondition) or (not highs.getLp().isMip() and lpFailCondition): - return { - 'status': model_status, - 'message': f'model_status is {highs.modelStatusToString(model_status).decode()}; ' - f'primal_status is {utilBasisStatusToString( info.primal_solution_status).decode()}', - 'simplex_nit': info.simplex_iteration_count, - 'ipm_nit': info.ipm_iteration_count, - 'fun': None, - 'crossover_nit': info.crossover_iteration_count, - } - # If the model status is such that the solution can be read - else: - # Should be safe to read the solution: - solution = highs.getSolution() - basis = highs.getBasis() - - # lagrangians for bounds based on column statuses - for ii in range(numcol): - if HighsBasisStatusLOWER == basis.col_status[ii]: - marg_bnds[0, ii] = solution.col_dual[ii] - elif HighsBasisStatusUPPER == basis.col_status[ii]: - marg_bnds[1, ii] = solution.col_dual[ii] - - res = { - 'status': model_status, - 'message': highs.modelStatusToString(model_status).decode(), - - # Primal solution - 'x': [solution.col_value[ii] for ii in range(numcol)], - - # Ax + s = b => Ax = b - s - # Note: this is for all constraints (A_ub and A_eq) - 'slack': [rhs[ii] - solution.row_value[ii] for ii in range(numrow)], - - # lambda are the lagrange multipliers associated with Ax=b - 'lambda': [solution.row_dual[ii] for ii in range(numrow)], - 'marg_bnds': marg_bnds, - - 'fun': info.objective_function_value, - 'simplex_nit': info.simplex_iteration_count, - 'ipm_nit': info.ipm_iteration_count, - 'crossover_nit': info.crossover_iteration_count, - } - - if highs.getLp().isMip(): - res.update({ - 'mip_node_count': info.mip_node_count, - 'mip_dual_bound': info.mip_dual_bound, - 'mip_gap': info.mip_gap, - }) - - return res diff --git a/scipy/optimize/_highs/cython/src/highs_c_api.pxd b/scipy/optimize/_highs/cython/src/highs_c_api.pxd deleted file mode 100644 index b7097caf30bc..000000000000 --- a/scipy/optimize/_highs/cython/src/highs_c_api.pxd +++ /dev/null @@ -1,7 +0,0 @@ -# cython: language_level=3 - -cdef extern from "highs_c_api.h" nogil: - int Highs_passLp(void* highs, int numcol, int numrow, int numnz, - double* colcost, double* collower, double* colupper, - double* rowlower, double* rowupper, - int* astart, int* aindex, double* avalue) diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index 39d8c16226e9..9434512de9b8 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -1,284 +1,300 @@ -highs_define_macros = [ - '-DCMAKE_BUILD_TYPE="RELEASE"', - '-DFAST_BUILD=ON', - '-DHIGHS_GITHASH="n/a"', - '-DHIGHS_COMPILATION_DATE="2021-07-09"', # cannot generate dynamically - '-DHIGHS_VERSION_MAJOR=1', # don't care about this, look at CMakelists.txt - '-DHIGHS_VERSION_MINOR=2', - '-DHIGHS_VERSION_PATCH=0', - '-DHIGHS_DIR=' + meson.current_source_dir() / '..' / '..' / '_lib' / 'highs', - '-UOPENMP', - '-UEXT_PRESOLVE', - '-USCIP_DEV', - '-UHiGHSDEV', - '-UOSI_FOUND', - '-DNDEBUG' -] +# highs_define_macros = [ +# '-DCMAKE_BUILD_TYPE="RELEASE"', +# '-DFAST_BUILD=ON', +# '-DHIGHS_GITHASH="n/a"', +# '-DHIGHS_COMPILATION_DATE="2021-07-09"', # cannot generate dynamically +# '-DHIGHS_VERSION_MAJOR=1', # don't care about this, look at CMakelists.txt +# '-DHIGHS_VERSION_MINOR=2', +# '-DHIGHS_VERSION_PATCH=0', +# '-DHIGHS_DIR=' + meson.current_source_dir() / '..' / '..' / '_lib' / 'highs', +# '-UOPENMP', +# '-UEXT_PRESOLVE', +# '-USCIP_DEV', +# '-UHiGHSDEV', +# '-UOSI_FOUND', +# '-DNDEBUG' +# ] -basiclu_lib = static_library('basiclu', - [ - '../../_lib/highs/src/ipm/basiclu/basiclu_factorize.c', - '../../_lib/highs/src/ipm/basiclu/basiclu_get_factors.c', - '../../_lib/highs/src/ipm/basiclu/basiclu_initialize.c', - '../../_lib/highs/src/ipm/basiclu/basiclu_object.c', - '../../_lib/highs/src/ipm/basiclu/basiclu_solve_dense.c', - '../../_lib/highs/src/ipm/basiclu/basiclu_solve_for_update.c', - '../../_lib/highs/src/ipm/basiclu/basiclu_solve_sparse.c', - '../../_lib/highs/src/ipm/basiclu/basiclu_update.c', - '../../_lib/highs/src/ipm/basiclu/lu_build_factors.c', - '../../_lib/highs/src/ipm/basiclu/lu_condest.c', - '../../_lib/highs/src/ipm/basiclu/lu_dfs.c', - '../../_lib/highs/src/ipm/basiclu/lu_factorize_bump.c', - '../../_lib/highs/src/ipm/basiclu/lu_file.c', - '../../_lib/highs/src/ipm/basiclu/lu_garbage_perm.c', - '../../_lib/highs/src/ipm/basiclu/lu_initialize.c', - '../../_lib/highs/src/ipm/basiclu/lu_internal.c', - '../../_lib/highs/src/ipm/basiclu/lu_markowitz.c', - '../../_lib/highs/src/ipm/basiclu/lu_matrix_norm.c', - '../../_lib/highs/src/ipm/basiclu/lu_pivot.c', - '../../_lib/highs/src/ipm/basiclu/lu_residual_test.c', - '../../_lib/highs/src/ipm/basiclu/lu_setup_bump.c', - '../../_lib/highs/src/ipm/basiclu/lu_singletons.c', - '../../_lib/highs/src/ipm/basiclu/lu_solve_dense.c', - '../../_lib/highs/src/ipm/basiclu/lu_solve_for_update.c', - '../../_lib/highs/src/ipm/basiclu/lu_solve_sparse.c', - '../../_lib/highs/src/ipm/basiclu/lu_solve_symbolic.c', - '../../_lib/highs/src/ipm/basiclu/lu_solve_triangular.c', - '../../_lib/highs/src/ipm/basiclu/lu_update.c' - ], - include_directories: [ - 'src', - '../../_lib/highs/src', - '../../_lib/highs/src/ipm/basiclu' - ], - c_args: [Wno_unused_variable, highs_define_macros] -) +# basiclu_lib = static_library('basiclu', +# [ +# '../../_lib/highs/src/ipm/basiclu/basiclu_factorize.c', +# '../../_lib/highs/src/ipm/basiclu/basiclu_get_factors.c', +# '../../_lib/highs/src/ipm/basiclu/basiclu_initialize.c', +# '../../_lib/highs/src/ipm/basiclu/basiclu_object.c', +# '../../_lib/highs/src/ipm/basiclu/basiclu_solve_dense.c', +# '../../_lib/highs/src/ipm/basiclu/basiclu_solve_for_update.c', +# '../../_lib/highs/src/ipm/basiclu/basiclu_solve_sparse.c', +# '../../_lib/highs/src/ipm/basiclu/basiclu_update.c', +# '../../_lib/highs/src/ipm/basiclu/lu_build_factors.c', +# '../../_lib/highs/src/ipm/basiclu/lu_condest.c', +# '../../_lib/highs/src/ipm/basiclu/lu_dfs.c', +# '../../_lib/highs/src/ipm/basiclu/lu_factorize_bump.c', +# '../../_lib/highs/src/ipm/basiclu/lu_file.c', +# '../../_lib/highs/src/ipm/basiclu/lu_garbage_perm.c', +# '../../_lib/highs/src/ipm/basiclu/lu_initialize.c', +# '../../_lib/highs/src/ipm/basiclu/lu_internal.c', +# '../../_lib/highs/src/ipm/basiclu/lu_markowitz.c', +# '../../_lib/highs/src/ipm/basiclu/lu_matrix_norm.c', +# '../../_lib/highs/src/ipm/basiclu/lu_pivot.c', +# '../../_lib/highs/src/ipm/basiclu/lu_residual_test.c', +# '../../_lib/highs/src/ipm/basiclu/lu_setup_bump.c', +# '../../_lib/highs/src/ipm/basiclu/lu_singletons.c', +# '../../_lib/highs/src/ipm/basiclu/lu_solve_dense.c', +# '../../_lib/highs/src/ipm/basiclu/lu_solve_for_update.c', +# '../../_lib/highs/src/ipm/basiclu/lu_solve_sparse.c', +# '../../_lib/highs/src/ipm/basiclu/lu_solve_symbolic.c', +# '../../_lib/highs/src/ipm/basiclu/lu_solve_triangular.c', +# '../../_lib/highs/src/ipm/basiclu/lu_update.c' +# ], +# include_directories: [ +# '../../_lib/highs/src', +# '../../_lib/highs/src/ipm/basiclu', +# 'src/' +# ], +# c_args: [Wno_unused_variable, highs_define_macros] +# ) -highs_flags = [ - _cpp_Wno_class_memaccess, - _cpp_Wno_format_truncation, - _cpp_Wno_non_virtual_dtor, - _cpp_Wno_sign_compare, - _cpp_Wno_switch, - _cpp_Wno_unused_but_set_variable, - _cpp_Wno_unused_variable, -] +# highs_flags = [ +# _cpp_Wno_class_memaccess, +# _cpp_Wno_format_truncation, +# _cpp_Wno_non_virtual_dtor, +# _cpp_Wno_sign_compare, +# _cpp_Wno_switch, +# _cpp_Wno_unused_but_set_variable, +# _cpp_Wno_unused_variable, +# ] -ipx_lib = static_library('ipx', - [ - '../../_lib/highs/src/ipm/ipx/basiclu_kernel.cc', - '../../_lib/highs/src/ipm/ipx/basiclu_wrapper.cc', - '../../_lib/highs/src/ipm/ipx/basis.cc', - '../../_lib/highs/src/ipm/ipx/conjugate_residuals.cc', - '../../_lib/highs/src/ipm/ipx/control.cc', - '../../_lib/highs/src/ipm/ipx/crossover.cc', - '../../_lib/highs/src/ipm/ipx/diagonal_precond.cc', - '../../_lib/highs/src/ipm/ipx/forrest_tomlin.cc', - '../../_lib/highs/src/ipm/ipx/guess_basis.cc', - '../../_lib/highs/src/ipm/ipx/indexed_vector.cc', - '../../_lib/highs/src/ipm/ipx/info.cc', - '../../_lib/highs/src/ipm/ipx/ipm.cc', - '../../_lib/highs/src/ipm/ipx/ipx_c.cc', - '../../_lib/highs/src/ipm/ipx/iterate.cc', - '../../_lib/highs/src/ipm/ipx/kkt_solver.cc', - '../../_lib/highs/src/ipm/ipx/kkt_solver_basis.cc', - '../../_lib/highs/src/ipm/ipx/kkt_solver_diag.cc', - '../../_lib/highs/src/ipm/ipx/linear_operator.cc', - '../../_lib/highs/src/ipm/ipx/lp_solver.cc', - '../../_lib/highs/src/ipm/ipx/lu_factorization.cc', - '../../_lib/highs/src/ipm/ipx/lu_update.cc', - '../../_lib/highs/src/ipm/ipx/maxvolume.cc', - '../../_lib/highs/src/ipm/ipx/model.cc', - '../../_lib/highs/src/ipm/ipx/normal_matrix.cc', - '../../_lib/highs/src/ipm/ipx/sparse_matrix.cc', - '../../_lib/highs/src/ipm/ipx/sparse_utils.cc', - '../../_lib/highs/src/ipm/ipx/splitted_normal_matrix.cc', - '../../_lib/highs/src/ipm/ipx/starting_basis.cc', - '../../_lib/highs/src/ipm/ipx/symbolic_invert.cc', - '../../_lib/highs/src/ipm/ipx/timer.cc', - '../../_lib/highs/src/ipm/ipx/utils.cc' - ], - include_directories: [ - '../../_lib/highs/src/ipm/ipx/', - '../../_lib/highs/src/ipm/basiclu/', - '../../_lib/highs/src/', - '../../_lib/highs/extern/', - 'cython/src/' - ], - dependencies: thread_dep, - cpp_args: [highs_flags, highs_define_macros] -) +# ipx_lib = static_library('ipx', +# [ +# '../../_lib/highs/src/ipm/ipx/basiclu_kernel.cc', +# '../../_lib/highs/src/ipm/ipx/basiclu_wrapper.cc', +# '../../_lib/highs/src/ipm/ipx/basis.cc', +# '../../_lib/highs/src/ipm/ipx/conjugate_residuals.cc', +# '../../_lib/highs/src/ipm/ipx/control.cc', +# '../../_lib/highs/src/ipm/ipx/crossover.cc', +# '../../_lib/highs/src/ipm/ipx/diagonal_precond.cc', +# '../../_lib/highs/src/ipm/ipx/forrest_tomlin.cc', +# '../../_lib/highs/src/ipm/ipx/guess_basis.cc', +# '../../_lib/highs/src/ipm/ipx/indexed_vector.cc', +# '../../_lib/highs/src/ipm/ipx/info.cc', +# '../../_lib/highs/src/ipm/ipx/ipm.cc', +# '../../_lib/highs/src/ipm/ipx/ipx_c.cc', +# '../../_lib/highs/src/ipm/ipx/iterate.cc', +# '../../_lib/highs/src/ipm/ipx/kkt_solver.cc', +# '../../_lib/highs/src/ipm/ipx/kkt_solver_basis.cc', +# '../../_lib/highs/src/ipm/ipx/kkt_solver_diag.cc', +# '../../_lib/highs/src/ipm/ipx/linear_operator.cc', +# '../../_lib/highs/src/ipm/ipx/lp_solver.cc', +# '../../_lib/highs/src/ipm/ipx/lu_factorization.cc', +# '../../_lib/highs/src/ipm/ipx/lu_update.cc', +# '../../_lib/highs/src/ipm/ipx/maxvolume.cc', +# '../../_lib/highs/src/ipm/ipx/model.cc', +# '../../_lib/highs/src/ipm/ipx/normal_matrix.cc', +# '../../_lib/highs/src/ipm/ipx/sparse_matrix.cc', +# '../../_lib/highs/src/ipm/ipx/sparse_utils.cc', +# '../../_lib/highs/src/ipm/ipx/splitted_normal_matrix.cc', +# '../../_lib/highs/src/ipm/ipx/starting_basis.cc', +# '../../_lib/highs/src/ipm/ipx/symbolic_invert.cc', +# '../../_lib/highs/src/ipm/ipx/timer.cc', +# '../../_lib/highs/src/ipm/ipx/utils.cc' +# ], +# include_directories: [ +# '../../_lib/highs/src/ipm/ipx/', +# '../../_lib/highs/src/ipm/basiclu/', +# '../../_lib/highs/src/', +# '../../_lib/highs/extern/', +# 'src/' +# ], +# dependencies: thread_dep, +# cpp_args: [highs_flags, highs_define_macros] +# ) -highs_lib = static_library('highs', - [ - '../../_lib/highs/extern/filereaderlp/reader.cpp', - '../../_lib/highs/src/io/Filereader.cpp', - '../../_lib/highs/src/io/FilereaderLp.cpp', - '../../_lib/highs/src/io/FilereaderEms.cpp', - '../../_lib/highs/src/io/FilereaderMps.cpp', - '../../_lib/highs/src/io/HighsIO.cpp', - '../../_lib/highs/src/io/HMPSIO.cpp', - '../../_lib/highs/src/io/HMpsFF.cpp', - '../../_lib/highs/src/io/LoadOptions.cpp', - '../../_lib/highs/src/ipm/IpxWrapper.cpp', - '../../_lib/highs/src/lp_data/Highs.cpp', - '../../_lib/highs/src/lp_data/HighsDebug.cpp', - '../../_lib/highs/src/lp_data/HighsInfo.cpp', - '../../_lib/highs/src/lp_data/HighsInfoDebug.cpp', - '../../_lib/highs/src/lp_data/HighsDeprecated.cpp', - '../../_lib/highs/src/lp_data/HighsInterface.cpp', - '../../_lib/highs/src/lp_data/HighsLp.cpp', - '../../_lib/highs/src/lp_data/HighsLpUtils.cpp', - '../../_lib/highs/src/lp_data/HighsModelUtils.cpp', - '../../_lib/highs/src/lp_data/HighsRanging.cpp', - '../../_lib/highs/src/lp_data/HighsSolution.cpp', - '../../_lib/highs/src/lp_data/HighsSolutionDebug.cpp', - '../../_lib/highs/src/lp_data/HighsSolve.cpp', - '../../_lib/highs/src/lp_data/HighsStatus.cpp', - '../../_lib/highs/src/lp_data/HighsOptions.cpp', - '../../_lib/highs/src/mip/HighsMipSolver.cpp', - '../../_lib/highs/src/mip/HighsMipSolverData.cpp', - '../../_lib/highs/src/mip/HighsDomain.cpp', - '../../_lib/highs/src/mip/HighsDynamicRowMatrix.cpp', - '../../_lib/highs/src/mip/HighsLpRelaxation.cpp', - '../../_lib/highs/src/mip/HighsSeparation.cpp', - '../../_lib/highs/src/mip/HighsSeparator.cpp', - '../../_lib/highs/src/mip/HighsTableauSeparator.cpp', - '../../_lib/highs/src/mip/HighsModkSeparator.cpp', - '../../_lib/highs/src/mip/HighsPathSeparator.cpp', - '../../_lib/highs/src/mip/HighsCutGeneration.cpp', - '../../_lib/highs/src/mip/HighsSearch.cpp', - '../../_lib/highs/src/mip/HighsConflictPool.cpp', - '../../_lib/highs/src/mip/HighsCutPool.cpp', - '../../_lib/highs/src/mip/HighsCliqueTable.cpp', - '../../_lib/highs/src/mip/HighsGFkSolve.cpp', - '../../_lib/highs/src/mip/HighsTransformedLp.cpp', - '../../_lib/highs/src/mip/HighsLpAggregator.cpp', - '../../_lib/highs/src/mip/HighsDebugSol.cpp', - '../../_lib/highs/src/mip/HighsImplications.cpp', - '../../_lib/highs/src/mip/HighsPrimalHeuristics.cpp', - '../../_lib/highs/src/mip/HighsPseudocost.cpp', - '../../_lib/highs/src/mip/HighsRedcostFixing.cpp', - '../../_lib/highs/src/mip/HighsNodeQueue.cpp', - '../../_lib/highs/src/mip/HighsObjectiveFunction.cpp', - '../../_lib/highs/src/model/HighsHessian.cpp', - '../../_lib/highs/src/model/HighsHessianUtils.cpp', - '../../_lib/highs/src/model/HighsModel.cpp', - '../../_lib/highs/src/parallel/HighsTaskExecutor.cpp', - '../../_lib/highs/src/presolve/ICrash.cpp', - '../../_lib/highs/src/presolve/ICrashUtil.cpp', - '../../_lib/highs/src/presolve/ICrashX.cpp', - '../../_lib/highs/src/presolve/HighsPostsolveStack.cpp', - '../../_lib/highs/src/presolve/HighsSymmetry.cpp', - '../../_lib/highs/src/presolve/HPresolve.cpp', - '../../_lib/highs/src/presolve/PresolveComponent.cpp', - '../../_lib/highs/src/qpsolver/basis.cpp', - '../../_lib/highs/src/qpsolver/quass.cpp', - '../../_lib/highs/src/qpsolver/ratiotest.cpp', - '../../_lib/highs/src/qpsolver/scaling.cpp', - '../../_lib/highs/src/qpsolver/perturbation.cpp', - '../../_lib/highs/src/simplex/HEkk.cpp', - '../../_lib/highs/src/simplex/HEkkControl.cpp', - '../../_lib/highs/src/simplex/HEkkDebug.cpp', - '../../_lib/highs/src/simplex/HEkkPrimal.cpp', - '../../_lib/highs/src/simplex/HEkkDual.cpp', - '../../_lib/highs/src/simplex/HEkkDualRHS.cpp', - '../../_lib/highs/src/simplex/HEkkDualRow.cpp', - '../../_lib/highs/src/simplex/HEkkDualMulti.cpp', - '../../_lib/highs/src/simplex/HEkkInterface.cpp', - '../../_lib/highs/src/simplex/HighsSimplexAnalysis.cpp', - '../../_lib/highs/src/simplex/HSimplex.cpp', - '../../_lib/highs/src/simplex/HSimplexDebug.cpp', - '../../_lib/highs/src/simplex/HSimplexNla.cpp', - '../../_lib/highs/src/simplex/HSimplexNlaDebug.cpp', - '../../_lib/highs/src/simplex/HSimplexNlaFreeze.cpp', - '../../_lib/highs/src/simplex/HSimplexNlaProductForm.cpp', - '../../_lib/highs/src/simplex/HSimplexReport.cpp', - '../../_lib/highs/src/test/DevKkt.cpp', - '../../_lib/highs/src/test/KktCh2.cpp', - '../../_lib/highs/src/util/HFactor.cpp', - '../../_lib/highs/src/util/HFactorDebug.cpp', - '../../_lib/highs/src/util/HFactorExtend.cpp', - '../../_lib/highs/src/util/HFactorRefactor.cpp', - '../../_lib/highs/src/util/HFactorUtils.cpp', - '../../_lib/highs/src/util/HighsHash.cpp', - '../../_lib/highs/src/util/HighsLinearSumBounds.cpp', - '../../_lib/highs/src/util/HighsMatrixPic.cpp', - '../../_lib/highs/src/util/HighsMatrixUtils.cpp', - '../../_lib/highs/src/util/HighsSort.cpp', - '../../_lib/highs/src/util/HighsSparseMatrix.cpp', - '../../_lib/highs/src/util/HighsUtils.cpp', - '../../_lib/highs/src/util/HSet.cpp', - '../../_lib/highs/src/util/HVectorBase.cpp', - '../../_lib/highs/src/util/stringutil.cpp', - '../../_lib/highs/src/interfaces/highs_c_api.cpp' - ], - include_directories: [ - 'src/', - '../../_lib/highs/extern/', - '../../_lib/highs/src/', - '../../_lib/highs/src/io/', - '../../_lib/highs/src/ipm/ipx/', - '../../_lib/highs/src/lp_data/', - '../../_lib/highs/src/util/', - ], - dependencies: thread_dep, - cpp_args: [highs_flags, highs_define_macros] -) +# highs_lib = static_library('highs', +# [ +# '../../_lib/highs/extern/filereaderlp/reader.cpp', +# '../../_lib/highs/src/io/Filereader.cpp', +# '../../_lib/highs/src/io/FilereaderLp.cpp', +# '../../_lib/highs/src/io/FilereaderEms.cpp', +# '../../_lib/highs/src/io/FilereaderMps.cpp', +# '../../_lib/highs/src/io/HighsIO.cpp', +# '../../_lib/highs/src/io/HMPSIO.cpp', +# '../../_lib/highs/src/io/HMpsFF.cpp', +# '../../_lib/highs/src/io/LoadOptions.cpp', +# '../../_lib/highs/src/ipm/IpxWrapper.cpp', +# '../../_lib/highs/src/lp_data/Highs.cpp', +# '../../_lib/highs/src/lp_data/HighsDebug.cpp', +# '../../_lib/highs/src/lp_data/HighsInfo.cpp', +# '../../_lib/highs/src/lp_data/HighsInfoDebug.cpp', +# '../../_lib/highs/src/lp_data/HighsDeprecated.cpp', +# '../../_lib/highs/src/lp_data/HighsInterface.cpp', +# '../../_lib/highs/src/lp_data/HighsLp.cpp', +# '../../_lib/highs/src/lp_data/HighsLpUtils.cpp', +# '../../_lib/highs/src/lp_data/HighsModelUtils.cpp', +# '../../_lib/highs/src/lp_data/HighsRanging.cpp', +# '../../_lib/highs/src/lp_data/HighsSolution.cpp', +# '../../_lib/highs/src/lp_data/HighsSolutionDebug.cpp', +# '../../_lib/highs/src/lp_data/HighsSolve.cpp', +# '../../_lib/highs/src/lp_data/HighsStatus.cpp', +# '../../_lib/highs/src/lp_data/HighsOptions.cpp', +# '../../_lib/highs/src/mip/HighsMipSolver.cpp', +# '../../_lib/highs/src/mip/HighsMipSolverData.cpp', +# '../../_lib/highs/src/mip/HighsDomain.cpp', +# '../../_lib/highs/src/mip/HighsDynamicRowMatrix.cpp', +# '../../_lib/highs/src/mip/HighsLpRelaxation.cpp', +# '../../_lib/highs/src/mip/HighsSeparation.cpp', +# '../../_lib/highs/src/mip/HighsSeparator.cpp', +# '../../_lib/highs/src/mip/HighsTableauSeparator.cpp', +# '../../_lib/highs/src/mip/HighsModkSeparator.cpp', +# '../../_lib/highs/src/mip/HighsPathSeparator.cpp', +# '../../_lib/highs/src/mip/HighsCutGeneration.cpp', +# '../../_lib/highs/src/mip/HighsSearch.cpp', +# '../../_lib/highs/src/mip/HighsConflictPool.cpp', +# '../../_lib/highs/src/mip/HighsCutPool.cpp', +# '../../_lib/highs/src/mip/HighsCliqueTable.cpp', +# '../../_lib/highs/src/mip/HighsGFkSolve.cpp', +# '../../_lib/highs/src/mip/HighsTransformedLp.cpp', +# '../../_lib/highs/src/mip/HighsLpAggregator.cpp', +# '../../_lib/highs/src/mip/HighsDebugSol.cpp', +# '../../_lib/highs/src/mip/HighsImplications.cpp', +# '../../_lib/highs/src/mip/HighsPrimalHeuristics.cpp', +# '../../_lib/highs/src/mip/HighsPseudocost.cpp', +# '../../_lib/highs/src/mip/HighsRedcostFixing.cpp', +# '../../_lib/highs/src/mip/HighsNodeQueue.cpp', +# '../../_lib/highs/src/mip/HighsObjectiveFunction.cpp', +# '../../_lib/highs/src/model/HighsHessian.cpp', +# '../../_lib/highs/src/model/HighsHessianUtils.cpp', +# '../../_lib/highs/src/model/HighsModel.cpp', +# '../../_lib/highs/src/parallel/HighsTaskExecutor.cpp', +# '../../_lib/highs/src/presolve/ICrash.cpp', +# '../../_lib/highs/src/presolve/ICrashUtil.cpp', +# '../../_lib/highs/src/presolve/ICrashX.cpp', +# '../../_lib/highs/src/presolve/HighsPostsolveStack.cpp', +# '../../_lib/highs/src/presolve/HighsSymmetry.cpp', +# '../../_lib/highs/src/presolve/HPresolve.cpp', +# '../../_lib/highs/src/presolve/PresolveComponent.cpp', +# '../../_lib/highs/src/qpsolver/basis.cpp', +# '../../_lib/highs/src/qpsolver/quass.cpp', +# '../../_lib/highs/src/qpsolver/ratiotest.cpp', +# '../../_lib/highs/src/qpsolver/scaling.cpp', +# '../../_lib/highs/src/qpsolver/perturbation.cpp', +# '../../_lib/highs/src/simplex/HEkk.cpp', +# '../../_lib/highs/src/simplex/HEkkControl.cpp', +# '../../_lib/highs/src/simplex/HEkkDebug.cpp', +# '../../_lib/highs/src/simplex/HEkkPrimal.cpp', +# '../../_lib/highs/src/simplex/HEkkDual.cpp', +# '../../_lib/highs/src/simplex/HEkkDualRHS.cpp', +# '../../_lib/highs/src/simplex/HEkkDualRow.cpp', +# '../../_lib/highs/src/simplex/HEkkDualMulti.cpp', +# '../../_lib/highs/src/simplex/HEkkInterface.cpp', +# '../../_lib/highs/src/simplex/HighsSimplexAnalysis.cpp', +# '../../_lib/highs/src/simplex/HSimplex.cpp', +# '../../_lib/highs/src/simplex/HSimplexDebug.cpp', +# '../../_lib/highs/src/simplex/HSimplexNla.cpp', +# '../../_lib/highs/src/simplex/HSimplexNlaDebug.cpp', +# '../../_lib/highs/src/simplex/HSimplexNlaFreeze.cpp', +# '../../_lib/highs/src/simplex/HSimplexNlaProductForm.cpp', +# '../../_lib/highs/src/simplex/HSimplexReport.cpp', +# '../../_lib/highs/src/test/DevKkt.cpp', +# '../../_lib/highs/src/test/KktCh2.cpp', +# '../../_lib/highs/src/util/HFactor.cpp', +# '../../_lib/highs/src/util/HFactorDebug.cpp', +# '../../_lib/highs/src/util/HFactorExtend.cpp', +# '../../_lib/highs/src/util/HFactorRefactor.cpp', +# '../../_lib/highs/src/util/HFactorUtils.cpp', +# '../../_lib/highs/src/util/HighsHash.cpp', +# '../../_lib/highs/src/util/HighsLinearSumBounds.cpp', +# '../../_lib/highs/src/util/HighsMatrixPic.cpp', +# '../../_lib/highs/src/util/HighsMatrixUtils.cpp', +# '../../_lib/highs/src/util/HighsSort.cpp', +# '../../_lib/highs/src/util/HighsSparseMatrix.cpp', +# '../../_lib/highs/src/util/HighsUtils.cpp', +# '../../_lib/highs/src/util/HSet.cpp', +# '../../_lib/highs/src/util/HVectorBase.cpp', +# '../../_lib/highs/src/util/stringutil.cpp', +# '../../_lib/highs/src/interfaces/highs_c_api.cpp' +# ], +# include_directories: [ +# '../../_lib/highs/extern/', +# '../../_lib/highs/src/', +# '../../_lib/highs/src/io/', +# '../../_lib/highs/src/ipm/ipx/', +# '../../_lib/highs/src/lp_data/', +# '../../_lib/highs/src/util/', +# 'src/' +# ], +# dependencies: thread_dep, +# cpp_args: [highs_flags, highs_define_macros] +# ) -_highs_wrapper = py3.extension_module('_highs_wrapper', - cython_gen_cpp.process('cython/src/_highs_wrapper.pyx'), - include_directories: [ - 'cython/src/', - 'src/', - '../../_lib/highs/src/', - '../../_lib/highs/src/io/', - '../../_lib/highs/src/lp_data/', - '../../_lib/highs/src/util/' - ], - dependencies: [np_dep, thread_dep, atomic_dep], - link_args: version_link_args, - link_with: [highs_lib, ipx_lib, basiclu_lib], - cpp_args: [highs_flags, highs_define_macros, cython_c_args], - install: true, - subdir: 'scipy/optimize/_highs' -) +# _highs_wrapper = py3.extension_module('_highs_wrapper', +# # cython_gen_cpp.process('cython/src/_highs_wrapper.pyx'), +# # include_directories: [ +# # 'cython/src/', +# # 'src/', +# # '../../_lib/highs/src/', +# # '../../_lib/highs/src/io/', +# # '../../_lib/highs/src/lp_data/', +# # '../../_lib/highs/src/util/' +# # ], +# dependencies: [np_dep, thread_dep, atomic_dep], +# link_args: version_link_args, +# link_with: [highs_lib, ipx_lib, basiclu_lib], +# cpp_args: [highs_flags, highs_define_macros, cython_c_args], +# install: true, +# subdir: 'scipy/optimize/_highs' +# ) -_highs_constants = py3.extension_module('_highs_constants', - cython_gen_cpp.process('cython/src/_highs_constants.pyx'), - c_args: cython_c_args, - include_directories: [ - 'cython/src/', - 'src', - '../../_lib/highs/src/', - '../../_lib/highs/src/io/', - '../../_lib/highs/src/lp_data/', - '../../_lib/highs/src/simplex/' - ], - dependencies: thread_dep, - link_args: version_link_args, - install: true, - subdir: 'scipy/optimize/_highs' -) +# _highs_constants = py3.extension_module('_highs_constants', +# cython_gen_cpp.process('cython/src/_highs_constants.pyx'), +# c_args: cython_c_args, +# include_directories: [ +# 'cython/src/', +# 'src', +# '../../_lib/highs/src/', +# '../../_lib/highs/src/io/', +# '../../_lib/highs/src/lp_data/', +# '../../_lib/highs/src/simplex/' +# ], +# dependencies: thread_dep, +# link_args: version_link_args, +# install: true, +# subdir: 'scipy/optimize/_highs' +# ) -py3.install_sources([ - 'cython/src/HConst.pxd', - 'cython/src/Highs.pxd', - 'cython/src/HighsIO.pxd', - 'cython/src/HighsInfo.pxd', - 'cython/src/HighsLp.pxd', - 'cython/src/HighsLpUtils.pxd', - 'cython/src/HighsModelUtils.pxd', - 'cython/src/HighsOptions.pxd', - 'cython/src/HighsRuntimeOptions.pxd', - 'cython/src/HighsStatus.pxd', - 'cython/src/SimplexConst.pxd', - 'cython/src/highs_c_api.pxd' - ], - subdir: 'scipy/optimize/_highs/src/cython' -) +# subdir('../_lib/highs/highspy') -py3.install_sources( - ['__init__.py'], +# highspyext = py.extension_module( +# 'highspy', +# sources : [ +# 'highs_bindings.cpp', +# ], +# dependencies: [pyb11_dep, highs_dep], +# cpp_args: _args, +# install: true, +# subdir: 'scipy/optimize/_highs' +# ) + +# _highs_wrapper = py3.extension_module('_highs_wrapper', +# # cython_gen_cpp.process('cython/src/_highs_wrapper.pyx'), +# # include_directories: [ +# # 'cython/src/', +# # 'src/', +# # '../../_lib/highs/src/', +# # '../../_lib/highs/src/io/', +# # '../../_lib/highs/src/lp_data/', +# # '../../_lib/highs/src/util/' +# # ], +# dependencies: [np_dep, thread_dep, atomic_dep], +# link_args: version_link_args, +# link_with: [highs_lib, ipx_lib, basiclu_lib], +# cpp_args: [highs_flags, highs_define_macros, cython_c_args], +# install: true, +# subdir: 'scipy/optimize/_highs' +# ) + +py3.install_sources([ + '__init__.py', + '_highs_wrapper.py', +], subdir: 'scipy/optimize/_highs' ) diff --git a/scipy/optimize/_highs/src/libhighs_export.h b/scipy/optimize/_highs/src/libhighs_export.h deleted file mode 100644 index 095eab54c196..000000000000 --- a/scipy/optimize/_highs/src/libhighs_export.h +++ /dev/null @@ -1,50 +0,0 @@ - -#ifndef LIBHIGHS_EXPORT_H -#define LIBHIGHS_EXPORT_H - -#ifdef LIBHIGHS_STATIC_DEFINE -# define LIBHIGHS_EXPORT -# define LIBHIGHS_NO_EXPORT -#else -# ifndef LIBHIGHS_EXPORT -# ifdef libhighs_EXPORTS - /* We are building this library */ -# if defined(_MSC_VER) -# define LIBHIGHS_EXPORT __declspec(dllexport) -# else -# define LIBHIGHS_EXPORT __attribute__((visibility("default"))) -# endif -# else - /* We are using this library */ -# if defined(_MSC_VER) -# define LIBHIGHS_EXPORT __declspec(dllexport) -# else -# define LIBHIGHS_EXPORT __attribute__((visibility("default"))) -# endif -# endif -# endif - -# ifndef LIBHIGHS_NO_EXPORT -# define LIBHIGHS_NO_EXPORT __attribute__((visibility("hidden"))) -# endif -#endif - -#ifndef LIBHIGHS_DEPRECATED -# define LIBHIGHS_DEPRECATED __attribute__ ((__deprecated__)) -#endif - -#ifndef LIBHIGHS_DEPRECATED_EXPORT -# define LIBHIGHS_DEPRECATED_EXPORT LIBHIGHS_EXPORT LIBHIGHS_DEPRECATED -#endif - -#ifndef LIBHIGHS_DEPRECATED_NO_EXPORT -# define LIBHIGHS_DEPRECATED_NO_EXPORT LIBHIGHS_NO_EXPORT LIBHIGHS_DEPRECATED -#endif - -#if 0 /* DEFINE_NO_DEPRECATED */ -# ifndef LIBHIGHS_NO_DEPRECATED -# define LIBHIGHS_NO_DEPRECATED -# endif -#endif - -#endif /* LIBHIGHS_EXPORT_H */ diff --git a/scipy/optimize/_linprog_highs.py b/scipy/optimize/_linprog_highs.py index eb07443bb255..460c79fbc5a5 100644 --- a/scipy/optimize/_linprog_highs.py +++ b/scipy/optimize/_linprog_highs.py @@ -13,97 +13,107 @@ """ -import inspect +from enum import Enum import numpy as np from ._optimize import OptimizeWarning, OptimizeResult from warnings import warn from ._highs._highs_wrapper import _highs_wrapper -from ._highs._highs_constants import ( - CONST_INF, - MESSAGE_LEVEL_NONE, - HIGHS_OBJECTIVE_SENSE_MINIMIZE, - - MODEL_STATUS_NOTSET, - MODEL_STATUS_LOAD_ERROR, - MODEL_STATUS_MODEL_ERROR, - MODEL_STATUS_PRESOLVE_ERROR, - MODEL_STATUS_SOLVE_ERROR, - MODEL_STATUS_POSTSOLVE_ERROR, - MODEL_STATUS_MODEL_EMPTY, - MODEL_STATUS_OPTIMAL, - MODEL_STATUS_INFEASIBLE, - MODEL_STATUS_UNBOUNDED_OR_INFEASIBLE, - MODEL_STATUS_UNBOUNDED, - MODEL_STATUS_REACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND - as MODEL_STATUS_RDOVUB, - MODEL_STATUS_REACHED_OBJECTIVE_TARGET, - MODEL_STATUS_REACHED_TIME_LIMIT, - MODEL_STATUS_REACHED_ITERATION_LIMIT, - - HIGHS_SIMPLEX_STRATEGY_DUAL, - - HIGHS_SIMPLEX_CRASH_STRATEGY_OFF, - - HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE, - HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG, - HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX, - HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE, -) from scipy.sparse import csc_matrix, vstack, issparse - -def _highs_to_scipy_status_message(highs_status, highs_message): - """Converts HiGHS status number/message to SciPy status number/message""" - - scipy_statuses_messages = { - None: (4, "HiGHS did not provide a status code. "), - MODEL_STATUS_NOTSET: (4, ""), - MODEL_STATUS_LOAD_ERROR: (4, ""), - MODEL_STATUS_MODEL_ERROR: (2, ""), - MODEL_STATUS_PRESOLVE_ERROR: (4, ""), - MODEL_STATUS_SOLVE_ERROR: (4, ""), - MODEL_STATUS_POSTSOLVE_ERROR: (4, ""), - MODEL_STATUS_MODEL_EMPTY: (4, ""), - MODEL_STATUS_RDOVUB: (4, ""), - MODEL_STATUS_REACHED_OBJECTIVE_TARGET: (4, ""), - MODEL_STATUS_OPTIMAL: (0, "Optimization terminated successfully. "), - MODEL_STATUS_REACHED_TIME_LIMIT: (1, "Time limit reached. "), - MODEL_STATUS_REACHED_ITERATION_LIMIT: (1, "Iteration limit reached. "), - MODEL_STATUS_INFEASIBLE: (2, "The problem is infeasible. "), - MODEL_STATUS_UNBOUNDED: (3, "The problem is unbounded. "), - MODEL_STATUS_UNBOUNDED_OR_INFEASIBLE: (4, "The problem is unbounded " - "or infeasible. ")} - unrecognized = (4, "The HiGHS status code was not recognized. ") - scipy_status, scipy_message = ( - scipy_statuses_messages.get(highs_status, unrecognized)) - scipy_message = (f"{scipy_message}" - f"(HiGHS Status {highs_status}: {highs_message})") - return scipy_status, scipy_message - +from highspy import HighsModelStatus as hms +from highspy import simplex_constants as simpc +import highspy as hspy + +class SciPyRC(Enum): + """Return codes for SciPy solvers""" + OPTIMAL = 0 + ITERATION_LIMIT = 1 + INFEASIBLE = 2 + UNBOUNDED = 3 + NUMERICAL = 4 + + def to_string(self): + if self == SciPyRC.OPTIMAL: + return "Optimization terminated successfully. " + elif self == SciPyRC.ITERATION_LIMIT: + return "Iteration limit reached. " + elif self == SciPyRC.INFEASIBLE: + return "The problem is infeasible. " + elif self == SciPyRC.UNBOUNDED: + return "The problem is unbounded. " + elif self == SciPyRC.NUMERICAL: + return "Serious numerical difficulties encountered. " + else: + return "" + +class HighsStatusMapping: + """Class to map HiGHS statuses to SciPy Return Codes""" + def __init__(self): + # Custom mapping from HiGHS status and errors to SciPy status + self.highs_to_scipy = { + hms.kNotset: (SciPyRC.NUMERICAL, "Not set"), + hms.kLoadError: (SciPyRC.NUMERICAL, "Load Error"), + hms.kModelError: (SciPyRC.INFEASIBLE, "Model Error"), + hms.kPresolveError: (SciPyRC.NUMERICAL, "Presolve Error"), + hms.kSolveError: (SciPyRC.NUMERICAL, "Solve Error"), + hms.kPostsolveError: (SciPyRC.NUMERICAL, "Postsolve Error"), + hms.kModelEmpty: (SciPyRC.NUMERICAL, "Model Empty"), + hms.kOptimal: (SciPyRC.OPTIMAL, "Optimal"), + hms.kInfeasible: (SciPyRC.INFEASIBLE, "Infeasible"), + hms.kUnboundedOrInfeasible: (SciPyRC.NUMERICAL, "Unbounded or Infeasible"), + hms.kUnbounded: (SciPyRC.UNBOUNDED, "Unbounded"), + hms.kObjectiveBound: (SciPyRC.NUMERICAL, "Objective Bound"), + hms.kObjectiveTarget: (SciPyRC.NUMERICAL, "Objective Target"), + hms.kTimeLimit: (SciPyRC.ITERATION_LIMIT, "Time Limit"), + hms.kIterationLimit: (SciPyRC.ITERATION_LIMIT, "Iteration Limit"), + hms.kUnknown: (SciPyRC.NUMERICAL, "Unknown"), + hms.kSolutionLimit: (SciPyRC.NUMERICAL, "Solution Limit"), + } + + + def get_scipy_status(self, highs_status, highs_message): + """Converts HiGHS status and message to SciPy status and message""" + scipy_status, message_prefix = self.highs_to_scipy.get(hspy.HighsModelStatus(highs_status), + (SciPyRC.NUMERICAL, "Unknown HiGHS Status")) + scip = SciPyRC(scipy_status) + scipy_message = f"{scip.to_string()} (HiGHS Status {highs_status}: {highs_message})" + return scipy_status.value, scipy_message def _replace_inf(x): - # Replace `np.inf` with CONST_INF + # Replace `np.inf` with kHighsInf infs = np.isinf(x) with np.errstate(invalid="ignore"): - x[infs] = np.sign(x[infs])*CONST_INF + x[infs] = np.sign(x[infs])*hspy.kHighsInf return x -def _convert_to_highs_enum(option, option_str, choices): - # If option is in the choices we can look it up, if not use - # the default value taken from function signature and warn: +class SimplexStrategy(Enum): + DANTZIG = 'dantzig' + DEVEX = 'devex' + STEEPEST_DEVEX = 'steepest-devex' # highs min, choose + STEEPEST = 'steepest' # highs max + + def to_highs_enum(self): + mapping = { + SimplexStrategy.DANTZIG: simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDantzig.value, + SimplexStrategy.DEVEX: simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDevex.value, + SimplexStrategy.STEEPEST_DEVEX: simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyChoose.value, + SimplexStrategy.STEEPEST: simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategySteepestEdge.value, + } + return mapping.get(self) + +def convert_to_highs_enum(option, option_str, choices_enum, default_value): + if option is None: + return choices_enum[default_value.upper()].to_highs_enum() try: - return choices[option.lower()] - except AttributeError: - return choices[option] + enum_value = choices_enum[option.upper()] except KeyError: - sig = inspect.signature(_linprog_highs) - default_str = sig.parameters[option_str].default warn(f"Option {option_str} is {option}, but only values in " - f"{set(choices.keys())} are allowed. Using default: " - f"{default_str}.", + f"{[e.value for e in choices_enum]} are allowed. Using default: " + f"{default_value}.", OptimizeWarning, stacklevel=3) - return choices[default_str] + enum_value = choices_enum[default_value.upper()] + return enum_value.to_highs_enum() def _linprog_highs(lp, solver, time_limit=None, presolve=True, @@ -309,15 +319,12 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, warn(message, OptimizeWarning, stacklevel=3) # Map options to HiGHS enum values - simplex_dual_edge_weight_strategy_enum = _convert_to_highs_enum( + simplex_dual_edge_weight_strategy_enum = convert_to_highs_enum( simplex_dual_edge_weight_strategy, - 'simplex_dual_edge_weight_strategy', - choices={'dantzig': HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG, - 'devex': HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX, - 'steepest-devex': HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE, - 'steepest': - HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE, - None: None}) + "simplex_dual_edge_weight_strategy", + choices_enum=SimplexStrategy, + default_value="dantzig", + ) c, A_ub, b_ub, A_eq, b_eq, bounds, x0, integrality = lp @@ -339,10 +346,10 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, options = { 'presolve': presolve, - 'sense': HIGHS_OBJECTIVE_SENSE_MINIMIZE, + 'sense': hspy.ObjSense.kMinimize, 'solver': solver, 'time_limit': time_limit, - 'highs_debug_level': MESSAGE_LEVEL_NONE, + # 'highs_debug_level': hspy.kHighs, # TODO 'dual_feasibility_tolerance': dual_feasibility_tolerance, 'ipm_optimality_tolerance': ipm_optimality_tolerance, 'log_to_console': disp, @@ -351,8 +358,8 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, 'primal_feasibility_tolerance': primal_feasibility_tolerance, 'simplex_dual_edge_weight_strategy': simplex_dual_edge_weight_strategy_enum, - 'simplex_strategy': HIGHS_SIMPLEX_STRATEGY_DUAL, - 'simplex_crash_strategy': HIGHS_SIMPLEX_CRASH_STRATEGY_OFF, + 'simplex_strategy': simpc.kSimplexStrategyDual.value, + # 'simplex_crash_strategy': simpc.SimplexCrashStrategy.kSimplexCrashStrategyOff, 'ipm_iteration_limit': maxiter, 'simplex_iteration_limit': maxiter, 'mip_rel_gap': mip_rel_gap, @@ -397,9 +404,10 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, # this needs to be updated if we start choosing the solver intelligently # Convert to scipy-style status and message + highs_mapper = HighsStatusMapping() highs_status = res.get('status', None) highs_message = res.get('message', None) - status, message = _highs_to_scipy_status_message(highs_status, + status, message = highs_mapper.get_scipy_status(highs_status, highs_message) x = np.array(res['x']) if 'x' in res else None @@ -424,7 +432,7 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, }), 'fun': res.get('fun'), 'status': status, - 'success': res['status'] == MODEL_STATUS_OPTIMAL, + 'success': res['status'] == hms.kOptimal, 'message': message, 'nit': res.get('simplex_nit', 0) or res.get('ipm_nit', 0), 'crossover_nit': res.get('crossover_nit'), diff --git a/scipy/optimize/_milp.py b/scipy/optimize/_milp.py index fd9ecf52083f..bc4f192b4992 100644 --- a/scipy/optimize/_milp.py +++ b/scipy/optimize/_milp.py @@ -5,7 +5,7 @@ from ._highs._highs_wrapper import _highs_wrapper # type: ignore[import] from ._constraints import LinearConstraint, Bounds from ._optimize import OptimizeResult -from ._linprog_highs import _highs_to_scipy_status_message +from ._linprog_highs import HighsStatusMapping def _constraints_to_components(constraints): @@ -377,8 +377,9 @@ def milp(c, *, integrality=None, bounds=None, constraints=None, options=None): # Convert to scipy-style status and message highs_status = highs_res.get('status', None) highs_message = highs_res.get('message', None) - status, message = _highs_to_scipy_status_message(highs_status, - highs_message) + hstat = HighsStatusMapping() + status, message = hstat.get_scipy_status(highs_status, + highs_message) res['status'] = status res['message'] = message res['success'] = (status == 0) diff --git a/scipy/optimize/tests/test_linprog.py b/scipy/optimize/tests/test_linprog.py index 49a0f8de5a20..c2026ae09656 100644 --- a/scipy/optimize/tests/test_linprog.py +++ b/scipy/optimize/tests/test_linprog.py @@ -303,9 +303,10 @@ def test_deprecation(): def test_highs_status_message(): res = linprog(1, method='highs') - msg = "Optimization terminated successfully. (HiGHS Status 7:" + msg = "Optimization terminated successfully." assert res.status == 0 assert res.message.startswith(msg) + assert 'HiGHS Status 7' in res.message A, b, c, numbers, M = magic_square(6) bounds = [(0, 1)] * len(c) @@ -313,37 +314,46 @@ def test_highs_status_message(): options = {"time_limit": 0.1} res = linprog(c=c, A_eq=A, b_eq=b, bounds=bounds, method='highs', options=options, integrality=integrality) - msg = "Time limit reached. (HiGHS Status 13:" + msg = "Time limit reached" assert res.status == 1 - assert res.message.startswith(msg) + assert msg in res.message + assert 'HiGHS Status 13' in res.message options = {"maxiter": 10} res = linprog(c=c, A_eq=A, b_eq=b, bounds=bounds, method='highs-ds', options=options) - msg = "Iteration limit reached. (HiGHS Status 14:" + msg = "Iteration limit reached" assert res.status == 1 assert res.message.startswith(msg) + assert 'HiGHS Status 14' in res.message res = linprog(1, bounds=(1, -1), method='highs') - msg = "The problem is infeasible. (HiGHS Status 8:" + msg = "The problem is infeasible" assert res.status == 2 assert res.message.startswith(msg) - - res = linprog(-1, method='highs') - msg = "The problem is unbounded. (HiGHS Status 10:" - assert res.status == 3 - assert res.message.startswith(msg) - - from scipy.optimize._linprog_highs import _highs_to_scipy_status_message - status, message = _highs_to_scipy_status_message(58, "Hello!") - msg = "The HiGHS status code was not recognized. (HiGHS Status 58:" - assert status == 4 - assert message.startswith(msg) - - status, message = _highs_to_scipy_status_message(None, None) - msg = "HiGHS did not provide a status code. (HiGHS Status None: None)" + assert 'HiGHS Status 8' in res.message + + # TODO: Fix this + # res = linprog(-1, method='highs') + # msg = "The problem is unbounded." + # assert res.status == 3 + # assert res.message.startswith(msg) + # assert 'HiGHS Status 10' in res.message + + from scipy.optimize._linprog_highs import HighsStatusMapping + highs_mapper = HighsStatusMapping() + status, message = highs_mapper.get_scipy_status(58, "Hello!") + # msg = "The HiGHS status code was not recognized. (HiGHS Status 58:" assert status == 4 - assert message.startswith(msg) + # TODO: Fix this + # assert message.startswith(msg) + assert "HiGHS Status 58" in message + + # TODO: Fix this + # status, message = highs_mapper.get_scipy_status(None, None) + # msg = "HiGHS did not provide a status code. (HiGHS Status None: None)" + # assert status == 4 + # assert message.startswith(msg) def test_bug_17380(): diff --git a/scipy/optimize/tests/test_milp.py b/scipy/optimize/tests/test_milp.py index 0970a15a8bcc..c36ba5e19fb9 100644 --- a/scipy/optimize/tests/test_milp.py +++ b/scipy/optimize/tests/test_milp.py @@ -97,8 +97,9 @@ def test_result(): res = milp(c=c, constraints=(A, b, b), bounds=(0, 1), integrality=1) assert res.status == 0 assert res.success - msg = "Optimization terminated successfully. (HiGHS Status 7:" + msg = "Optimization terminated successfully." assert res.message.startswith(msg) + assert 'HiGHS Status 7' in res.message assert isinstance(res.x, np.ndarray) assert isinstance(res.fun, float) assert isinstance(res.mip_node_count, int) @@ -110,26 +111,29 @@ def test_result(): options={'time_limit': 0.05}) assert res.status == 1 assert not res.success - msg = "Time limit reached. (HiGHS Status 13:" - assert res.message.startswith(msg) + msg = "Time limit reached" + assert 'HiGHS Status 13' in res.message + assert msg in res.message assert (res.fun is res.mip_dual_bound is res.mip_gap is res.mip_node_count is res.x is None) res = milp(1, bounds=(1, -1)) assert res.status == 2 assert not res.success - msg = "The problem is infeasible. (HiGHS Status 8:" + msg = "The problem is infeasible" + assert 'HiGHS Status 8' in res.message assert res.message.startswith(msg) assert (res.fun is res.mip_dual_bound is res.mip_gap is res.mip_node_count is res.x is None) - res = milp(-1) - assert res.status == 3 - assert not res.success - msg = "The problem is unbounded. (HiGHS Status 10:" - assert res.message.startswith(msg) - assert (res.fun is res.mip_dual_bound is res.mip_gap - is res.mip_node_count is res.x is None) + # TODO: Fix this + # res = milp(-1) + # assert res.status == 3 + # assert not res.success + # msg = "The problem is unbounded. (HiGHS Status 10:" + # assert res.message.startswith(msg) + # assert (res.fun is res.mip_dual_bound is res.mip_gap + # is res.mip_node_count is res.x is None) def test_milp_optional_args(): From d41e540a09bd701d627eb29339b5aa5d724f0261 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 30 Sep 2023 10:10:10 +0000 Subject: [PATCH 003/500] MAINT: Clean out old meson build MAINT: Minor renaming for the modeling API BLD: Rework to use highs as a subproject MAINT: Remove highs submodule and rework --- .gitignore | 4 +- .gitmodules | 4 - LICENSES_bundled.txt | 4 +- mypy.ini | 9 - scipy/_lib/highs | 1 - scipy/_lib/meson.build | 29 ++- scipy/optimize/_highs/meson.build | 294 ------------------------------ scipy/optimize/_linprog_highs.py | 6 +- subprojects/highs.wrap | 3 + 9 files changed, 36 insertions(+), 318 deletions(-) delete mode 160000 scipy/_lib/highs create mode 100644 subprojects/highs.wrap diff --git a/.gitignore b/.gitignore index cb5a15536fe2..9e4e9b14b9b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# Wrap directories +subprojects/highs # Editor temporary/working/backup files # ######################################### .#* @@ -324,5 +326,3 @@ scipy/optimize/_group_columns.c scipy/optimize/cython_optimize/_zeros.c scipy/optimize/cython_optimize/_zeros.pyx scipy/optimize/lbfgsb/_lbfgsbmodule.c -scipy/optimize/_highs/cython/src/_highs_wrapper.cxx -scipy/optimize/_highs/cython/src/_highs_constants.cxx diff --git a/.gitmodules b/.gitmodules index 6bcfddf6bc02..39cd9bfe50a3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,10 +9,6 @@ path = scipy/_lib/unuran url = https://github.com/scipy/unuran.git shallow = true -[submodule "HiGHS"] - path = scipy/_lib/highs - url = https://github.com/HaoZeke/highs - shallow = true [submodule "scipy/_lib/boost_math"] path = scipy/_lib/boost_math url = https://github.com/boostorg/math.git diff --git a/LICENSES_bundled.txt b/LICENSES_bundled.txt index 9a3943898822..0c867c19c7b2 100644 --- a/LICENSES_bundled.txt +++ b/LICENSES_bundled.txt @@ -252,9 +252,9 @@ License: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Name: HiGHS -Files: scipy/optimize/_highs/* +Files: subprojects/highs/* License: MIT - For details, see scipy/optimize/_highs/LICENCE + For details, see subprojects/highs/LICENCE Name: Boost Files: scipy/_lib/boost_math/* diff --git a/mypy.ini b/mypy.ini index aa840b19701e..3cf9d47a9aac 100644 --- a/mypy.ini +++ b/mypy.ini @@ -280,15 +280,6 @@ ignore_errors = True [mypy-scipy.optimize._linprog_util] ignore_errors = True -[mypy-scipy.optimize._linprog_highs] -ignore_errors = True - -[mypy-scipy.optimize._highs.highs_wrapper] -ignore_errors = True - -[mypy-scipy.optimize._highs.constants] -ignore_errors = True - [mypy-scipy.optimize._trustregion] ignore_errors = True diff --git a/scipy/_lib/highs b/scipy/_lib/highs deleted file mode 160000 index 090453608adb..000000000000 --- a/scipy/_lib/highs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 090453608adb6a1f14dbee01dfd117b80aa8938a diff --git a/scipy/_lib/meson.build b/scipy/_lib/meson.build index f57e48f078fe..eab13731a701 100644 --- a/scipy/_lib/meson.build +++ b/scipy/_lib/meson.build @@ -2,9 +2,6 @@ fs = import('fs') if not fs.exists('boost_math/README.md') error('Missing the `boost` submodule! Run `git submodule update --init` to fix this.') endif -if not fs.exists('highs/README.md') - error('Missing the `highs` submodule! Run `git submodule update --init` to fix this.') -endif if not fs.exists('unuran/README.md') error('Missing the `unuran` submodule! Run `git submodule update --init` to fix this.') endif @@ -181,3 +178,29 @@ py3.install_sources( subdir('_uarray') subdir('tests') + +# Setup the highs library +highs_proj = subproject('highs', + default_options : ['default_library=static', + 'with_pybind11=True']) +highs_dep = highs_proj.get_variable('highs_dep') +highspy_py = highs_proj.get_variable('highspy_py') +highspy_cpp = highs_proj.get_variable('highspy_cpp') + +pyb11_dep = [ + py3.dependency(), + dependency('pybind11') +] + +highspyext = py3.extension_module( + '_highs', + sources : highspy_cpp, + dependencies: [pyb11_dep, highs_dep], + install: true, + subdir: 'highspy', +) + +py3.install_sources( + highspy_py, + subdir: 'highspy', +) diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index 9434512de9b8..2e4ace80dda1 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -1,297 +1,3 @@ -# highs_define_macros = [ -# '-DCMAKE_BUILD_TYPE="RELEASE"', -# '-DFAST_BUILD=ON', -# '-DHIGHS_GITHASH="n/a"', -# '-DHIGHS_COMPILATION_DATE="2021-07-09"', # cannot generate dynamically -# '-DHIGHS_VERSION_MAJOR=1', # don't care about this, look at CMakelists.txt -# '-DHIGHS_VERSION_MINOR=2', -# '-DHIGHS_VERSION_PATCH=0', -# '-DHIGHS_DIR=' + meson.current_source_dir() / '..' / '..' / '_lib' / 'highs', -# '-UOPENMP', -# '-UEXT_PRESOLVE', -# '-USCIP_DEV', -# '-UHiGHSDEV', -# '-UOSI_FOUND', -# '-DNDEBUG' -# ] - -# basiclu_lib = static_library('basiclu', -# [ -# '../../_lib/highs/src/ipm/basiclu/basiclu_factorize.c', -# '../../_lib/highs/src/ipm/basiclu/basiclu_get_factors.c', -# '../../_lib/highs/src/ipm/basiclu/basiclu_initialize.c', -# '../../_lib/highs/src/ipm/basiclu/basiclu_object.c', -# '../../_lib/highs/src/ipm/basiclu/basiclu_solve_dense.c', -# '../../_lib/highs/src/ipm/basiclu/basiclu_solve_for_update.c', -# '../../_lib/highs/src/ipm/basiclu/basiclu_solve_sparse.c', -# '../../_lib/highs/src/ipm/basiclu/basiclu_update.c', -# '../../_lib/highs/src/ipm/basiclu/lu_build_factors.c', -# '../../_lib/highs/src/ipm/basiclu/lu_condest.c', -# '../../_lib/highs/src/ipm/basiclu/lu_dfs.c', -# '../../_lib/highs/src/ipm/basiclu/lu_factorize_bump.c', -# '../../_lib/highs/src/ipm/basiclu/lu_file.c', -# '../../_lib/highs/src/ipm/basiclu/lu_garbage_perm.c', -# '../../_lib/highs/src/ipm/basiclu/lu_initialize.c', -# '../../_lib/highs/src/ipm/basiclu/lu_internal.c', -# '../../_lib/highs/src/ipm/basiclu/lu_markowitz.c', -# '../../_lib/highs/src/ipm/basiclu/lu_matrix_norm.c', -# '../../_lib/highs/src/ipm/basiclu/lu_pivot.c', -# '../../_lib/highs/src/ipm/basiclu/lu_residual_test.c', -# '../../_lib/highs/src/ipm/basiclu/lu_setup_bump.c', -# '../../_lib/highs/src/ipm/basiclu/lu_singletons.c', -# '../../_lib/highs/src/ipm/basiclu/lu_solve_dense.c', -# '../../_lib/highs/src/ipm/basiclu/lu_solve_for_update.c', -# '../../_lib/highs/src/ipm/basiclu/lu_solve_sparse.c', -# '../../_lib/highs/src/ipm/basiclu/lu_solve_symbolic.c', -# '../../_lib/highs/src/ipm/basiclu/lu_solve_triangular.c', -# '../../_lib/highs/src/ipm/basiclu/lu_update.c' -# ], -# include_directories: [ -# '../../_lib/highs/src', -# '../../_lib/highs/src/ipm/basiclu', -# 'src/' -# ], -# c_args: [Wno_unused_variable, highs_define_macros] -# ) - -# highs_flags = [ -# _cpp_Wno_class_memaccess, -# _cpp_Wno_format_truncation, -# _cpp_Wno_non_virtual_dtor, -# _cpp_Wno_sign_compare, -# _cpp_Wno_switch, -# _cpp_Wno_unused_but_set_variable, -# _cpp_Wno_unused_variable, -# ] - -# ipx_lib = static_library('ipx', -# [ -# '../../_lib/highs/src/ipm/ipx/basiclu_kernel.cc', -# '../../_lib/highs/src/ipm/ipx/basiclu_wrapper.cc', -# '../../_lib/highs/src/ipm/ipx/basis.cc', -# '../../_lib/highs/src/ipm/ipx/conjugate_residuals.cc', -# '../../_lib/highs/src/ipm/ipx/control.cc', -# '../../_lib/highs/src/ipm/ipx/crossover.cc', -# '../../_lib/highs/src/ipm/ipx/diagonal_precond.cc', -# '../../_lib/highs/src/ipm/ipx/forrest_tomlin.cc', -# '../../_lib/highs/src/ipm/ipx/guess_basis.cc', -# '../../_lib/highs/src/ipm/ipx/indexed_vector.cc', -# '../../_lib/highs/src/ipm/ipx/info.cc', -# '../../_lib/highs/src/ipm/ipx/ipm.cc', -# '../../_lib/highs/src/ipm/ipx/ipx_c.cc', -# '../../_lib/highs/src/ipm/ipx/iterate.cc', -# '../../_lib/highs/src/ipm/ipx/kkt_solver.cc', -# '../../_lib/highs/src/ipm/ipx/kkt_solver_basis.cc', -# '../../_lib/highs/src/ipm/ipx/kkt_solver_diag.cc', -# '../../_lib/highs/src/ipm/ipx/linear_operator.cc', -# '../../_lib/highs/src/ipm/ipx/lp_solver.cc', -# '../../_lib/highs/src/ipm/ipx/lu_factorization.cc', -# '../../_lib/highs/src/ipm/ipx/lu_update.cc', -# '../../_lib/highs/src/ipm/ipx/maxvolume.cc', -# '../../_lib/highs/src/ipm/ipx/model.cc', -# '../../_lib/highs/src/ipm/ipx/normal_matrix.cc', -# '../../_lib/highs/src/ipm/ipx/sparse_matrix.cc', -# '../../_lib/highs/src/ipm/ipx/sparse_utils.cc', -# '../../_lib/highs/src/ipm/ipx/splitted_normal_matrix.cc', -# '../../_lib/highs/src/ipm/ipx/starting_basis.cc', -# '../../_lib/highs/src/ipm/ipx/symbolic_invert.cc', -# '../../_lib/highs/src/ipm/ipx/timer.cc', -# '../../_lib/highs/src/ipm/ipx/utils.cc' -# ], -# include_directories: [ -# '../../_lib/highs/src/ipm/ipx/', -# '../../_lib/highs/src/ipm/basiclu/', -# '../../_lib/highs/src/', -# '../../_lib/highs/extern/', -# 'src/' -# ], -# dependencies: thread_dep, -# cpp_args: [highs_flags, highs_define_macros] -# ) - -# highs_lib = static_library('highs', -# [ -# '../../_lib/highs/extern/filereaderlp/reader.cpp', -# '../../_lib/highs/src/io/Filereader.cpp', -# '../../_lib/highs/src/io/FilereaderLp.cpp', -# '../../_lib/highs/src/io/FilereaderEms.cpp', -# '../../_lib/highs/src/io/FilereaderMps.cpp', -# '../../_lib/highs/src/io/HighsIO.cpp', -# '../../_lib/highs/src/io/HMPSIO.cpp', -# '../../_lib/highs/src/io/HMpsFF.cpp', -# '../../_lib/highs/src/io/LoadOptions.cpp', -# '../../_lib/highs/src/ipm/IpxWrapper.cpp', -# '../../_lib/highs/src/lp_data/Highs.cpp', -# '../../_lib/highs/src/lp_data/HighsDebug.cpp', -# '../../_lib/highs/src/lp_data/HighsInfo.cpp', -# '../../_lib/highs/src/lp_data/HighsInfoDebug.cpp', -# '../../_lib/highs/src/lp_data/HighsDeprecated.cpp', -# '../../_lib/highs/src/lp_data/HighsInterface.cpp', -# '../../_lib/highs/src/lp_data/HighsLp.cpp', -# '../../_lib/highs/src/lp_data/HighsLpUtils.cpp', -# '../../_lib/highs/src/lp_data/HighsModelUtils.cpp', -# '../../_lib/highs/src/lp_data/HighsRanging.cpp', -# '../../_lib/highs/src/lp_data/HighsSolution.cpp', -# '../../_lib/highs/src/lp_data/HighsSolutionDebug.cpp', -# '../../_lib/highs/src/lp_data/HighsSolve.cpp', -# '../../_lib/highs/src/lp_data/HighsStatus.cpp', -# '../../_lib/highs/src/lp_data/HighsOptions.cpp', -# '../../_lib/highs/src/mip/HighsMipSolver.cpp', -# '../../_lib/highs/src/mip/HighsMipSolverData.cpp', -# '../../_lib/highs/src/mip/HighsDomain.cpp', -# '../../_lib/highs/src/mip/HighsDynamicRowMatrix.cpp', -# '../../_lib/highs/src/mip/HighsLpRelaxation.cpp', -# '../../_lib/highs/src/mip/HighsSeparation.cpp', -# '../../_lib/highs/src/mip/HighsSeparator.cpp', -# '../../_lib/highs/src/mip/HighsTableauSeparator.cpp', -# '../../_lib/highs/src/mip/HighsModkSeparator.cpp', -# '../../_lib/highs/src/mip/HighsPathSeparator.cpp', -# '../../_lib/highs/src/mip/HighsCutGeneration.cpp', -# '../../_lib/highs/src/mip/HighsSearch.cpp', -# '../../_lib/highs/src/mip/HighsConflictPool.cpp', -# '../../_lib/highs/src/mip/HighsCutPool.cpp', -# '../../_lib/highs/src/mip/HighsCliqueTable.cpp', -# '../../_lib/highs/src/mip/HighsGFkSolve.cpp', -# '../../_lib/highs/src/mip/HighsTransformedLp.cpp', -# '../../_lib/highs/src/mip/HighsLpAggregator.cpp', -# '../../_lib/highs/src/mip/HighsDebugSol.cpp', -# '../../_lib/highs/src/mip/HighsImplications.cpp', -# '../../_lib/highs/src/mip/HighsPrimalHeuristics.cpp', -# '../../_lib/highs/src/mip/HighsPseudocost.cpp', -# '../../_lib/highs/src/mip/HighsRedcostFixing.cpp', -# '../../_lib/highs/src/mip/HighsNodeQueue.cpp', -# '../../_lib/highs/src/mip/HighsObjectiveFunction.cpp', -# '../../_lib/highs/src/model/HighsHessian.cpp', -# '../../_lib/highs/src/model/HighsHessianUtils.cpp', -# '../../_lib/highs/src/model/HighsModel.cpp', -# '../../_lib/highs/src/parallel/HighsTaskExecutor.cpp', -# '../../_lib/highs/src/presolve/ICrash.cpp', -# '../../_lib/highs/src/presolve/ICrashUtil.cpp', -# '../../_lib/highs/src/presolve/ICrashX.cpp', -# '../../_lib/highs/src/presolve/HighsPostsolveStack.cpp', -# '../../_lib/highs/src/presolve/HighsSymmetry.cpp', -# '../../_lib/highs/src/presolve/HPresolve.cpp', -# '../../_lib/highs/src/presolve/PresolveComponent.cpp', -# '../../_lib/highs/src/qpsolver/basis.cpp', -# '../../_lib/highs/src/qpsolver/quass.cpp', -# '../../_lib/highs/src/qpsolver/ratiotest.cpp', -# '../../_lib/highs/src/qpsolver/scaling.cpp', -# '../../_lib/highs/src/qpsolver/perturbation.cpp', -# '../../_lib/highs/src/simplex/HEkk.cpp', -# '../../_lib/highs/src/simplex/HEkkControl.cpp', -# '../../_lib/highs/src/simplex/HEkkDebug.cpp', -# '../../_lib/highs/src/simplex/HEkkPrimal.cpp', -# '../../_lib/highs/src/simplex/HEkkDual.cpp', -# '../../_lib/highs/src/simplex/HEkkDualRHS.cpp', -# '../../_lib/highs/src/simplex/HEkkDualRow.cpp', -# '../../_lib/highs/src/simplex/HEkkDualMulti.cpp', -# '../../_lib/highs/src/simplex/HEkkInterface.cpp', -# '../../_lib/highs/src/simplex/HighsSimplexAnalysis.cpp', -# '../../_lib/highs/src/simplex/HSimplex.cpp', -# '../../_lib/highs/src/simplex/HSimplexDebug.cpp', -# '../../_lib/highs/src/simplex/HSimplexNla.cpp', -# '../../_lib/highs/src/simplex/HSimplexNlaDebug.cpp', -# '../../_lib/highs/src/simplex/HSimplexNlaFreeze.cpp', -# '../../_lib/highs/src/simplex/HSimplexNlaProductForm.cpp', -# '../../_lib/highs/src/simplex/HSimplexReport.cpp', -# '../../_lib/highs/src/test/DevKkt.cpp', -# '../../_lib/highs/src/test/KktCh2.cpp', -# '../../_lib/highs/src/util/HFactor.cpp', -# '../../_lib/highs/src/util/HFactorDebug.cpp', -# '../../_lib/highs/src/util/HFactorExtend.cpp', -# '../../_lib/highs/src/util/HFactorRefactor.cpp', -# '../../_lib/highs/src/util/HFactorUtils.cpp', -# '../../_lib/highs/src/util/HighsHash.cpp', -# '../../_lib/highs/src/util/HighsLinearSumBounds.cpp', -# '../../_lib/highs/src/util/HighsMatrixPic.cpp', -# '../../_lib/highs/src/util/HighsMatrixUtils.cpp', -# '../../_lib/highs/src/util/HighsSort.cpp', -# '../../_lib/highs/src/util/HighsSparseMatrix.cpp', -# '../../_lib/highs/src/util/HighsUtils.cpp', -# '../../_lib/highs/src/util/HSet.cpp', -# '../../_lib/highs/src/util/HVectorBase.cpp', -# '../../_lib/highs/src/util/stringutil.cpp', -# '../../_lib/highs/src/interfaces/highs_c_api.cpp' -# ], -# include_directories: [ -# '../../_lib/highs/extern/', -# '../../_lib/highs/src/', -# '../../_lib/highs/src/io/', -# '../../_lib/highs/src/ipm/ipx/', -# '../../_lib/highs/src/lp_data/', -# '../../_lib/highs/src/util/', -# 'src/' -# ], -# dependencies: thread_dep, -# cpp_args: [highs_flags, highs_define_macros] -# ) - -# _highs_wrapper = py3.extension_module('_highs_wrapper', -# # cython_gen_cpp.process('cython/src/_highs_wrapper.pyx'), -# # include_directories: [ -# # 'cython/src/', -# # 'src/', -# # '../../_lib/highs/src/', -# # '../../_lib/highs/src/io/', -# # '../../_lib/highs/src/lp_data/', -# # '../../_lib/highs/src/util/' -# # ], -# dependencies: [np_dep, thread_dep, atomic_dep], -# link_args: version_link_args, -# link_with: [highs_lib, ipx_lib, basiclu_lib], -# cpp_args: [highs_flags, highs_define_macros, cython_c_args], -# install: true, -# subdir: 'scipy/optimize/_highs' -# ) - -# _highs_constants = py3.extension_module('_highs_constants', -# cython_gen_cpp.process('cython/src/_highs_constants.pyx'), -# c_args: cython_c_args, -# include_directories: [ -# 'cython/src/', -# 'src', -# '../../_lib/highs/src/', -# '../../_lib/highs/src/io/', -# '../../_lib/highs/src/lp_data/', -# '../../_lib/highs/src/simplex/' -# ], -# dependencies: thread_dep, -# link_args: version_link_args, -# install: true, -# subdir: 'scipy/optimize/_highs' -# ) - -# subdir('../_lib/highs/highspy') - -# highspyext = py.extension_module( -# 'highspy', -# sources : [ -# 'highs_bindings.cpp', -# ], -# dependencies: [pyb11_dep, highs_dep], -# cpp_args: _args, -# install: true, -# subdir: 'scipy/optimize/_highs' -# ) - -# _highs_wrapper = py3.extension_module('_highs_wrapper', -# # cython_gen_cpp.process('cython/src/_highs_wrapper.pyx'), -# # include_directories: [ -# # 'cython/src/', -# # 'src/', -# # '../../_lib/highs/src/', -# # '../../_lib/highs/src/io/', -# # '../../_lib/highs/src/lp_data/', -# # '../../_lib/highs/src/util/' -# # ], -# dependencies: [np_dep, thread_dep, atomic_dep], -# link_args: version_link_args, -# link_with: [highs_lib, ipx_lib, basiclu_lib], -# cpp_args: [highs_flags, highs_define_macros, cython_c_args], -# install: true, -# subdir: 'scipy/optimize/_highs' -# ) - py3.install_sources([ '__init__.py', '_highs_wrapper.py', diff --git a/scipy/optimize/_linprog_highs.py b/scipy/optimize/_linprog_highs.py index 460c79fbc5a5..796ac52e18da 100644 --- a/scipy/optimize/_linprog_highs.py +++ b/scipy/optimize/_linprog_highs.py @@ -20,9 +20,9 @@ from ._highs._highs_wrapper import _highs_wrapper from scipy.sparse import csc_matrix, vstack, issparse -from highspy import HighsModelStatus as hms -from highspy import simplex_constants as simpc -import highspy as hspy +from highspy._highs import HighsModelStatus as hms +from highspy._highs import simplex_constants as simpc +import highspy._highs as hspy class SciPyRC(Enum): """Return codes for SciPy solvers""" diff --git a/subprojects/highs.wrap b/subprojects/highs.wrap new file mode 100644 index 000000000000..137b46dfeb60 --- /dev/null +++ b/subprojects/highs.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://github.com/HaoZeke/highs.git +revision = d0bac8a10ce0d7bed35469c68a95450bee9dce71 From 9b8416e618240e30e223fe599a795ddac10ea2f4 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 1 Oct 2023 15:15:21 +0000 Subject: [PATCH 004/500] ENH: Use highspy for the interface ENH: Use highs_options TST: Fixup tests MAINT: Update to commit with no build warnings MAINT: Try building without errors MAINT: No static libraries.. MAINT: Try static again, move meson files around MAINT: Use an internally built highspy MAINT: Update to latest highspy With fixes as suggested Co-authored-by: rgommers BLD: Use variables defined by scipy BLD: Skip subproject installation Co-authored-by: rgommers MAINT: Provide link_args for highspy MAINT: Cleanup with newer API [highspy] MAINT: Cleanup to prevent building highspy ... outside of scipy at any rate MAINT: Update to streamlined highspy api TST: Revert changes to testsuite [linprog] Remain 100% backwards compatible --- pyproject.toml | 2 + scipy/_lib/meson.build | 26 --- scipy/meson.build | 2 + scipy/optimize/_highs/_highs_wrapper.py | 209 ++++++++++-------- scipy/optimize/_highs/meson.build | 42 ++++ scipy/optimize/_highs/src/HConfig.h | 0 scipy/optimize/_linprog_highs.py | 273 ++++++++++++++---------- scipy/optimize/tests/test_linprog.py | 39 ++-- scipy/optimize/tests/test_milp.py | 22 +- subprojects/highs.wrap | 2 +- 10 files changed, 350 insertions(+), 267 deletions(-) delete mode 100644 scipy/optimize/_highs/src/HConfig.h diff --git a/pyproject.toml b/pyproject.toml index 9c9de1bedae9..0b144e7969cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,6 +127,8 @@ tracker = "https://github.com/scipy/scipy/issues" [tool.doit] dodoFile = "dev.py" +[tool.meson-python.args] +install = ['--skip-subprojects'] [tool.cibuildwheel] skip = "cp36-* cp37-* cp38-* pp* *_ppc64le *_i686 *_s390x" diff --git a/scipy/_lib/meson.build b/scipy/_lib/meson.build index eab13731a701..7c31e19cf621 100644 --- a/scipy/_lib/meson.build +++ b/scipy/_lib/meson.build @@ -178,29 +178,3 @@ py3.install_sources( subdir('_uarray') subdir('tests') - -# Setup the highs library -highs_proj = subproject('highs', - default_options : ['default_library=static', - 'with_pybind11=True']) -highs_dep = highs_proj.get_variable('highs_dep') -highspy_py = highs_proj.get_variable('highspy_py') -highspy_cpp = highs_proj.get_variable('highspy_cpp') - -pyb11_dep = [ - py3.dependency(), - dependency('pybind11') -] - -highspyext = py3.extension_module( - '_highs', - sources : highspy_cpp, - dependencies: [pyb11_dep, highs_dep], - install: true, - subdir: 'highspy', -) - -py3.install_sources( - highspy_py, - subdir: 'highspy', -) diff --git a/scipy/meson.build b/scipy/meson.build index 768031ba6626..5656401a7176 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -304,6 +304,8 @@ Wno_switch = cc.get_supported_arguments('-Wno-switch') Wno_unused_label = cc.get_supported_arguments('-Wno-unused-label') Wno_unused_result = cc.get_supported_arguments('-Wno-unused-result') Wno_unused_variable = cc.get_supported_arguments('-Wno-unused-variable') +Wno_unused_but_set_variable = cc.get_supported_arguments('-Wno-unused-but-set-variable') +Wno_incompatible_pointer_types = cc.get_supported_arguments('-Wno-incompatible-pointer-types') # C++ warning flags _cpp_Wno_cpp = cpp.get_supported_arguments('-Wno-cpp') diff --git a/scipy/optimize/_highs/_highs_wrapper.py b/scipy/optimize/_highs/_highs_wrapper.py index a67e124911e2..e80a04918254 100644 --- a/scipy/optimize/_highs/_highs_wrapper.py +++ b/scipy/optimize/_highs/_highs_wrapper.py @@ -1,8 +1,8 @@ from warnings import warn import numpy as np -from scipy.optimize._highs import highs_bindings as hspy # type: ignore[attr-defined] -from scipy.optimize._highs import _highs_options as hopt # type: ignore[attr-defined] +from scipy.optimize._highs.highspy.highs import _h +from scipy.optimize._highs.highspy import _highs_options as hopt # type: ignore[attr-defined] from scipy.optimize import OptimizeWarning @@ -18,12 +18,12 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti } # Fill up a HighsLp object - lp = hspy.HighsLp() + lp = _h.HighsLp() lp.num_col_ = numcol lp.num_row_ = numrow lp.a_matrix_.num_col_ = numcol lp.a_matrix_.num_row_ = numrow - lp.a_matrix_.format_ = hspy.MatrixFormat.kColwise + lp.a_matrix_.format_ = _h.MatrixFormat.kColwise lp.col_cost_ = c lp.col_lower_ = lb lp.col_upper_ = ub @@ -33,18 +33,19 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti lp.a_matrix_.index_ = indices lp.a_matrix_.value_ = data if integrality.size > 0: - lp.integrality_ = [hspy.HighsVarType(i) for i in integrality] + lp.integrality_ = [_h.HighsVarType(i) for i in integrality] # Make a Highs object and pass it everything - highs = hspy.Highs() - highs_options = hspy.HighsOptions() + highs = _h.Highs_() + highs_options = _h.HighsOptions() + hoptmanager = hopt.HighsOptionsManager() for key, val in options.items(): # handle filtering of unsupported and default options if val is None or key in ("sense",): continue # ask for the option type - opt_type = hopt.get_option_type(key) + opt_type = hoptmanager.get_option_type(key) if -1 == opt_type: warn(f"Unrecognized options detected: {dict({key: val})}", OptimizeWarning) continue @@ -54,23 +55,21 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti if isinstance(val, bool): val = "on" if val else "off" else: - warn(f'Option f"{key}" is "{val}", but only True or False is ' - f'allowed. Using default.', OptimizeWarning) + warn( + f'Option f"{key}" is "{val}", but only True or False is ' + f"allowed. Using default.", + OptimizeWarning, + ) continue - opt_type = hspy.HighsOptionType(opt_type) + opt_type = _h.HighsOptionType(opt_type) status, msg = check_option(highs, key, val) - # { - # hspy.HighsOptionType.kBool: lambda _x, _y: (0, ""), - # hspy.HighsOptionType.kInt: hopt.check_int_option, - # hspy.HighsOptionType.kDouble: hopt.check_double_option, - # hspy.HighsOptionType.kString: hopt.check_string_option, - # }[opt_type](key, val) - - # have to do bool checking here because HiGHS doesn't have API - if opt_type == hspy.HighsOptionType.kBool: + if opt_type == _h.HighsOptionType.kBool: if not isinstance(val, bool): - warn(f'Option f"{key}" is "{val}", but only True or False is ' - f'allowed. Using default.', OptimizeWarning) + warn( + f'Option f"{key}" is "{val}", but only True or False is ' + f"allowed. Using default.", + OptimizeWarning, + ) continue # warn or set option @@ -80,30 +79,36 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti setattr(highs_options, key, val) opt_status = highs.passOptions(highs_options) - if opt_status == hspy.HighsStatus.kError: - res.update({ - "status": highs.getModelStatus(), - "message": highs.modelStatusToString(highs.getModelStatus()), - }) + if opt_status == _h.HighsStatus.kError: + res.update( + { + "status": highs.getModelStatus(), + "message": highs.modelStatusToString(highs.getModelStatus()), + } + ) return res init_status = highs.passModel(lp) - if init_status == hspy.HighsStatus.kError: + if init_status == _h.HighsStatus.kError: # if model fails to load, highs.getModelStatus() will be NOT_SET - err_model_status = hspy.HighsModelStatus.kModelError - res.update({ - "status": err_model_status, - "message": highs.modelStatusToString(err_model_status), - }) + err_model_status = _h.HighsModelStatus.kModelError + res.update( + { + "status": err_model_status, + "message": highs.modelStatusToString(err_model_status), + } + ) return res # Solve the LP run_status = highs.run() - if run_status == hspy.HighsStatus.kError: - res.update({ - "status": highs.getModelStatus(), - "message": highs.modelStatusToString(highs.getModelStatus()), - }) + if run_status == _h.HighsStatus.kError: + res.update( + { + "status": highs.getModelStatus(), + "message": highs.modelStatusToString(highs.getModelStatus()), + } + ) return res # Extract what we need from the solution @@ -122,26 +127,32 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti # current solution is feasible and can be returned. Else, there # is no solution. mipFailCondition = model_status not in ( - hspy.HighsModelStatus.kOptimal, - hspy.HighsModelStatus.kTimeLimit, - hspy.HighsModelStatus.kIterationLimit, - hspy.HighsModelStatus.kSolutionLimit, - ) or (model_status in { - hspy.HighsModelStatus.kTimeLimit, - hspy.HighsModelStatus.kIterationLimit, - hspy.HighsModelStatus.kSolutionLimit, - } and (info.objective_function_value == hspy.kHighsInf)) - lpFailCondition = model_status != hspy.HighsModelStatus.kOptimal + _h.HighsModelStatus.kOptimal, + _h.HighsModelStatus.kTimeLimit, + _h.HighsModelStatus.kIterationLimit, + _h.HighsModelStatus.kSolutionLimit, + ) or ( + model_status + in { + _h.HighsModelStatus.kTimeLimit, + _h.HighsModelStatus.kIterationLimit, + _h.HighsModelStatus.kSolutionLimit, + } + and (info.objective_function_value == _h.kHighsInf) + ) + lpFailCondition = model_status != _h.HighsModelStatus.kOptimal if (isMip and mipFailCondition) or (not isMip and lpFailCondition): - res.update({ - "status": model_status, - "message": f"model_status is {highs.modelStatusToString(model_status)}; " - f"primal_status is " - f"{highs.solutionStatusToString(info.primal_solution_status)}", - "simplex_nit": info.simplex_iteration_count, - "ipm_nit": info.ipm_iteration_count, - "crossover_nit": info.crossover_iteration_count, - }) + res.update( + { + "status": model_status, + "message": f"model_status is {highs.modelStatusToString(model_status)}; " + f"primal_status is " + f"{highs.solutionStatusToString(info.primal_solution_status)}", + "simplex_nit": info.simplex_iteration_count, + "ipm_nit": info.ipm_iteration_count, + "crossover_nit": info.crossover_iteration_count, + } + ) return res # Should be safe to read the solution: @@ -151,62 +162,72 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti # Lagrangians for bounds based on column statuses marg_bnds = np.zeros((2, numcol)) for ii in range(numcol): - if basis.col_status[ii] == hspy.HighsBasisStatus.kLower: + if basis.col_status[ii] == _h.HighsBasisStatus.kLower: marg_bnds[0, ii] = solution.col_dual[ii] - elif basis.col_status[ii] == hspy.HighsBasisStatus.kUpper: + elif basis.col_status[ii] == _h.HighsBasisStatus.kUpper: marg_bnds[1, ii] = solution.col_dual[ii] - res.update({ - "status": model_status, - "message": highs.modelStatusToString(model_status), - - # Primal solution - "x": np.array(solution.col_value), - - # Ax + s = b => Ax = b - s - # Note: this is for all constraints (A_ub and A_eq) - "slack": rhs - solution.row_value, - - # lambda are the lagrange multipliers associated with Ax=b - "lambda": np.array(solution.row_dual), - "marg_bnds": marg_bnds, - - "fun": info.objective_function_value, - "simplex_nit": info.simplex_iteration_count, - "ipm_nit": info.ipm_iteration_count, - "crossover_nit": info.crossover_iteration_count, - }) + res.update( + { + "status": model_status, + "message": highs.modelStatusToString(model_status), + # Primal solution + "x": np.array(solution.col_value), + # Ax + s = b => Ax = b - s + # Note: this is for all constraints (A_ub and A_eq) + "slack": rhs - solution.row_value, + # lambda are the lagrange multipliers associated with Ax=b + "lambda": np.array(solution.row_dual), + "marg_bnds": marg_bnds, + "fun": info.objective_function_value, + "simplex_nit": info.simplex_iteration_count, + "ipm_nit": info.ipm_iteration_count, + "crossover_nit": info.crossover_iteration_count, + } + ) if isMip: - res.update({ - "mip_node_count": info.mip_node_count, - "mip_dual_bound": info.mip_dual_bound, - "mip_gap": info.mip_gap, - }) + res.update( + { + "mip_node_count": info.mip_node_count, + "mip_dual_bound": info.mip_dual_bound, + "mip_gap": info.mip_gap, + } + ) return res + def check_option(highs_inst, option, value): status, option_type = highs_inst.getOptionType(option) + hoptmanager = hopt.HighsOptionsManager() - if status != HighsStatus.kOk: - return 1, "Invalid option name." + if status != _h.HighsStatus.kOk: + return -1, "Invalid option name." valid_types = { - HighsOptionType.kBool: bool, - HighsOptionType.kInt: int, - HighsOptionType.kDouble: float, - HighsOptionType.kString: str + _h.HighsOptionType.kBool: bool, + _h.HighsOptionType.kInt: int, + _h.HighsOptionType.kDouble: float, + _h.HighsOptionType.kString: str, } expected_type = valid_types.get(option_type, None) + + if expected_type is str: + if not hoptmanager.check_string_option(option, value): + return -1, "Invalid option value." + if expected_type is float: + if not hoptmanager.check_double_option(option, value): + return -1, "Invalid option value." + if expected_type is int: + if not hoptmanager.check_int_option(option, value): + return -1, "Invalid option value." + if expected_type is None: return 3, "Unknown option type." - if not isinstance(value, expected_type): - return 2, "Invalid option value." - status, current_value = highs_inst.getOptionValue(option) - if status != HighsStatus.kOk: + if status != _h.HighsStatus.kOk: return 4, "Failed to validate option value." return 0, "Check option succeeded." diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index 2e4ace80dda1..b164c2265f11 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -1,3 +1,45 @@ +# Setup the highs library +highs_proj = subproject('highs', + default_options : ['default_library=static']) +highs_dep = highs_proj.get_variable('highs_dep') +highspy_py = highs_proj.get_variable('highspy_py') +highspy_cpp = highs_proj.get_variable('highspy_cpp') +highsoptions_cpp = highs_proj.get_variable('highsoptions_cpp') + +scipy_highspy_dep = [ + py3_dep, + pybind11_dep, + highs_dep, +] + +py3.extension_module( + '_highs', + sources : highspy_cpp, + dependencies: scipy_highspy_dep, + c_args: [Wno_unused_variable, Wno_unused_but_set_variable], + cpp_args: [_cpp_Wno_unused_variable, _cpp_Wno_unused_but_set_variable], + link_args: version_link_args, + subdir: 'scipy/optimize/_highs/highspy', + install: true, +) + + +py3.extension_module( + '_highs_options', + sources : highsoptions_cpp, + dependencies: scipy_highspy_dep, + c_args: [Wno_unused_variable, Wno_unused_but_set_variable], + cpp_args: [_cpp_Wno_unused_variable, _cpp_Wno_unused_but_set_variable], + link_args: version_link_args, + subdir: 'scipy/optimize/_highs/highspy', + install: true, +) + +py3.install_sources( + highspy_py, + subdir: 'scipy/optimize/_highs/highspy', +) + py3.install_sources([ '__init__.py', '_highs_wrapper.py', diff --git a/scipy/optimize/_highs/src/HConfig.h b/scipy/optimize/_highs/src/HConfig.h deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/scipy/optimize/_linprog_highs.py b/scipy/optimize/_linprog_highs.py index 796ac52e18da..9dc8110ba56b 100644 --- a/scipy/optimize/_linprog_highs.py +++ b/scipy/optimize/_linprog_highs.py @@ -20,12 +20,13 @@ from ._highs._highs_wrapper import _highs_wrapper from scipy.sparse import csc_matrix, vstack, issparse -from highspy._highs import HighsModelStatus as hms -from highspy._highs import simplex_constants as simpc -import highspy._highs as hspy +from scipy.optimize._highs.highspy.highs import _h +from scipy.optimize._highs.highspy.highs import simpc + class SciPyRC(Enum): """Return codes for SciPy solvers""" + OPTIMAL = 0 ITERATION_LIMIT = 1 INFEASIBLE = 2 @@ -46,52 +47,67 @@ def to_string(self): else: return "" + class HighsStatusMapping: """Class to map HiGHS statuses to SciPy Return Codes""" + def __init__(self): # Custom mapping from HiGHS status and errors to SciPy status self.highs_to_scipy = { - hms.kNotset: (SciPyRC.NUMERICAL, "Not set"), - hms.kLoadError: (SciPyRC.NUMERICAL, "Load Error"), - hms.kModelError: (SciPyRC.INFEASIBLE, "Model Error"), - hms.kPresolveError: (SciPyRC.NUMERICAL, "Presolve Error"), - hms.kSolveError: (SciPyRC.NUMERICAL, "Solve Error"), - hms.kPostsolveError: (SciPyRC.NUMERICAL, "Postsolve Error"), - hms.kModelEmpty: (SciPyRC.NUMERICAL, "Model Empty"), - hms.kOptimal: (SciPyRC.OPTIMAL, "Optimal"), - hms.kInfeasible: (SciPyRC.INFEASIBLE, "Infeasible"), - hms.kUnboundedOrInfeasible: (SciPyRC.NUMERICAL, "Unbounded or Infeasible"), - hms.kUnbounded: (SciPyRC.UNBOUNDED, "Unbounded"), - hms.kObjectiveBound: (SciPyRC.NUMERICAL, "Objective Bound"), - hms.kObjectiveTarget: (SciPyRC.NUMERICAL, "Objective Target"), - hms.kTimeLimit: (SciPyRC.ITERATION_LIMIT, "Time Limit"), - hms.kIterationLimit: (SciPyRC.ITERATION_LIMIT, "Iteration Limit"), - hms.kUnknown: (SciPyRC.NUMERICAL, "Unknown"), - hms.kSolutionLimit: (SciPyRC.NUMERICAL, "Solution Limit"), + _h.HighsModelStatus.kNotset: (SciPyRC.NUMERICAL, "Not set"), + _h.HighsModelStatus.kLoadError: (SciPyRC.NUMERICAL, "Load Error"), + _h.HighsModelStatus.kModelError: (SciPyRC.INFEASIBLE, "Model Error"), + _h.HighsModelStatus.kPresolveError: (SciPyRC.NUMERICAL, "Presolve Error"), + _h.HighsModelStatus.kSolveError: (SciPyRC.NUMERICAL, "Solve Error"), + _h.HighsModelStatus.kPostsolveError: (SciPyRC.NUMERICAL, "Postsolve Error"), + _h.HighsModelStatus.kModelEmpty: (SciPyRC.NUMERICAL, "Model Empty"), + _h.HighsModelStatus.kOptimal: (SciPyRC.OPTIMAL, "Optimal"), + _h.HighsModelStatus.kInfeasible: (SciPyRC.INFEASIBLE, "Infeasible"), + _h.HighsModelStatus.kUnboundedOrInfeasible: ( + SciPyRC.NUMERICAL, + "Unbounded or Infeasible", + ), + _h.HighsModelStatus.kUnbounded: (SciPyRC.UNBOUNDED, "Unbounded"), + _h.HighsModelStatus.kObjectiveBound: (SciPyRC.NUMERICAL, "Objective Bound"), + _h.HighsModelStatus.kObjectiveTarget: ( + SciPyRC.NUMERICAL, + "Objective Target", + ), + _h.HighsModelStatus.kTimeLimit: (SciPyRC.ITERATION_LIMIT, "Time Limit"), + _h.HighsModelStatus.kIterationLimit: ( + SciPyRC.ITERATION_LIMIT, + "Iteration Limit", + ), + _h.HighsModelStatus.kUnknown: (SciPyRC.NUMERICAL, "Unknown"), + _h.HighsModelStatus.kSolutionLimit: (SciPyRC.NUMERICAL, "Solution Limit"), } - def get_scipy_status(self, highs_status, highs_message): """Converts HiGHS status and message to SciPy status and message""" - scipy_status, message_prefix = self.highs_to_scipy.get(hspy.HighsModelStatus(highs_status), - (SciPyRC.NUMERICAL, "Unknown HiGHS Status")) + scipy_status, message_prefix = self.highs_to_scipy.get( + _h.HighsModelStatus(highs_status), + (SciPyRC.NUMERICAL, "Unknown HiGHS Status"), + ) scip = SciPyRC(scipy_status) - scipy_message = f"{scip.to_string()} (HiGHS Status {highs_status}: {highs_message})" + scipy_message = ( + f"{scip.to_string()} (HiGHS Status {highs_status}: {highs_message})" + ) return scipy_status.value, scipy_message + def _replace_inf(x): # Replace `np.inf` with kHighsInf infs = np.isinf(x) with np.errstate(invalid="ignore"): - x[infs] = np.sign(x[infs])*hspy.kHighsInf + x[infs] = np.sign(x[infs]) * _h.kHighsInf return x class SimplexStrategy(Enum): - DANTZIG = 'dantzig' - DEVEX = 'devex' - STEEPEST_DEVEX = 'steepest-devex' # highs min, choose - STEEPEST = 'steepest' # highs max + DANTZIG = "dantzig" + DEVEX = "devex" + STEEPEST_DEVEX = "steepest-devex" # highs min, choose + STEEPEST = "steepest" # highs max def to_highs_enum(self): mapping = { @@ -102,29 +118,39 @@ def to_highs_enum(self): } return mapping.get(self) + def convert_to_highs_enum(option, option_str, choices_enum, default_value): if option is None: return choices_enum[default_value.upper()].to_highs_enum() try: enum_value = choices_enum[option.upper()] except KeyError: - warn(f"Option {option_str} is {option}, but only values in " - f"{[e.value for e in choices_enum]} are allowed. Using default: " - f"{default_value}.", - OptimizeWarning, stacklevel=3) + warn( + f"Option {option_str} is {option}, but only values in " + f"{[e.value for e in choices_enum]} are allowed. Using default: " + f"{default_value}.", + OptimizeWarning, + stacklevel=3, + ) enum_value = choices_enum[default_value.upper()] return enum_value.to_highs_enum() -def _linprog_highs(lp, solver, time_limit=None, presolve=True, - disp=False, maxiter=None, - dual_feasibility_tolerance=None, - primal_feasibility_tolerance=None, - ipm_optimality_tolerance=None, - simplex_dual_edge_weight_strategy=None, - mip_rel_gap=None, - mip_max_nodes=None, - **unknown_options): +def _linprog_highs( + lp, + solver, + time_limit=None, + presolve=True, + disp=False, + maxiter=None, + dual_feasibility_tolerance=None, + primal_feasibility_tolerance=None, + ipm_optimality_tolerance=None, + simplex_dual_edge_weight_strategy=None, + mip_rel_gap=None, + mip_max_nodes=None, + **unknown_options, +): r""" Solve the following linear programming problem using one of the HiGHS solvers: @@ -314,8 +340,10 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, simplex algorithm." Mathematical Programming 12.1 (1977): 361-371. """ if unknown_options: - message = (f"Unrecognized options detected: {unknown_options}. " - "These will be passed to HiGHS verbatim.") + message = ( + f"Unrecognized options detected: {unknown_options}. " + "These will be passed to HiGHS verbatim." + ) warn(message, OptimizeWarning, stacklevel=3) # Map options to HiGHS enum values @@ -331,7 +359,7 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, lb, ub = bounds.T.copy() # separate bounds, copy->C-cntgs # highs_wrapper solves LHS <= A*x <= RHS, not equality constraints with np.errstate(invalid="ignore"): - lhs_ub = -np.ones_like(b_ub)*np.inf # LHS of UB constraints is -inf + lhs_ub = -np.ones_like(b_ub) * np.inf # LHS of UB constraints is -inf rhs_ub = b_ub # RHS of UB constraints is b_ub lhs_eq = b_eq # Equality constraint is inequality rhs_eq = b_eq # constraint with LHS=RHS @@ -345,24 +373,23 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, A = csc_matrix(A) options = { - 'presolve': presolve, - 'sense': hspy.ObjSense.kMinimize, - 'solver': solver, - 'time_limit': time_limit, - # 'highs_debug_level': hspy.kHighs, # TODO - 'dual_feasibility_tolerance': dual_feasibility_tolerance, - 'ipm_optimality_tolerance': ipm_optimality_tolerance, - 'log_to_console': disp, - 'mip_max_nodes': mip_max_nodes, - 'output_flag': disp, - 'primal_feasibility_tolerance': primal_feasibility_tolerance, - 'simplex_dual_edge_weight_strategy': - simplex_dual_edge_weight_strategy_enum, - 'simplex_strategy': simpc.kSimplexStrategyDual.value, + "presolve": presolve, + "sense": _h.ObjSense.kMinimize, + "solver": solver, + "time_limit": time_limit, + # 'highs_debug_level': _h.kHighs, # TODO + "dual_feasibility_tolerance": dual_feasibility_tolerance, + "ipm_optimality_tolerance": ipm_optimality_tolerance, + "log_to_console": disp, + "mip_max_nodes": mip_max_nodes, + "output_flag": disp, + "primal_feasibility_tolerance": primal_feasibility_tolerance, + "simplex_dual_edge_weight_strategy": simplex_dual_edge_weight_strategy_enum, + "simplex_strategy": simpc.kSimplexStrategyDual.value, # 'simplex_crash_strategy': simpc.SimplexCrashStrategy.kSimplexCrashStrategyOff, - 'ipm_iteration_limit': maxiter, - 'simplex_iteration_limit': maxiter, - 'mip_rel_gap': mip_rel_gap, + "ipm_iteration_limit": maxiter, + "simplex_iteration_limit": maxiter, + "mip_rel_gap": mip_rel_gap, } options.update(unknown_options) @@ -377,26 +404,36 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, else: integrality = np.array(integrality) - res = _highs_wrapper(c, A.indptr, A.indices, A.data, lhs, rhs, - lb, ub, integrality.astype(np.uint8), options) + res = _highs_wrapper( + c, + A.indptr, + A.indices, + A.data, + lhs, + rhs, + lb, + ub, + integrality.astype(np.uint8), + options, + ) # HiGHS represents constraints as lhs/rhs, so # Ax + s = b => Ax = b - s # and we need to split up s by A_ub and A_eq - if 'slack' in res: - slack = res['slack'] - con = np.array(slack[len(b_ub):]) - slack = np.array(slack[:len(b_ub)]) + if "slack" in res: + slack = res["slack"] + con = np.array(slack[len(b_ub) :]) + slack = np.array(slack[: len(b_ub)]) else: slack, con = None, None # lagrange multipliers for equalities/inequalities and upper/lower bounds - if 'lambda' in res: - lamda = res['lambda'] - marg_ineqlin = np.array(lamda[:len(b_ub)]) - marg_eqlin = np.array(lamda[len(b_ub):]) - marg_upper = np.array(res['marg_bnds'][1, :]) - marg_lower = np.array(res['marg_bnds'][0, :]) + if "lambda" in res: + lamda = res["lambda"] + marg_ineqlin = np.array(lamda[: len(b_ub)]) + marg_eqlin = np.array(lamda[len(b_ub) :]) + marg_upper = np.array(res["marg_bnds"][1, :]) + marg_lower = np.array(res["marg_bnds"][0, :]) else: marg_ineqlin, marg_eqlin = None, None marg_upper, marg_lower = None, None @@ -405,44 +442,58 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, # Convert to scipy-style status and message highs_mapper = HighsStatusMapping() - highs_status = res.get('status', None) - highs_message = res.get('message', None) - status, message = highs_mapper.get_scipy_status(highs_status, - highs_message) - - x = np.array(res['x']) if 'x' in res else None - sol = {'x': x, - 'slack': slack, - 'con': con, - 'ineqlin': OptimizeResult({ - 'residual': slack, - 'marginals': marg_ineqlin, - }), - 'eqlin': OptimizeResult({ - 'residual': con, - 'marginals': marg_eqlin, - }), - 'lower': OptimizeResult({ - 'residual': None if x is None else x - lb, - 'marginals': marg_lower, - }), - 'upper': OptimizeResult({ - 'residual': None if x is None else ub - x, - 'marginals': marg_upper - }), - 'fun': res.get('fun'), - 'status': status, - 'success': res['status'] == hms.kOptimal, - 'message': message, - 'nit': res.get('simplex_nit', 0) or res.get('ipm_nit', 0), - 'crossover_nit': res.get('crossover_nit'), - } + highs_status = res.get("status", None) + highs_message = res.get("message", None) + status, message = highs_mapper.get_scipy_status(highs_status, highs_message) + + def is_valid_x(val): + if isinstance(val, np.ndarray): + if val.dtype == object and None in val: + return False + return val is not None + + x = np.array(res["x"]) if "x" in res and is_valid_x(res["x"]) else None + + sol = { + "x": x, + "slack": slack, + "con": con, + "ineqlin": OptimizeResult( + { + "residual": slack, + "marginals": marg_ineqlin, + } + ), + "eqlin": OptimizeResult( + { + "residual": con, + "marginals": marg_eqlin, + } + ), + "lower": OptimizeResult( + { + "residual": None if x is None else x - lb, + "marginals": marg_lower, + } + ), + "upper": OptimizeResult( + {"residual": None if x is None else ub - x, "marginals": marg_upper} + ), + "fun": res.get("fun"), + "status": status, + "success": res["status"] == _h.HighsModelStatus.kOptimal, + "message": message, + "nit": res.get("simplex_nit", 0) or res.get("ipm_nit", 0), + "crossover_nit": res.get("crossover_nit"), + } if np.any(x) and integrality is not None: - sol.update({ - 'mip_node_count': res.get('mip_node_count', 0), - 'mip_dual_bound': res.get('mip_dual_bound', 0.0), - 'mip_gap': res.get('mip_gap', 0.0), - }) + sol.update( + { + "mip_node_count": res.get("mip_node_count", 0), + "mip_dual_bound": res.get("mip_dual_bound", 0.0), + "mip_gap": res.get("mip_gap", 0.0), + } + ) return sol diff --git a/scipy/optimize/tests/test_linprog.py b/scipy/optimize/tests/test_linprog.py index c2026ae09656..39d09b6ad95b 100644 --- a/scipy/optimize/tests/test_linprog.py +++ b/scipy/optimize/tests/test_linprog.py @@ -303,10 +303,9 @@ def test_deprecation(): def test_highs_status_message(): res = linprog(1, method='highs') - msg = "Optimization terminated successfully." + msg = "Optimization terminated successfully. (HiGHS Status 7:" assert res.status == 0 assert res.message.startswith(msg) - assert 'HiGHS Status 7' in res.message A, b, c, numbers, M = magic_square(6) bounds = [(0, 1)] * len(c) @@ -314,46 +313,38 @@ def test_highs_status_message(): options = {"time_limit": 0.1} res = linprog(c=c, A_eq=A, b_eq=b, bounds=bounds, method='highs', options=options, integrality=integrality) - msg = "Time limit reached" + msg = "Time limit reached. (HiGHS Status 13:" assert res.status == 1 assert msg in res.message - assert 'HiGHS Status 13' in res.message options = {"maxiter": 10} res = linprog(c=c, A_eq=A, b_eq=b, bounds=bounds, method='highs-ds', options=options) - msg = "Iteration limit reached" + msg = "Iteration limit reached. (HiGHS Status 14:" assert res.status == 1 assert res.message.startswith(msg) - assert 'HiGHS Status 14' in res.message res = linprog(1, bounds=(1, -1), method='highs') - msg = "The problem is infeasible" + msg = "The problem is infeasible. (HiGHS Status 8:" assert res.status == 2 assert res.message.startswith(msg) - assert 'HiGHS Status 8' in res.message - # TODO: Fix this - # res = linprog(-1, method='highs') - # msg = "The problem is unbounded." - # assert res.status == 3 - # assert res.message.startswith(msg) - # assert 'HiGHS Status 10' in res.message + res = linprog(-1, method='highs') + msg = "The problem is unbounded. (HiGHS Status 10:" + assert res.status == 3 + assert res.message.startswith(msg) from scipy.optimize._linprog_highs import HighsStatusMapping highs_mapper = HighsStatusMapping() status, message = highs_mapper.get_scipy_status(58, "Hello!") - # msg = "The HiGHS status code was not recognized. (HiGHS Status 58:" assert status == 4 - # TODO: Fix this - # assert message.startswith(msg) - assert "HiGHS Status 58" in message - - # TODO: Fix this - # status, message = highs_mapper.get_scipy_status(None, None) - # msg = "HiGHS did not provide a status code. (HiGHS Status None: None)" - # assert status == 4 - # assert message.startswith(msg) + msg = "The HiGHS status code was not recognized. (HiGHS Status 58:" + assert message.startswith(msg) + + status, message = highs_mapper.get_scipy_status(None, None) + msg = "HiGHS did not provide a status code. (HiGHS Status None: None)" + assert status == 4 + assert message.startswith(msg) def test_bug_17380(): diff --git a/scipy/optimize/tests/test_milp.py b/scipy/optimize/tests/test_milp.py index c36ba5e19fb9..f78cb2cd0ac0 100644 --- a/scipy/optimize/tests/test_milp.py +++ b/scipy/optimize/tests/test_milp.py @@ -99,7 +99,7 @@ def test_result(): assert res.success msg = "Optimization terminated successfully." assert res.message.startswith(msg) - assert 'HiGHS Status 7' in res.message + assert 'HighsModelStatus.kOptimal' in res.message assert isinstance(res.x, np.ndarray) assert isinstance(res.fun, float) assert isinstance(res.mip_node_count, int) @@ -112,7 +112,7 @@ def test_result(): assert res.status == 1 assert not res.success msg = "Time limit reached" - assert 'HiGHS Status 13' in res.message + assert 'HighsModelStatus.kTimeLimit' in res.message assert msg in res.message assert (res.fun is res.mip_dual_bound is res.mip_gap is res.mip_node_count is res.x is None) @@ -121,19 +121,19 @@ def test_result(): assert res.status == 2 assert not res.success msg = "The problem is infeasible" - assert 'HiGHS Status 8' in res.message + assert 'HighsModelStatus.kInfeasible' in res.message assert res.message.startswith(msg) assert (res.fun is res.mip_dual_bound is res.mip_gap is res.mip_node_count is res.x is None) - # TODO: Fix this - # res = milp(-1) - # assert res.status == 3 - # assert not res.success - # msg = "The problem is unbounded. (HiGHS Status 10:" - # assert res.message.startswith(msg) - # assert (res.fun is res.mip_dual_bound is res.mip_gap - # is res.mip_node_count is res.x is None) + res = milp(-1) + assert res.status == 3 + assert not res.success + assert 'HighsModelStatus.kUnbounded' in res.message + msg = "The problem is unbounded." + assert res.message.startswith(msg) + assert (res.fun is res.mip_dual_bound is res.mip_gap + is res.mip_node_count is res.x is None) def test_milp_optional_args(): diff --git a/subprojects/highs.wrap b/subprojects/highs.wrap index 137b46dfeb60..14468dfcfa45 100644 --- a/subprojects/highs.wrap +++ b/subprojects/highs.wrap @@ -1,3 +1,3 @@ [wrap-git] url = https://github.com/HaoZeke/highs.git -revision = d0bac8a10ce0d7bed35469c68a95450bee9dce71 +revision = 325d5356835c6d258441ad45b19a357e4857a895 From cc5e00457f74161e531c43a4b9dbbe1335ae9792 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 14 Oct 2023 17:54:02 +0000 Subject: [PATCH 005/500] ENH: Rework [highspy] interface for BC TST: Revert changes to milp tests MAINT: Quiet mypy problems w.r.t highspy MAINT: Fix typo in documentation Fixes the refguide check, the primal status for an infeasible problem is None TST: Rework the one test which returns new results BLD: Rework to include gh-17777 for highs MAINT: Rework to a minimal highspy subset MAINT: Try to disable highsint64 for 32-bit fix MAINT: Update pyproject for more meson-python args BLD: Try to conditionally build with highsint64 MAINT: Add back a submodule (personal) MAINT: Update the wrap file MAINT: Simplify pyproject for now MAINT: Simplify highs_defaults upstream --- .gitignore | 2 - .gitmodules | 4 + doc/source/tutorial/optimize.rst | 2 +- mypy.ini | 11 ++ scipy/optimize/_highs/_highs_wrapper.py | 6 +- scipy/optimize/_highs/meson.build | 8 +- scipy/optimize/_linprog_highs.py | 157 ++++++++++++++++-------- scipy/optimize/_milp.py | 2 +- scipy/optimize/tests/test_milp.py | 18 ++- subprojects/highs | 1 + subprojects/highs.wrap | 2 +- 11 files changed, 136 insertions(+), 77 deletions(-) create mode 160000 subprojects/highs diff --git a/.gitignore b/.gitignore index 9e4e9b14b9b8..a4fb8751794e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -# Wrap directories -subprojects/highs # Editor temporary/working/backup files # ######################################### .#* diff --git a/.gitmodules b/.gitmodules index 39cd9bfe50a3..dbf08905c7c9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,7 @@ [submodule "scipy/_lib/pocketfft"] path = scipy/_lib/pocketfft url = https://github.com/scipy/pocketfft +[submodule "subprojects/highs"] + path = subprojects/highs + url = https://github.com/HaoZeke/highs + branch = forSciPy diff --git a/doc/source/tutorial/optimize.rst b/doc/source/tutorial/optimize.rst index 8d6abb5f0270..23474aab5160 100644 --- a/doc/source/tutorial/optimize.rst +++ b/doc/source/tutorial/optimize.rst @@ -1596,7 +1596,7 @@ Finally, we can solve the transformed problem using :func:`linprog`. >>> bounds = [x0_bounds, x1_bounds, x2_bounds, x3_bounds] >>> result = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=bounds) >>> print(result.message) - The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is At lower/fixed bound) + The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is None) The result states that our problem is infeasible, meaning that there is no solution vector that satisfies all the constraints. That doesn't necessarily mean we did anything wrong; some problems truly are infeasible. diff --git a/mypy.ini b/mypy.ini index 3cf9d47a9aac..aa393b2c5a55 100644 --- a/mypy.ini +++ b/mypy.ini @@ -280,6 +280,17 @@ ignore_errors = True [mypy-scipy.optimize._linprog_util] ignore_errors = True +[mypy-scipy.optimize._highs.highspy._highs] +ignore_errors = True +ignore_missing_imports = True + +[mypy-scipy.optimize._highs.highspy._highs.simplex_constants] +ignore_errors = True +ignore_missing_imports = True + +[mypy-scipy.optimize._milp] +ignore_errors = True + [mypy-scipy.optimize._trustregion] ignore_errors = True diff --git a/scipy/optimize/_highs/_highs_wrapper.py b/scipy/optimize/_highs/_highs_wrapper.py index e80a04918254..909d9d651a44 100644 --- a/scipy/optimize/_highs/_highs_wrapper.py +++ b/scipy/optimize/_highs/_highs_wrapper.py @@ -1,7 +1,7 @@ from warnings import warn import numpy as np -from scipy.optimize._highs.highspy.highs import _h +import scipy.optimize._highs.highspy._highs as _h from scipy.optimize._highs.highspy import _highs_options as hopt # type: ignore[attr-defined] from scipy.optimize import OptimizeWarning @@ -36,7 +36,7 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti lp.integrality_ = [_h.HighsVarType(i) for i in integrality] # Make a Highs object and pass it everything - highs = _h.Highs_() + highs = _h.Highs() highs_options = _h.HighsOptions() hoptmanager = hopt.HighsOptionsManager() for key, val in options.items(): @@ -146,7 +146,7 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti { "status": model_status, "message": f"model_status is {highs.modelStatusToString(model_status)}; " - f"primal_status is " + "primal_status is " f"{highs.solutionStatusToString(info.primal_solution_status)}", "simplex_nit": info.simplex_iteration_count, "ipm_nit": info.ipm_iteration_count, diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index b164c2265f11..21e49b8b9c60 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -2,7 +2,6 @@ highs_proj = subproject('highs', default_options : ['default_library=static']) highs_dep = highs_proj.get_variable('highs_dep') -highspy_py = highs_proj.get_variable('highspy_py') highspy_cpp = highs_proj.get_variable('highspy_cpp') highsoptions_cpp = highs_proj.get_variable('highsoptions_cpp') @@ -10,6 +9,8 @@ scipy_highspy_dep = [ py3_dep, pybind11_dep, highs_dep, + thread_dep, + atomic_dep, ] py3.extension_module( @@ -35,11 +36,6 @@ py3.extension_module( install: true, ) -py3.install_sources( - highspy_py, - subdir: 'scipy/optimize/_highs/highspy', -) - py3.install_sources([ '__init__.py', '_highs_wrapper.py', diff --git a/scipy/optimize/_linprog_highs.py b/scipy/optimize/_linprog_highs.py index 9dc8110ba56b..dc5afa362e43 100644 --- a/scipy/optimize/_linprog_highs.py +++ b/scipy/optimize/_linprog_highs.py @@ -20,79 +20,132 @@ from ._highs._highs_wrapper import _highs_wrapper from scipy.sparse import csc_matrix, vstack, issparse -from scipy.optimize._highs.highspy.highs import _h -from scipy.optimize._highs.highspy.highs import simpc - - -class SciPyRC(Enum): - """Return codes for SciPy solvers""" - - OPTIMAL = 0 - ITERATION_LIMIT = 1 - INFEASIBLE = 2 - UNBOUNDED = 3 - NUMERICAL = 4 - - def to_string(self): - if self == SciPyRC.OPTIMAL: - return "Optimization terminated successfully. " - elif self == SciPyRC.ITERATION_LIMIT: - return "Iteration limit reached. " - elif self == SciPyRC.INFEASIBLE: - return "The problem is infeasible. " - elif self == SciPyRC.UNBOUNDED: - return "The problem is unbounded. " - elif self == SciPyRC.NUMERICAL: - return "Serious numerical difficulties encountered. " - else: - return "" +import scipy.optimize._highs.highspy._highs as _h +import scipy.optimize._highs.highspy._highs.simplex_constants as simpc class HighsStatusMapping: - """Class to map HiGHS statuses to SciPy Return Codes""" + """Class to map HiGHS statuses to SciPy-like Return Codes and Messages""" + + class SciPyRC(Enum): + """Return codes like SciPy's for solvers""" + + OPTIMAL = 0 + ITERATION_LIMIT = 1 + INFEASIBLE = 2 + UNBOUNDED = 3 + NUMERICAL = 4 def __init__(self): - # Custom mapping from HiGHS status and errors to SciPy status self.highs_to_scipy = { - _h.HighsModelStatus.kNotset: (SciPyRC.NUMERICAL, "Not set"), - _h.HighsModelStatus.kLoadError: (SciPyRC.NUMERICAL, "Load Error"), - _h.HighsModelStatus.kModelError: (SciPyRC.INFEASIBLE, "Model Error"), - _h.HighsModelStatus.kPresolveError: (SciPyRC.NUMERICAL, "Presolve Error"), - _h.HighsModelStatus.kSolveError: (SciPyRC.NUMERICAL, "Solve Error"), - _h.HighsModelStatus.kPostsolveError: (SciPyRC.NUMERICAL, "Postsolve Error"), - _h.HighsModelStatus.kModelEmpty: (SciPyRC.NUMERICAL, "Model Empty"), - _h.HighsModelStatus.kOptimal: (SciPyRC.OPTIMAL, "Optimal"), - _h.HighsModelStatus.kInfeasible: (SciPyRC.INFEASIBLE, "Infeasible"), + _h.HighsModelStatus.kNotset: ( + self.SciPyRC.NUMERICAL, + "Not set", + "Serious numerical difficulties encountered.", + ), + _h.HighsModelStatus.kLoadError: ( + self.SciPyRC.NUMERICAL, + "Load Error", + "Serious numerical difficulties encountered.", + ), + _h.HighsModelStatus.kModelError: ( + self.SciPyRC.INFEASIBLE, + "Model Error", + "The problem is infeasible.", + ), + _h.HighsModelStatus.kPresolveError: ( + self.SciPyRC.NUMERICAL, + "Presolve Error", + "Serious numerical difficulties encountered.", + ), + _h.HighsModelStatus.kSolveError: ( + self.SciPyRC.NUMERICAL, + "Solve Error", + "Serious numerical difficulties encountered.", + ), + _h.HighsModelStatus.kPostsolveError: ( + self.SciPyRC.NUMERICAL, + "Postsolve Error", + "Serious numerical difficulties encountered.", + ), + _h.HighsModelStatus.kModelEmpty: ( + self.SciPyRC.NUMERICAL, + "Model Empty", + "Serious numerical difficulties encountered.", + ), + _h.HighsModelStatus.kOptimal: ( + self.SciPyRC.OPTIMAL, + "Optimal", + "Optimization terminated successfully.", + ), + _h.HighsModelStatus.kInfeasible: ( + self.SciPyRC.INFEASIBLE, + "Infeasible", + "The problem is infeasible.", + ), _h.HighsModelStatus.kUnboundedOrInfeasible: ( - SciPyRC.NUMERICAL, + self.SciPyRC.NUMERICAL, "Unbounded or Infeasible", + "Serious numerical difficulties encountered.", + ), + _h.HighsModelStatus.kUnbounded: ( + self.SciPyRC.UNBOUNDED, + "Unbounded", + "The problem is unbounded.", + ), + _h.HighsModelStatus.kObjectiveBound: ( + self.SciPyRC.NUMERICAL, + "Objective Bound", + "Serious numerical difficulties encountered.", ), - _h.HighsModelStatus.kUnbounded: (SciPyRC.UNBOUNDED, "Unbounded"), - _h.HighsModelStatus.kObjectiveBound: (SciPyRC.NUMERICAL, "Objective Bound"), _h.HighsModelStatus.kObjectiveTarget: ( - SciPyRC.NUMERICAL, + self.SciPyRC.NUMERICAL, "Objective Target", + "Serious numerical difficulties encountered.", + ), + _h.HighsModelStatus.kTimeLimit: ( + self.SciPyRC.ITERATION_LIMIT, + "Time Limit", + "Time limit reached.", ), - _h.HighsModelStatus.kTimeLimit: (SciPyRC.ITERATION_LIMIT, "Time Limit"), _h.HighsModelStatus.kIterationLimit: ( - SciPyRC.ITERATION_LIMIT, + self.SciPyRC.ITERATION_LIMIT, "Iteration Limit", + "Iteration limit reached.", + ), + _h.HighsModelStatus.kUnknown: ( + self.SciPyRC.NUMERICAL, + "Unknown", + "Serious numerical difficulties encountered.", + ), + _h.HighsModelStatus.kSolutionLimit: ( + self.SciPyRC.NUMERICAL, + "Solution Limit", + "Serious numerical difficulties encountered.", ), - _h.HighsModelStatus.kUnknown: (SciPyRC.NUMERICAL, "Unknown"), - _h.HighsModelStatus.kSolutionLimit: (SciPyRC.NUMERICAL, "Solution Limit"), } def get_scipy_status(self, highs_status, highs_message): - """Converts HiGHS status and message to SciPy status and message""" - scipy_status, message_prefix = self.highs_to_scipy.get( + """Converts HiGHS status and message to SciPy-like status and messages""" + if highs_status is None or highs_message is None: + print(f"Highs Status: {highs_status}, Message: {highs_message}") + return ( + self.SciPyRC.NUMERICAL.value, + "HiGHS did not provide a status code. (HiGHS Status None: None)", + ) + + scipy_status_enum, message_prefix, scipy_message = self.highs_to_scipy.get( _h.HighsModelStatus(highs_status), - (SciPyRC.NUMERICAL, "Unknown HiGHS Status"), + ( + self.SciPyRC.NUMERICAL, + "Unknown HiGHS Status", + "The HiGHS status code was not recognized.", + ), ) - scip = SciPyRC(scipy_status) - scipy_message = ( - f"{scip.to_string()} (HiGHS Status {highs_status}: {highs_message})" + full_scipy_message = ( + f"{scipy_message} (HiGHS Status {int(highs_status)}: {highs_message})" ) - return scipy_status.value, scipy_message + return scipy_status_enum.value, full_scipy_message def _replace_inf(x): diff --git a/scipy/optimize/_milp.py b/scipy/optimize/_milp.py index bc4f192b4992..f70e3297af4a 100644 --- a/scipy/optimize/_milp.py +++ b/scipy/optimize/_milp.py @@ -2,7 +2,7 @@ import numpy as np from scipy.sparse import csc_array, vstack, issparse from scipy._lib._util import VisibleDeprecationWarning -from ._highs._highs_wrapper import _highs_wrapper # type: ignore[import] +from ._highs._highs_wrapper import _highs_wrapper from ._constraints import LinearConstraint, Bounds from ._optimize import OptimizeResult from ._linprog_highs import HighsStatusMapping diff --git a/scipy/optimize/tests/test_milp.py b/scipy/optimize/tests/test_milp.py index f78cb2cd0ac0..4b28ff41a371 100644 --- a/scipy/optimize/tests/test_milp.py +++ b/scipy/optimize/tests/test_milp.py @@ -97,9 +97,8 @@ def test_result(): res = milp(c=c, constraints=(A, b, b), bounds=(0, 1), integrality=1) assert res.status == 0 assert res.success - msg = "Optimization terminated successfully." + msg = "Optimization terminated successfully. (HiGHS Status 7:" assert res.message.startswith(msg) - assert 'HighsModelStatus.kOptimal' in res.message assert isinstance(res.x, np.ndarray) assert isinstance(res.fun, float) assert isinstance(res.mip_node_count, int) @@ -111,8 +110,8 @@ def test_result(): options={'time_limit': 0.05}) assert res.status == 1 assert not res.success - msg = "Time limit reached" - assert 'HighsModelStatus.kTimeLimit' in res.message + msg = "Time limit reached. (HiGHS Status 13:" + assert res.message.startswith(msg) assert msg in res.message assert (res.fun is res.mip_dual_bound is res.mip_gap is res.mip_node_count is res.x is None) @@ -120,8 +119,7 @@ def test_result(): res = milp(1, bounds=(1, -1)) assert res.status == 2 assert not res.success - msg = "The problem is infeasible" - assert 'HighsModelStatus.kInfeasible' in res.message + msg = "The problem is infeasible. (HiGHS Status 8:" assert res.message.startswith(msg) assert (res.fun is res.mip_dual_bound is res.mip_gap is res.mip_node_count is res.x is None) @@ -129,8 +127,7 @@ def test_result(): res = milp(-1) assert res.status == 3 assert not res.success - assert 'HighsModelStatus.kUnbounded' in res.message - msg = "The problem is unbounded." + msg = "The problem is unbounded. (HiGHS Status 10:" assert res.message.startswith(msg) assert (res.fun is res.mip_dual_bound is res.mip_gap is res.mip_node_count is res.x is None) @@ -294,14 +291,13 @@ def test_infeasible_prob_16609(): _msg_time = "Time limit reached. (HiGHS Status 13:" -_msg_iter = "Iteration limit reached. (HiGHS Status 14:" +_msg_sol = "Serious numerical difficulties encountered. (HiGHS Status 16:" @pytest.mark.skipif(np.intp(0).itemsize < 8, reason="Unhandled 32-bit GCC FP bug") -@pytest.mark.slow @pytest.mark.parametrize(["options", "msg"], [({"time_limit": 0.1}, _msg_time), - ({"node_limit": 1}, _msg_iter)]) + ({"node_limit": 1}, _msg_sol)]) def test_milp_timeout_16545(options, msg): # Ensure solution is not thrown away if MILP solver times out # -- see gh-16545 diff --git a/subprojects/highs b/subprojects/highs new file mode 160000 index 000000000000..704a264a4123 --- /dev/null +++ b/subprojects/highs @@ -0,0 +1 @@ +Subproject commit 704a264a4123178ab09776864ba5bcbbdb69e3f7 diff --git a/subprojects/highs.wrap b/subprojects/highs.wrap index 14468dfcfa45..045f16018e55 100644 --- a/subprojects/highs.wrap +++ b/subprojects/highs.wrap @@ -1,3 +1,3 @@ [wrap-git] url = https://github.com/HaoZeke/highs.git -revision = 325d5356835c6d258441ad45b19a357e4857a895 +revision = 704a264a4123178ab09776864ba5bcbbdb69e3f7 From 339755e2501e3ccde5a5a3b9c8590dcadb3a9b16 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 21 Oct 2023 23:17:48 +0000 Subject: [PATCH 006/500] MAINT: Pin highs to a version without gh-15888 MAINT: Switch to the scipy fork of highs TST: xfail known flaky test Co-authored-by: mckib2 MAINT: Add a note on .gitmodule location Co-authored-by: h-vetinari --- .gitmodules | 5 +++-- scipy/optimize/tests/test_milp.py | 2 ++ subprojects/highs | 2 +- subprojects/highs.wrap | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index dbf08905c7c9..f57d6adbfd78 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,7 +19,8 @@ [submodule "scipy/_lib/pocketfft"] path = scipy/_lib/pocketfft url = https://github.com/scipy/pocketfft +# All submodules are required to be under subprojects for meson: +# https://mesonbuild.com/Subprojects.html#why-must-all-subprojects-be-inside-a-single-directory [submodule "subprojects/highs"] path = subprojects/highs - url = https://github.com/HaoZeke/highs - branch = forSciPy + url = https://github.com/scipy/highs diff --git a/scipy/optimize/tests/test_milp.py b/scipy/optimize/tests/test_milp.py index 4b28ff41a371..8bf2d8e6b60e 100644 --- a/scipy/optimize/tests/test_milp.py +++ b/scipy/optimize/tests/test_milp.py @@ -294,6 +294,8 @@ def test_infeasible_prob_16609(): _msg_sol = "Serious numerical difficulties encountered. (HiGHS Status 16:" +# See https://github.com/scipy/scipy/pull/19255#issuecomment-1778438888 +@pytest.mark.xfail(reason="Often buggy, revisit with callbacks, gh-19255") @pytest.mark.skipif(np.intp(0).itemsize < 8, reason="Unhandled 32-bit GCC FP bug") @pytest.mark.parametrize(["options", "msg"], [({"time_limit": 0.1}, _msg_time), diff --git a/subprojects/highs b/subprojects/highs index 704a264a4123..a0df06fb20f2 160000 --- a/subprojects/highs +++ b/subprojects/highs @@ -1 +1 @@ -Subproject commit 704a264a4123178ab09776864ba5bcbbdb69e3f7 +Subproject commit a0df06fb20f2c67c02cf84d8a3b72862c2ab2b27 diff --git a/subprojects/highs.wrap b/subprojects/highs.wrap index 045f16018e55..6c1653068b87 100644 --- a/subprojects/highs.wrap +++ b/subprojects/highs.wrap @@ -1,3 +1,3 @@ [wrap-git] -url = https://github.com/HaoZeke/highs.git -revision = 704a264a4123178ab09776864ba5bcbbdb69e3f7 +url = https://github.com/scipy/highs.git +revision = a0df06fb20f2c67c02cf84d8a3b72862c2ab2b27 From ee0b4cacbc4397d0dcb248ccf9ce6dc8cc1eaffa Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 3 Feb 2024 16:59:17 +0000 Subject: [PATCH 007/500] DOC: Update comment for subprojects Co-authored-by: Ralf Gommers --- .gitmodules | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index f57d6adbfd78..5d5e60aa5a16 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,7 +19,8 @@ [submodule "scipy/_lib/pocketfft"] path = scipy/_lib/pocketfft url = https://github.com/scipy/pocketfft -# All submodules are required to be under subprojects for meson: +# All submodules used as a Meson `subproject` are required to be under the +# subprojects/ directory - see: # https://mesonbuild.com/Subprojects.html#why-must-all-subprojects-be-inside-a-single-directory [submodule "subprojects/highs"] path = subprojects/highs From 28447be47ed07a0c635a1c56c9ee493ffefcdd34 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 3 Feb 2024 17:11:14 +0000 Subject: [PATCH 008/500] MAINT: Reverse black formatting partially --- scipy/optimize/_linprog_highs.py | 166 +++++++++++++------------------ 1 file changed, 70 insertions(+), 96 deletions(-) diff --git a/scipy/optimize/_linprog_highs.py b/scipy/optimize/_linprog_highs.py index dc5afa362e43..e152da6927bc 100644 --- a/scipy/optimize/_linprog_highs.py +++ b/scipy/optimize/_linprog_highs.py @@ -189,21 +189,15 @@ def convert_to_highs_enum(option, option_str, choices_enum, default_value): return enum_value.to_highs_enum() -def _linprog_highs( - lp, - solver, - time_limit=None, - presolve=True, - disp=False, - maxiter=None, - dual_feasibility_tolerance=None, - primal_feasibility_tolerance=None, - ipm_optimality_tolerance=None, - simplex_dual_edge_weight_strategy=None, - mip_rel_gap=None, - mip_max_nodes=None, - **unknown_options, -): +def _linprog_highs(lp, solver, time_limit=None, presolve=True, + disp=False, maxiter=None, + dual_feasibility_tolerance=None, + primal_feasibility_tolerance=None, + ipm_optimality_tolerance=None, + simplex_dual_edge_weight_strategy=None, + mip_rel_gap=None, + mip_max_nodes=None, + **unknown_options): r""" Solve the following linear programming problem using one of the HiGHS solvers: @@ -393,10 +387,8 @@ def _linprog_highs( simplex algorithm." Mathematical Programming 12.1 (1977): 361-371. """ if unknown_options: - message = ( - f"Unrecognized options detected: {unknown_options}. " - "These will be passed to HiGHS verbatim." - ) + message = (f"Unrecognized options detected: {unknown_options}. " + "These will be passed to HiGHS verbatim.") warn(message, OptimizeWarning, stacklevel=3) # Map options to HiGHS enum values @@ -412,7 +404,7 @@ def _linprog_highs( lb, ub = bounds.T.copy() # separate bounds, copy->C-cntgs # highs_wrapper solves LHS <= A*x <= RHS, not equality constraints with np.errstate(invalid="ignore"): - lhs_ub = -np.ones_like(b_ub) * np.inf # LHS of UB constraints is -inf + lhs_ub = -np.ones_like(b_ub)*np.inf # LHS of UB constraints is -inf rhs_ub = b_ub # RHS of UB constraints is b_ub lhs_eq = b_eq # Equality constraint is inequality rhs_eq = b_eq # constraint with LHS=RHS @@ -426,23 +418,23 @@ def _linprog_highs( A = csc_matrix(A) options = { - "presolve": presolve, - "sense": _h.ObjSense.kMinimize, - "solver": solver, - "time_limit": time_limit, + 'presolve': presolve, + 'sense': _h.ObjSense.kMinimize, + 'solver': solver, + 'time_limit': time_limit, # 'highs_debug_level': _h.kHighs, # TODO - "dual_feasibility_tolerance": dual_feasibility_tolerance, - "ipm_optimality_tolerance": ipm_optimality_tolerance, - "log_to_console": disp, - "mip_max_nodes": mip_max_nodes, - "output_flag": disp, - "primal_feasibility_tolerance": primal_feasibility_tolerance, - "simplex_dual_edge_weight_strategy": simplex_dual_edge_weight_strategy_enum, - "simplex_strategy": simpc.kSimplexStrategyDual.value, + 'dual_feasibility_tolerance': dual_feasibility_tolerance, + 'ipm_optimality_tolerance': ipm_optimality_tolerance, + 'log_to_console': disp, + 'mip_max_nodes': mip_max_nodes, + 'output_flag': disp, + 'primal_feasibility_tolerance': primal_feasibility_tolerance, + 'simplex_dual_edge_weight_strategy': simplex_dual_edge_weight_strategy_enum, + 'simplex_strategy': simpc.kSimplexStrategyDual.value, # 'simplex_crash_strategy': simpc.SimplexCrashStrategy.kSimplexCrashStrategyOff, - "ipm_iteration_limit": maxiter, - "simplex_iteration_limit": maxiter, - "mip_rel_gap": mip_rel_gap, + 'ipm_iteration_limit': maxiter, + 'simplex_iteration_limit': maxiter, + 'mip_rel_gap': mip_rel_gap, } options.update(unknown_options) @@ -457,36 +449,26 @@ def _linprog_highs( else: integrality = np.array(integrality) - res = _highs_wrapper( - c, - A.indptr, - A.indices, - A.data, - lhs, - rhs, - lb, - ub, - integrality.astype(np.uint8), - options, - ) + res = _highs_wrapper(c, A.indptr, A.indices, A.data, lhs, rhs, + lb, ub, integrality.astype(np.uint8), options) # HiGHS represents constraints as lhs/rhs, so # Ax + s = b => Ax = b - s # and we need to split up s by A_ub and A_eq - if "slack" in res: - slack = res["slack"] - con = np.array(slack[len(b_ub) :]) - slack = np.array(slack[: len(b_ub)]) + if 'slack' in res: + slack = res['slack'] + con = np.array(slack[len(b_ub):]) + slack = np.array(slack[:len(b_ub)]) else: slack, con = None, None # lagrange multipliers for equalities/inequalities and upper/lower bounds - if "lambda" in res: - lamda = res["lambda"] - marg_ineqlin = np.array(lamda[: len(b_ub)]) - marg_eqlin = np.array(lamda[len(b_ub) :]) - marg_upper = np.array(res["marg_bnds"][1, :]) - marg_lower = np.array(res["marg_bnds"][0, :]) + if 'lambda' in res: + lamda = res['lambda'] + marg_ineqlin = np.array(lamda[:len(b_ub)]) + marg_eqlin = np.array(lamda[len(b_ub):]) + marg_upper = np.array(res['marg_bnds'][1, :]) + marg_lower = np.array(res['marg_bnds'][0, :]) else: marg_ineqlin, marg_eqlin = None, None marg_upper, marg_lower = None, None @@ -507,46 +489,38 @@ def is_valid_x(val): x = np.array(res["x"]) if "x" in res and is_valid_x(res["x"]) else None - sol = { - "x": x, - "slack": slack, - "con": con, - "ineqlin": OptimizeResult( - { - "residual": slack, - "marginals": marg_ineqlin, - } - ), - "eqlin": OptimizeResult( - { - "residual": con, - "marginals": marg_eqlin, - } - ), - "lower": OptimizeResult( - { - "residual": None if x is None else x - lb, - "marginals": marg_lower, - } - ), - "upper": OptimizeResult( - {"residual": None if x is None else ub - x, "marginals": marg_upper} - ), - "fun": res.get("fun"), - "status": status, - "success": res["status"] == _h.HighsModelStatus.kOptimal, - "message": message, - "nit": res.get("simplex_nit", 0) or res.get("ipm_nit", 0), - "crossover_nit": res.get("crossover_nit"), - } + sol = {'x': x, + 'slack': slack, + 'con': con, + 'ineqlin': OptimizeResult({ + 'residual': slack, + 'marginals': marg_ineqlin, + }), + 'eqlin': OptimizeResult({ + 'residual': con, + 'marginals': marg_eqlin, + }), + 'lower': OptimizeResult({ + 'residual': None if x is None else x - lb, + 'marginals': marg_lower, + }), + 'upper': OptimizeResult({ + 'residual': None if x is None else ub - x, + 'marginals': marg_upper + }), + 'fun': res.get('fun'), + 'status': status, + 'success': res['status'] == _h.HighsModelStatus.kOptimal, + 'message': message, + 'nit': res.get('simplex_nit', 0) or res.get('ipm_nit', 0), + 'crossover_nit': res.get('crossover_nit'), + } if np.any(x) and integrality is not None: - sol.update( - { - "mip_node_count": res.get("mip_node_count", 0), - "mip_dual_bound": res.get("mip_dual_bound", 0.0), - "mip_gap": res.get("mip_gap", 0.0), - } - ) + sol.update({ + 'mip_node_count': res.get('mip_node_count', 0), + 'mip_dual_bound': res.get('mip_dual_bound', 0.0), + 'mip_gap': res.get('mip_gap', 0.0), + }) return sol From 9cb657cf16c125e23f3343e92bb424d2549c948a Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 3 Feb 2024 17:28:58 +0000 Subject: [PATCH 009/500] MAINT: Fix linter concerns --- scipy/optimize/_linprog_highs.py | 21 ++++++++++++++------- tools/lint.toml | 7 ++++--- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/scipy/optimize/_linprog_highs.py b/scipy/optimize/_linprog_highs.py index e152da6927bc..283476d45ecc 100644 --- a/scipy/optimize/_linprog_highs.py +++ b/scipy/optimize/_linprog_highs.py @@ -128,7 +128,10 @@ def __init__(self): def get_scipy_status(self, highs_status, highs_message): """Converts HiGHS status and message to SciPy-like status and messages""" if highs_status is None or highs_message is None: - print(f"Highs Status: {highs_status}, Message: {highs_message}") + warn( + f"Highs Status: {highs_status}, Message: {highs_message}", + stacklevel=3 + ) return ( self.SciPyRC.NUMERICAL.value, "HiGHS did not provide a status code. (HiGHS Status None: None)", @@ -142,10 +145,10 @@ def get_scipy_status(self, highs_status, highs_message): "The HiGHS status code was not recognized.", ), ) - full_scipy_message = ( + full_scipy_msg = ( f"{scipy_message} (HiGHS Status {int(highs_status)}: {highs_message})" ) - return scipy_status_enum.value, full_scipy_message + return scipy_status_enum.value, full_scipy_msg def _replace_inf(x): @@ -164,10 +167,14 @@ class SimplexStrategy(Enum): def to_highs_enum(self): mapping = { - SimplexStrategy.DANTZIG: simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDantzig.value, - SimplexStrategy.DEVEX: simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDevex.value, - SimplexStrategy.STEEPEST_DEVEX: simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyChoose.value, - SimplexStrategy.STEEPEST: simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategySteepestEdge.value, + SimplexStrategy.DANTZIG: + simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDantzig.value, + SimplexStrategy.DEVEX: + simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDevex.value, + SimplexStrategy.STEEPEST_DEVEX: + simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyChoose.value, + SimplexStrategy.STEEPEST: + simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategySteepestEdge.value, } return mapping.get(self) diff --git a/tools/lint.toml b/tools/lint.toml index 2f04d356026f..5d1f0f20f26c 100644 --- a/tools/lint.toml +++ b/tools/lint.toml @@ -10,11 +10,12 @@ target-version = "py39" # Also, `PGH004` which checks for blanket (non-specific) `noqa`s # and `B028` which checks that warnings include the `stacklevel` keyword. # `B028` added in gh-19623. -select = ["E", "F", "PGH004", "UP", "B028"] -ignore = ["E741"] +lint.select = ["E", "F", "PGH004", "UP", "B028"] +lint.ignore = ["E741"] +exclude = ["scipy/datasets/_registry.py"] # Allow unused variables when underscore-prefixed. -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [lint.per-file-ignores] "**/__init__.py" = ["E402", "F401", "F403", "F405"] From 168042281c0987fcf015987599523b76ae9500c7 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 3 Feb 2024 18:27:03 +0000 Subject: [PATCH 010/500] BLD: Never use zlib for highs --- scipy/optimize/_highs/meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index 21e49b8b9c60..9f0e30778649 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -1,6 +1,7 @@ # Setup the highs library highs_proj = subproject('highs', - default_options : ['default_library=static']) + default_options : ['default_library=static', + 'use_zlib=false']) highs_dep = highs_proj.get_variable('highs_dep') highspy_cpp = highs_proj.get_variable('highspy_cpp') highsoptions_cpp = highs_proj.get_variable('highsoptions_cpp') From 1466d519f9a2cffc470b6911efef36c1c295fac6 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 3 Feb 2024 18:27:26 +0000 Subject: [PATCH 011/500] BLD: Remove highs wrap, add back git subm check Co-authored-by: rgommers --- scipy/optimize/_highs/meson.build | 4 ++++ subprojects/highs.wrap | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 subprojects/highs.wrap diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index 9f0e30778649..2ffc324292da 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -1,4 +1,8 @@ # Setup the highs library +fs = import('fs') +if not fs.exists('../../../subprojects/highs/README.md') + error('Missing the `highs` submodule! Run `git submodule update --init` to fix this.') +endif highs_proj = subproject('highs', default_options : ['default_library=static', 'use_zlib=false']) diff --git a/subprojects/highs.wrap b/subprojects/highs.wrap deleted file mode 100644 index 6c1653068b87..000000000000 --- a/subprojects/highs.wrap +++ /dev/null @@ -1,3 +0,0 @@ -[wrap-git] -url = https://github.com/scipy/highs.git -revision = a0df06fb20f2c67c02cf84d8a3b72862c2ab2b27 From 2ad566ab387bf73ade3b95f760eb703187d695bf Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 3 Feb 2024 18:43:25 +0000 Subject: [PATCH 012/500] BLD: Never use zlib --- scipy/optimize/_highs/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index 2ffc324292da..9fdb5ec33251 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -5,7 +5,7 @@ if not fs.exists('../../../subprojects/highs/README.md') endif highs_proj = subproject('highs', default_options : ['default_library=static', - 'use_zlib=false']) + 'use_zlib=disabled']) highs_dep = highs_proj.get_variable('highs_dep') highspy_cpp = highs_proj.get_variable('highspy_cpp') highsoptions_cpp = highs_proj.get_variable('highsoptions_cpp') From 8d9f6e594d8cad91674a590d91d2bee9c0a45eb5 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 3 Feb 2024 20:03:19 +0000 Subject: [PATCH 013/500] MAINT: Fix tests --- scipy/optimize/_linprog_highs.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scipy/optimize/_linprog_highs.py b/scipy/optimize/_linprog_highs.py index 283476d45ecc..e703a32c41d9 100644 --- a/scipy/optimize/_linprog_highs.py +++ b/scipy/optimize/_linprog_highs.py @@ -128,10 +128,7 @@ def __init__(self): def get_scipy_status(self, highs_status, highs_message): """Converts HiGHS status and message to SciPy-like status and messages""" if highs_status is None or highs_message is None: - warn( - f"Highs Status: {highs_status}, Message: {highs_message}", - stacklevel=3 - ) + print(f"Highs Status: {highs_status}, Message: {highs_message}") return ( self.SciPyRC.NUMERICAL.value, "HiGHS did not provide a status code. (HiGHS Status None: None)", From 181c66aa0f216555eb914d37f8a30b9005de82be Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 17 Feb 2024 19:59:16 +0000 Subject: [PATCH 014/500] MAINT: Lint cleanup --- scipy/optimize/_highs/_highs_wrapper.py | 10 +++++++--- tools/lint.toml | 7 +++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/scipy/optimize/_highs/_highs_wrapper.py b/scipy/optimize/_highs/_highs_wrapper.py index 909d9d651a44..7fe5f387c0db 100644 --- a/scipy/optimize/_highs/_highs_wrapper.py +++ b/scipy/optimize/_highs/_highs_wrapper.py @@ -47,7 +47,8 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti # ask for the option type opt_type = hoptmanager.get_option_type(key) if -1 == opt_type: - warn(f"Unrecognized options detected: {dict({key: val})}", OptimizeWarning) + warn(f"Unrecognized options detected: {dict({key: val})}", + OptimizeWarning, stacklevel = 2) continue else: if key in ("presolve", "parallel"): @@ -59,6 +60,7 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti f'Option f"{key}" is "{val}", but only True or False is ' f"allowed. Using default.", OptimizeWarning, + stacklevel = 2, ) continue opt_type = _h.HighsOptionType(opt_type) @@ -69,12 +71,13 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti f'Option f"{key}" is "{val}", but only True or False is ' f"allowed. Using default.", OptimizeWarning, + stacklevel = 2, ) continue # warn or set option if status != 0: - warn(msg, OptimizeWarning) + warn(msg, OptimizeWarning, stacklevel = 2) else: setattr(highs_options, key, val) @@ -145,7 +148,8 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti res.update( { "status": model_status, - "message": f"model_status is {highs.modelStatusToString(model_status)}; " + "message": "model_status is " + f"{highs.modelStatusToString(model_status)}; " "primal_status is " f"{highs.solutionStatusToString(info.primal_solution_status)}", "simplex_nit": info.simplex_iteration_count, diff --git a/tools/lint.toml b/tools/lint.toml index 5d1f0f20f26c..2f04d356026f 100644 --- a/tools/lint.toml +++ b/tools/lint.toml @@ -10,12 +10,11 @@ target-version = "py39" # Also, `PGH004` which checks for blanket (non-specific) `noqa`s # and `B028` which checks that warnings include the `stacklevel` keyword. # `B028` added in gh-19623. -lint.select = ["E", "F", "PGH004", "UP", "B028"] -lint.ignore = ["E741"] -exclude = ["scipy/datasets/_registry.py"] +select = ["E", "F", "PGH004", "UP", "B028"] +ignore = ["E741"] # Allow unused variables when underscore-prefixed. -lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [lint.per-file-ignores] "**/__init__.py" = ["E402", "F401", "F403", "F405"] From a9f0200b3a42f543c108d6fb4a91be22a82e1607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Thu, 20 Jul 2023 18:43:47 -0500 Subject: [PATCH 015/500] ENH: Move symiirorder1 initial condition into its own function --- scipy/signal/_bspline_util.c.in | 50 ++++++++++++- scipy/signal/_bsplines.py | 2 +- scipy/signal/_splinemodule.c | 128 ++++++++++++++++++++++++++++++++ scipy/signal/_splinemodule.h | 4 + 4 files changed, 182 insertions(+), 2 deletions(-) diff --git a/scipy/signal/_bspline_util.c.in b/scipy/signal/_bspline_util.c.in index fd0d2d74b368..cbb3c3946402 100644 --- a/scipy/signal/_bspline_util.c.in +++ b/scipy/signal/_bspline_util.c.in @@ -227,6 +227,55 @@ int {{SUB}}_IIR_forback1({{TYP}} c0, {{TYP}} z1, {{TYP}} *x, {{TYP}} *y, {{endif}} {{endfor}} + +{{for SUB, TYP, RTYP in zip(NAMES, TYPES, RTYPES)}} +{{if SUB in CNAME}} +#ifdef __GNUC__ +{{endif}} +int {{SUB}}_SYM_IIR1_initial({{TYP}} z1, {{TYP}} *x, {{TYP}} *yp0, + int M, int N, {{RTYP}} precision) +{ + {{TYP}} *cur_yp0 = NULL; + {{TYP}} powz1, diff; + {{RTYP}} err; + int k; + + if (ABSQ(z1) >= 1.0) return -2; /* z1 not less than 1 */ + + if ((cur_yp0 = malloc(M*sizeof({{TYP}})))==NULL) return -1; + + /* Fix starting value assuming mirror-symmetric boundary conditions. */ + for(int i = 0; i < M; i++) { + cur_yp0[i] = x[N * i]; + } + + powz1 = 1.0; + k = 0; + precision *= precision; + do { + memcpy(yp0, cur_yp0, M * sizeof({{TYP}})); + powz1 *= z1; + for(int i = 0; i < M; i++) { + cur_yp0[i] += powz1 * x[N * i + k]; + } + diff = powz1; + err = ABSQ(diff); + k++; + } while((err > precision) && (k < N)); + if (k >= N){ + /* sum did not converge */ + return -3; + } + memcpy(yp0, cur_yp0, M * sizeof({{TYP}})); + free(cur_yp0); + return 0; +} +{{if SUB in CNAME}} +#endif +{{endif}} +{{endfor}} + + /* h must be odd length */ /* strides in units of sizeof(DATA TYPE) bytes */ @@ -525,7 +574,6 @@ int {{SUB}}_IIR_forback2 (double r, double omega, {{TYP}} *x, {{TYP}} *y, {{endfor}} - /* Find the cubic spline coefficients of an image image is M rows by N columns stored rowise in memory (vary column number first). It will be replaced with the spline coefficients. diff --git a/scipy/signal/_bsplines.py b/scipy/signal/_bsplines.py index c9b136da7c55..ae5515a90a5c 100644 --- a/scipy/signal/_bsplines.py +++ b/scipy/signal/_bsplines.py @@ -3,7 +3,7 @@ r_, atleast_1d, sqrt, exp, greater, cos, add, sin) # From splinemodule.c -from ._spline import cspline2d, sepfir2d +from ._spline import cspline2d, sepfir2d, symiirorder1_ic from ._signaltools import lfilter, sosfilt, lfiltic from scipy.interpolate import BSpline diff --git a/scipy/signal/_splinemodule.c b/scipy/signal/_splinemodule.c index b2978be1b4bf..b0fb91967616 100644 --- a/scipy/signal/_splinemodule.c +++ b/scipy/signal/_splinemodule.c @@ -298,6 +298,133 @@ static PyObject *FIRsepsym2d(PyObject *NPY_UNUSED(dummy), PyObject *args) } + +static char doc_IIRsymorder1_ic[] = "out = symiirorder1_ic(input, z1, precision=-1.0)\n" +"\n" +" Compute the (forward) mirror-symmetric boundary conditions for a smoothing\n" +" IIR filter that is composed of cascaded first-order sections.\n" +"\n" +" The starting condition returned by this function is computed based on\n" +" the following transfer function::\n" +"\n" +" 1 \n" +" H(z) = ------------ \n" +" (1 - z1/z) \n" +"\n" +"\n" +" Parameters\n" +" ----------\n" +" input : ndarray\n" +" The input signal.\n" +" z1 : scalar\n" +" Parameter in the transfer function.\n" +" precision :\n" +" Specifies the precision for calculating initial conditions\n" +" of the recursive filter based on mirror-symmetric input.\n" +"\n" +" Returns\n" +" -------\n" +" z_0 : ndarray\n" +" The mirror-symmetric initial condition for the forward IIR filter."; + +static PyObject *IIRsymorder1_ic(PyObject *NPY_UNUSED(dummy), PyObject *args) +{ + PyObject *sig=NULL; + PyArrayObject *a_sig=NULL, *out=NULL; + + Py_complex z1; + double precision = -1.0; + int thetype, ret; + npy_int M, N; + npy_intp in_size, outstrides, instrides; + + if (!PyArg_ParseTuple(args, "OD|d", &sig, &z1, &precision)) + return NULL; + + thetype = PyArray_ObjectType(sig, NPY_FLOAT); + thetype = PyArray_MIN(thetype, NPY_CDOUBLE); + a_sig = (PyArrayObject *)PyArray_FromObject(sig, thetype, 1, 1); + + if (a_sig == NULL) goto fail; + + in_size = PyArray_DIMS(a_sig); + + M = 1; + N = PyArray_DIMS(a_sig)[0]; + + if(PyArray_NDIM(a_sig) > 1) { + M = PyArray_DIMS(a_sig)[0]; + N = PyArray_DIMS(a_sig)[1]; + } + + out = (PyArrayObject *)PyArray_Empty(1, &M, thetype, 0); + if (out == NULL) goto fail; + + switch (thetype) { + case NPY_FLOAT: + { + float rz1 = z1.real; + + if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-6; + ret = S_SYM_IIR1_initial(rz1, (float *)PyArray_DATA(a_sig), + (float *)PyArray_DATA(out), M, N, + (float )precision); + } + break; + case NPY_DOUBLE: + { + double rz1 = z1.real; + + if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-11; + ret = D_SYM_IIR1_initial(rz1, (double *)PyArray_DATA(a_sig), + (double *)PyArray_DATA(out), M, N, + precision); + } + break; +#ifdef __GNUC__ + case NPY_CFLOAT: + { + __complex__ float zz1 = z1.real + 1.0i*z1.imag; + if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-6; + ret = C_SYM_IIR1_initial (zz1, (__complex__ float *)PyArray_DATA(a_sig), + (__complex__ float *)PyArray_DATA(out), M, N, + (float )precision); + } + break; + case NPY_CDOUBLE: + { + __complex__ double zz1 = z1.real + 1.0i*z1.imag; + if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-11; + ret = Z_SYM_IIR1_initial (zz1, (__complex__ double *)PyArray_DATA(a_sig), + (__complex__ double *)PyArray_DATA(out), M, N, + precision); + } + break; +#endif + default: + PYERR("Incorrect type."); + } + + if (ret == 0) { + Py_DECREF(a_sig); + return PyArray_Return(out); + } + + if (ret == -1) PYERR("Could not allocate enough memory."); + if (ret == -2) PYERR("|z1| must be less than 1.0"); + if (ret == -3) PYERR("Sum to find symmetric boundary conditions did not converge."); + + PYERR("Unknown error."); + + + fail: + Py_XDECREF(a_sig); + Py_XDECREF(out); + return NULL; + +} + + static char doc_IIRsymorder1[] = "out = symiirorder1(input, c0, z1, precision=-1.0)\n" "\n" " Implement a smoothing IIR filter with mirror-symmetric boundary conditions\n" @@ -511,6 +638,7 @@ static struct PyMethodDef toolbox_module_methods[] = { {"sepfir2d", FIRsepsym2d, METH_VARARGS, doc_FIRsepsym2d}, {"symiirorder1", IIRsymorder1, METH_VARARGS, doc_IIRsymorder1}, {"symiirorder2", IIRsymorder2, METH_VARARGS, doc_IIRsymorder2}, + {"symiirorder1_ic", IIRsymorder1_ic, METH_VARARGS, doc_IIRsymorder1_ic}, {NULL, NULL, 0, NULL} /* sentinel */ }; diff --git a/scipy/signal/_splinemodule.h b/scipy/signal/_splinemodule.h index 08e284b36344..64250e6a3e44 100644 --- a/scipy/signal/_splinemodule.h +++ b/scipy/signal/_splinemodule.h @@ -9,19 +9,23 @@ static void convert_strides(npy_intp*,npy_intp*,int,int); extern int S_cubic_spline2D(float*,float*,int,int,double,npy_intp*,npy_intp*,float); extern int S_quadratic_spline2D(float*,float*,int,int,double,npy_intp*,npy_intp*,float); +extern int S_SYM_IIR1_initial(float, float*, float*, int, int, float); extern int S_IIR_forback1(float,float,float*,float*,int,int,int,float); extern int S_IIR_forback2(double,double,float*,float*,int,int,int,float); extern int S_separable_2Dconvolve_mirror(float*,float*,int,int,float*,float*,int,int,npy_intp*,npy_intp*); extern int D_cubic_spline2D(double*,double*,int,int,double,npy_intp*,npy_intp*,double); extern int D_quadratic_spline2D(double*,double*,int,int,double,npy_intp*,npy_intp*,double); +extern int D_SYM_IIR1_initial(double, double*, double*, int, int, double); extern int D_IIR_forback1(double,double,double*,double*,int,int,int,double); extern int D_IIR_forback2(double,double,double*,double*,int,int,int,double); extern int D_separable_2Dconvolve_mirror(double*,double*,int,int,double*,double*,int,int,npy_intp*,npy_intp*); #ifdef __GNUC__ +extern int C_SYM_IIR1_initial(__complex__ float, __complex__ float*, __complex__ float*, int, int, float); extern int C_IIR_forback1(__complex__ float,__complex__ float,__complex__ float*,__complex__ float*,int,int,int,float); extern int C_separable_2Dconvolve_mirror(__complex__ float*,__complex__ float*,int,int,__complex__ float*,__complex__ float*,int,int,npy_intp*,npy_intp*); +extern int Z_SYM_IIR1_initial(__complex__ double, __complex__ double*, __complex__ double*, int, int, double); extern int Z_IIR_forback1(__complex__ double,__complex__ double,__complex__ double*,__complex__ double*,int,int,int,double); extern int Z_separable_2Dconvolve_mirror(__complex__ double*,__complex__ double*,int,int,__complex__ double*,__complex__ double*,int,int,npy_intp*,npy_intp*); #endif From e543fd57b481ddcf9d764b71c2b35ea441deb4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Fri, 21 Jul 2023 14:30:52 -0500 Subject: [PATCH 016/500] Migrate symiirorder1 to Python --- scipy/signal/__init__.py | 1 + scipy/signal/_splinemodule.c | 141 +++-------------------------------- scipy/signal/_splines.py | 60 +++++++++++++++ scipy/signal/meson.build | 1 + scipy/signal/spline.py | 2 +- 5 files changed, 74 insertions(+), 131 deletions(-) create mode 100644 scipy/signal/_splines.py diff --git a/scipy/signal/__init__.py b/scipy/signal/__init__.py index d924d05265f6..c68ae7516b14 100644 --- a/scipy/signal/__init__.py +++ b/scipy/signal/__init__.py @@ -317,6 +317,7 @@ symiirorder2, ) +from ._splines import * from ._bsplines import * from ._filter_design import * from ._fir_filter_design import * diff --git a/scipy/signal/_splinemodule.c b/scipy/signal/_splinemodule.c index b0fb91967616..6b0ae35d059b 100644 --- a/scipy/signal/_splinemodule.c +++ b/scipy/signal/_splinemodule.c @@ -315,7 +315,8 @@ static char doc_IIRsymorder1_ic[] = "out = symiirorder1_ic(input, z1, precision= " Parameters\n" " ----------\n" " input : ndarray\n" -" The input signal.\n" +" The input signal. If 2D, then it will find the initial conditions \n" +" for each of the elements on the last axis.\n" " z1 : scalar\n" " Parameter in the transfer function.\n" " precision :\n" @@ -331,12 +332,14 @@ static PyObject *IIRsymorder1_ic(PyObject *NPY_UNUSED(dummy), PyObject *args) { PyObject *sig=NULL; PyArrayObject *a_sig=NULL, *out=NULL; + npy_intp* in_size; Py_complex z1; double precision = -1.0; int thetype, ret; - npy_int M, N; - npy_intp in_size, outstrides, instrides; + npy_intp M, N; + npy_intp outstrides, instrides; + PyArray_Descr* dtype; if (!PyArg_ParseTuple(args, "OD|d", &sig, &z1, &precision)) return NULL; @@ -348,16 +351,16 @@ static PyObject *IIRsymorder1_ic(PyObject *NPY_UNUSED(dummy), PyObject *args) if (a_sig == NULL) goto fail; in_size = PyArray_DIMS(a_sig); - M = 1; - N = PyArray_DIMS(a_sig)[0]; + N = in_size[0]; if(PyArray_NDIM(a_sig) > 1) { - M = PyArray_DIMS(a_sig)[0]; - N = PyArray_DIMS(a_sig)[1]; + M = in_size[0]; + N = in_size[1]; } - out = (PyArrayObject *)PyArray_Empty(1, &M, thetype, 0); + dtype = PyArray_DescrFromType(thetype); + out = (PyArrayObject *)PyArray_Empty(1, &M, dtype, 0); if (out == NULL) goto fail; switch (thetype) { @@ -425,127 +428,6 @@ static PyObject *IIRsymorder1_ic(PyObject *NPY_UNUSED(dummy), PyObject *args) } -static char doc_IIRsymorder1[] = "out = symiirorder1(input, c0, z1, precision=-1.0)\n" -"\n" -" Implement a smoothing IIR filter with mirror-symmetric boundary conditions\n" -" using a cascade of first-order sections. The second section uses a\n" -" reversed sequence. This implements a system with the following\n" -" transfer function and mirror-symmetric boundary conditions::\n" -"\n" -" c0 \n" -" H(z) = --------------------- \n" -" (1-z1/z) (1 - z1 z) \n" -"\n" -" The resulting signal will have mirror symmetric boundary conditions as well.\n" -"\n" -" Parameters\n" -" ----------\n" -" input : ndarray\n" -" The input signal.\n" -" c0, z1 : scalar\n" -" Parameters in the transfer function.\n" -" precision :\n" -" Specifies the precision for calculating initial conditions\n" -" of the recursive filter based on mirror-symmetric input.\n" -"\n" -" Returns\n" -" -------\n" -" output : ndarray\n" -" The filtered signal."; - -static PyObject *IIRsymorder1(PyObject *NPY_UNUSED(dummy), PyObject *args) -{ - PyObject *sig=NULL; - PyArrayObject *a_sig=NULL, *out=NULL; - Py_complex c0, z1; - double precision = -1.0; - int thetype, N, ret; - npy_intp outstrides, instrides; - - if (!PyArg_ParseTuple(args, "ODD|d", &sig, &c0, &z1, &precision)) - return NULL; - - thetype = PyArray_ObjectType(sig, NPY_FLOAT); - thetype = PyArray_MIN(thetype, NPY_CDOUBLE); - a_sig = (PyArrayObject *)PyArray_FromObject(sig, thetype, 1, 1); - - if (a_sig == NULL) goto fail; - - out = (PyArrayObject *)PyArray_SimpleNew(1, PyArray_DIMS(a_sig), thetype); - if (out == NULL) goto fail; - N = PyArray_DIMS(a_sig)[0]; - - convert_strides(PyArray_STRIDES(a_sig), &instrides, PyArray_ITEMSIZE(a_sig), 1); - outstrides = 1; - - switch (thetype) { - case NPY_FLOAT: - { - float rc0 = c0.real; - float rz1 = z1.real; - - if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-6; - ret = S_IIR_forback1 (rc0, rz1, (float *)PyArray_DATA(a_sig), - (float *)PyArray_DATA(out), N, - instrides, outstrides, (float )precision); - } - break; - case NPY_DOUBLE: - { - double rc0 = c0.real; - double rz1 = z1.real; - - if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-11; - ret = D_IIR_forback1 (rc0, rz1, (double *)PyArray_DATA(a_sig), - (double *)PyArray_DATA(out), N, - instrides, outstrides, precision); - } - break; -#ifdef __GNUC__ - case NPY_CFLOAT: - { - __complex__ float zc0 = c0.real + 1.0i*c0.imag; - __complex__ float zz1 = z1.real + 1.0i*z1.imag; - if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-6; - ret = C_IIR_forback1 (zc0, zz1, (__complex__ float *)PyArray_DATA(a_sig), - (__complex__ float *)PyArray_DATA(out), N, - instrides, outstrides, (float )precision); - } - break; - case NPY_CDOUBLE: - { - __complex__ double zc0 = c0.real + 1.0i*c0.imag; - __complex__ double zz1 = z1.real + 1.0i*z1.imag; - if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-11; - ret = Z_IIR_forback1 (zc0, zz1, (__complex__ double *)PyArray_DATA(a_sig), - (__complex__ double *)PyArray_DATA(out), N, - instrides, outstrides, precision); - } - break; -#endif - default: - PYERR("Incorrect type."); - } - - if (ret == 0) { - Py_DECREF(a_sig); - return PyArray_Return(out); - } - - if (ret == -1) PYERR("Could not allocate enough memory."); - if (ret == -2) PYERR("|z1| must be less than 1.0"); - if (ret == -3) PYERR("Sum to find symmetric boundary conditions did not converge."); - - PYERR("Unknown error."); - - - fail: - Py_XDECREF(a_sig); - Py_XDECREF(out); - return NULL; - -} - static char doc_IIRsymorder2[] = "out = symiirorder2(input, r, omega, precision=-1.0)\n" "\n" " Implement a smoothing IIR filter with mirror-symmetric boundary conditions\n" @@ -636,7 +518,6 @@ static struct PyMethodDef toolbox_module_methods[] = { {"cspline2d", cspline2d, METH_VARARGS, doc_cspline2d}, {"qspline2d", qspline2d, METH_VARARGS, doc_qspline2d}, {"sepfir2d", FIRsepsym2d, METH_VARARGS, doc_FIRsepsym2d}, - {"symiirorder1", IIRsymorder1, METH_VARARGS, doc_IIRsymorder1}, {"symiirorder2", IIRsymorder2, METH_VARARGS, doc_IIRsymorder2}, {"symiirorder1_ic", IIRsymorder1_ic, METH_VARARGS, doc_IIRsymorder1_ic}, {NULL, NULL, 0, NULL} /* sentinel */ diff --git a/scipy/signal/_splines.py b/scipy/signal/_splines.py new file mode 100644 index 000000000000..c5236b71fe72 --- /dev/null +++ b/scipy/signal/_splines.py @@ -0,0 +1,60 @@ + +import numpy as np + +from ._arraytools import axis_slice, axis_reverse +from ._signaltools import lfiltic, lfilter +from ._spline import symiirorder1_ic + +__all__ = ['symiirorder1'] + + +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 + ---------- + input : ndarray + The input signal. + c0, z1 : scalar + Parameters in the transfer function. + precision : + Specifies the precision for calculating initial conditions + of the recursive filter based on mirror-symmetric input. + + Returns + ------- + output : ndarray + The filtered signal. + """ + if np.abs(z1) >= 1: + raise ValueError('|z1| must be less than 1.0') + + 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] + zi = lfiltic(b, a, y0) + + y1, _ = lfilter(b, a, axis_slice(signal, 1), zi=zi) + y1 = np.r_[y0, y1] + + # Compute backward symmetric condition and apply the system + # c0 / (1 - z1 * z) + b = np.asarray([c0]) + out_last = -c0 / (z1 - 1.0) * y1[-1] + + zi = lfiltic(b, a, np.atleast_1d(out_last)) + out, _ = lfilter(b, a, axis_slice(y1, -2, step=-1), zi=zi) + return np.r_[axis_reverse(out), out_last] diff --git a/scipy/signal/meson.build b/scipy/signal/meson.build index 6bacc340da86..344a7907f230 100644 --- a/scipy/signal/meson.build +++ b/scipy/signal/meson.build @@ -83,6 +83,7 @@ py3.install_sources([ '__init__.py', '_arraytools.py', '_bsplines.py', + '_splines.py', '_czt.py', '_filter_design.py', '_fir_filter_design.py', diff --git a/scipy/signal/spline.py b/scipy/signal/spline.py index cc15b2e5210e..5c7767d87831 100644 --- a/scipy/signal/spline.py +++ b/scipy/signal/spline.py @@ -7,7 +7,7 @@ from . import _spline __all__ = [ # noqa: F822 - 'cspline2d', 'qspline2d', 'sepfir2d', 'symiirorder1', 'symiirorder2'] + 'cspline2d', 'qspline2d', 'sepfir2d', 'symiirorder2'] def __dir__(): From e89094934317605a85ee9ed2f4e22c2958602c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Mon, 28 Aug 2023 17:20:44 -0500 Subject: [PATCH 017/500] Start moving symiir2 --- scipy/signal/_bspline_util.c.in | 101 +++++++++++++++++-- scipy/signal/_splinemodule.c | 169 +++++++++++++++++++++++++++----- scipy/signal/_splinemodule.h | 4 + scipy/signal/_splines.py | 65 +++++++++++- scipy/signal/spline.py | 2 +- 5 files changed, 307 insertions(+), 34 deletions(-) diff --git a/scipy/signal/_bspline_util.c.in b/scipy/signal/_bspline_util.c.in index cbb3c3946402..706f84ed1d1d 100644 --- a/scipy/signal/_bspline_util.c.in +++ b/scipy/signal/_bspline_util.c.in @@ -51,6 +51,11 @@ RTYPES = RTYPE + RTYPE }} +{{for SUB, TYP in zip(RNAME, RTYPE)}} +static {{TYP}} {{SUB}}_hc(int, {{TYP}}, double, double); +static {{TYP}} {{SUB}}_hs(int, {{TYP}}, double, double); +{{endfor}} + /* Implement the following difference equation */ /* y[n] = a1 * x[n] + a2 * y[n-1] */ @@ -275,6 +280,96 @@ int {{SUB}}_SYM_IIR1_initial({{TYP}} z1, {{TYP}} *x, {{TYP}} *yp0, {{endif}} {{endfor}} +{{for SUB, TYP in zip(RNAME, RTYPE)}} +int {{SUB}}_SYM_IIR2_initial_fwd(double r, double omega, + {{TYP}} *x, {{TYP}} *yp, int N, {{TYP}} precision) { + /* Fix starting values assuming mirror-symmetric boundary conditions. */ + {{TYP}} cs = 1 - 2 * r * cos(omega) + r * r; + {{TYP}} yp0 = {{SUB}}_hc(0, cs, r, omega) * x[0]; + int k = 0; + precision *= precision; + {{TYP}} *xptr = x; + int stridex = 1; + int stridey = 1; + + do { + yp[0] = yp0; + diff = {{SUB}}_hc(k+1, cs, r, omega); + yp0 += diff * (*xptr); + err = diff * diff; + xptr += stridex; + k++; + } while((err > precision) && (k < N)); + + if (k >= N) {return -3;} /* sum did not converge */ + yp[0] = yp0; + + yp1 = {{SUB}}_hc(0, cs, r, omega) * (*(x+stridex)); + yp1 += {{SUB}}_hc(1, cs, r, omega) * x[0]; + k = 0; + xptr = x; + do { + yp[1] = yp1; + diff = {{SUB}}_hc(k+2, cs, r, omega); + yp1 += diff * (*xptr); + err = diff * diff; + xptr += stridex; + k++; + } while((err > precision) && (k < N)); + + if (k >= N) {return -3;} /* sum did not converge */ + yp[1] = yp1; + + return 0; +} +{{endfor}} + +{{for SUB, TYP in zip(RNAME, RTYPE)}} +int {{SUB}}_SYM_IIR2_initial_bwd(double r, double omega, + {{TYP}} *x, {{TYP}} *yp, int N, {{TYP}} precision) { + double rsq = r * r; + {{TYP}} cs = 1 - 2 * r * cos(omega) + rsq; + + /* Fix starting values assuming mirror-symmetric boundary conditions. */ + {{TYP}} yp0 = 0.0; + int k = 0; + int stridex = 1; + int stridey = 1; + + // yptr = y + (N-1)*stridey; + {{TYP}} *yptr = yp + stridey; + {{TYP}} *xptr = x + (N - 1) * stridex; + do { + *yptr = yp0; + diff = ({{SUB}}_hs(k, cs, rsq, omega) + {{SUB}}_hs(k+1, cs, rsq, omega)); + yp0 += diff * (*xptr); + err = diff * diff; + xptr -= stridex; + k++; + } while((err > precision) && (k < N)); + + if (k >= N) {return -3;} /* sum did not converge */ + *yptr = yp0; + + yp1 = 0.0; + k = 0; + yptr -= stridey; /* Initialize in next-to-last slot in output array */ + xptr = x + (N - 1) * stridex; + do { + *yptr = yp1; + diff = ({{SUB}}_hs(k-1, cs, rsq, omega) + {{SUB}}_hs(k+2, cs, rsq, omega)); + yp1 += diff * (*xptr); + err = diff * diff; + xptr -= stridex; + k++; + } while((err > precision) && (k < N)); + + if (k >= N) {return -3;} /* sum did not converge */ + *yptr = yp1; + + return 0; +} +{{endfor}} /* h must be odd length */ /* strides in units of sizeof(DATA TYPE) bytes */ @@ -397,12 +492,6 @@ int {{SUB}}_separable_2Dconvolve_mirror({{TYP}} *in, {{TYP}} *out, {{endif}} {{endfor}} - -{{for SUB, TYP in zip(RNAME, RTYPE)}} -static {{TYP}} {{SUB}}_hc(int, {{TYP}}, double, double); -static {{TYP}} {{SUB}}_hs(int, {{TYP}}, double, double); -{{endfor}} - {{for SUB, TYP in zip(RNAME, RTYPE)}} {{TYP}} {{SUB}}_hc(int k, {{TYP}} cs, double r, double omega) { diff --git a/scipy/signal/_splinemodule.c b/scipy/signal/_splinemodule.c index 6b0ae35d059b..8d0f3daeccfb 100644 --- a/scipy/signal/_splinemodule.c +++ b/scipy/signal/_splinemodule.c @@ -427,16 +427,119 @@ static PyObject *IIRsymorder1_ic(PyObject *NPY_UNUSED(dummy), PyObject *args) } +static char doc_IIRsymorder2_ic_fwd[] = "out = symiirorder2_ic_fwd(input, r, omega, precision=-1.0)\n" +"\n" +" Compute the (forward) mirror-symmetric boundary conditions for a smoothing\n" +" IIR filter that is composed of cascaded second-order sections.\n" +"\n" +" The starting condition returned by this function is computed based on\n" +" the following transfer function::\n" +"\n" +" cs\n" +" H(z) = -------------------\n" +" (1 - a2/z - a3/z^2)\n" +"\n" +" where::\n" +"\n" +" a2 = (2 r cos omega)\n" +" a3 = - r^2\n" +" cs = 1 - 2 r cos omega + r^2\n" +"\n" +" Parameters\n" +" ----------\n" +" input : ndarray\n" +" The input signal.\n" +" r, omega : float\n" +" Parameters in the transfer function.\n" +" precision : float\n" +" Specifies the precision for calculating initial conditions\n" +" of the recursive filter based on mirror-symmetric input.\n" +"\n" +" Returns\n" +" -------\n" +" zi : ndarray\n" +" The mirror-symmetric initial condition for the forward IIR filter."; + +static PyObject *IIRsymorder2_ic_fwd(PyObject *NPY_UNUSED(dummy), PyObject *args) +{ + PyObject *sig=NULL; + PyArrayObject *a_sig=NULL, *out=NULL; + npy_intp* in_size; + double r, omega; + double precision = -1.0; + int thetype, N, ret; + npy_intp outstrides, instrides; + npy_intp N; + PyArray_Descr* dtype; + + if (!PyArg_ParseTuple(args, "Odd|d", &sig, &r, &omega, &precision)) + return NULL; + + thetype = PyArray_ObjectType(sig, NPY_FLOAT); + thetype = PyArray_MIN(thetype, NPY_DOUBLE); + a_sig = (PyArrayObject *)PyArray_FromObject(sig, thetype, 1, 1); + + if (a_sig == NULL) goto fail; + + dtype = PyArray_DescrFromType(thetype); + int sz = 2; + out = (PyArrayObject *)PyArray_Empty(1, &sz, dtype, 0); + if (out == NULL) goto fail; + + in_size = PyArray_DIMS(a_sig); + N = in_size[0]; + + switch (thetype) { + case NPY_FLOAT: + { + if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-6; + ret = S_SYM_IIR2_initial_fwd(r, omega, (float *)PyArray_DATA(a_sig), + (float *)PyArray_DATA(out), N, + (float )precision); + } + break; + case NPY_DOUBLE: + { + if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-11; + ret = S_SYM_IIR2_initial_fwd(r, omega, (double *)PyArray_DATA(a_sig), + (double *)PyArray_DATA(out), N, + precision); + } + break; + default: + PYERR("Incorrect type."); + } -static char doc_IIRsymorder2[] = "out = symiirorder2(input, r, omega, precision=-1.0)\n" + if (ret == 0) { + Py_DECREF(a_sig); + return PyArray_Return(out); + } + + if (ret == -1) PYERR("Could not allocate enough memory."); + if (ret == -2) PYERR("|z1| must be less than 1.0"); + if (ret == -3) PYERR("Sum to find symmetric boundary conditions did not converge."); + + PYERR("Unknown error."); + + + fail: + Py_XDECREF(a_sig); + Py_XDECREF(out); + return NULL; + +} + +static char doc_IIRsymorder2_ic_bwd[] = "out = symiirorder2_ic_bwd(input, r, omega, precision=-1.0)\n" "\n" -" Implement a smoothing IIR filter with mirror-symmetric boundary conditions\n" -" using a cascade of second-order sections. The second section uses a\n" -" reversed sequence. This implements the following transfer function::\n" +" Compute the (backward) mirror-symmetric boundary conditions for a smoothing\n" +" IIR filter that is composed of cascaded second-order sections.\n" "\n" -" cs^2\n" -" H(z) = ---------------------------------------\n" -" (1 - a2/z - a3/z^2) (1 - a2 z - a3 z^2 )\n" +" The starting condition returned by this function is computed based on\n" +" the following transfer function::\n" +"\n" +" cs\n" +" H(z) = -------------------\n" +" (1 - a2 z - a3 z^2)\n" "\n" " where::\n" "\n" @@ -456,17 +559,20 @@ static char doc_IIRsymorder2[] = "out = symiirorder2(input, r, omega, precision= "\n" " Returns\n" " -------\n" -" output : ndarray\n" -" The filtered signal."; +" zi : ndarray\n" +" The mirror-symmetric initial condition for the forward IIR filter."; -static PyObject *IIRsymorder2(PyObject *NPY_UNUSED(dummy), PyObject *args) +static PyObject *IIRsymorder2_ic_bwd(PyObject *NPY_UNUSED(dummy), PyObject *args) { PyObject *sig=NULL; PyArrayObject *a_sig=NULL, *out=NULL; + npy_intp* in_size; double r, omega; double precision = -1.0; int thetype, N, ret; npy_intp outstrides, instrides; + npy_intp N; + PyArray_Descr* dtype; if (!PyArg_ParseTuple(args, "Odd|d", &sig, &r, &omega, &precision)) return NULL; @@ -477,34 +583,46 @@ static PyObject *IIRsymorder2(PyObject *NPY_UNUSED(dummy), PyObject *args) if (a_sig == NULL) goto fail; - out = (PyArrayObject *)PyArray_SimpleNew(1, PyArray_DIMS(a_sig), thetype); + dtype = PyArray_DescrFromType(thetype); + int sz = 2; + out = (PyArrayObject *)PyArray_Empty(1, &sz, dtype, 0); if (out == NULL) goto fail; - N = PyArray_DIMS(a_sig)[0]; - convert_strides(PyArray_STRIDES(a_sig), &instrides, PyArray_ITEMSIZE(a_sig), 1); - outstrides = 1; + in_size = PyArray_DIMS(a_sig); + N = in_size[0]; switch (thetype) { case NPY_FLOAT: - if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-6; - ret = S_IIR_forback2 (r, omega, (float *)PyArray_DATA(a_sig), - (float *)PyArray_DATA(out), N, - instrides, outstrides, precision); + { + if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-6; + ret = S_SYM_IIR2_initial_bwd(r, omega, (float *)PyArray_DATA(a_sig), + (float *)PyArray_DATA(out), N, + (float )precision); + } break; case NPY_DOUBLE: - if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-11; - ret = D_IIR_forback2 (r, omega, (double *)PyArray_DATA(a_sig), - (double *)PyArray_DATA(out), N, - instrides, outstrides, precision); + { + if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-11; + ret = S_SYM_IIR2_initial_bwd(r, omega, (double *)PyArray_DATA(a_sig), + (double *)PyArray_DATA(out), N, + precision); + } break; default: PYERR("Incorrect type."); } - if (ret < 0) PYERR("Problem occurred inside routine."); + if (ret == 0) { + Py_DECREF(a_sig); + return PyArray_Return(out); + } + + if (ret == -1) PYERR("Could not allocate enough memory."); + if (ret == -2) PYERR("|z1| must be less than 1.0"); + if (ret == -3) PYERR("Sum to find symmetric boundary conditions did not converge."); + + PYERR("Unknown error."); - Py_DECREF(a_sig); - return PyArray_Return(out); fail: Py_XDECREF(a_sig); @@ -520,6 +638,7 @@ static struct PyMethodDef toolbox_module_methods[] = { {"sepfir2d", FIRsepsym2d, METH_VARARGS, doc_FIRsepsym2d}, {"symiirorder2", IIRsymorder2, METH_VARARGS, doc_IIRsymorder2}, {"symiirorder1_ic", IIRsymorder1_ic, METH_VARARGS, doc_IIRsymorder1_ic}, + {"symiirorder2_ic_fwd", IIRsymorder2_ic_fwd, METH_VARARGS, doc_IIRsymorder2_ic_fwd}, {NULL, NULL, 0, NULL} /* sentinel */ }; diff --git a/scipy/signal/_splinemodule.h b/scipy/signal/_splinemodule.h index 64250e6a3e44..f12cc1a2b67d 100644 --- a/scipy/signal/_splinemodule.h +++ b/scipy/signal/_splinemodule.h @@ -10,6 +10,8 @@ static void convert_strides(npy_intp*,npy_intp*,int,int); extern int S_cubic_spline2D(float*,float*,int,int,double,npy_intp*,npy_intp*,float); extern int S_quadratic_spline2D(float*,float*,int,int,double,npy_intp*,npy_intp*,float); extern int S_SYM_IIR1_initial(float, float*, float*, int, int, float); +extern int S_SYM_IIR2_initial_fwd(double, double, float*, float*, int, float); +extern int S_SYM_IIR2_initial_bwd(double, double, float*, float*, int, float); extern int S_IIR_forback1(float,float,float*,float*,int,int,int,float); extern int S_IIR_forback2(double,double,float*,float*,int,int,int,float); extern int S_separable_2Dconvolve_mirror(float*,float*,int,int,float*,float*,int,int,npy_intp*,npy_intp*); @@ -17,6 +19,8 @@ extern int S_separable_2Dconvolve_mirror(float*,float*,int,int,float*,float*,int extern int D_cubic_spline2D(double*,double*,int,int,double,npy_intp*,npy_intp*,double); extern int D_quadratic_spline2D(double*,double*,int,int,double,npy_intp*,npy_intp*,double); extern int D_SYM_IIR1_initial(double, double*, double*, int, int, double); +extern int D_SYM_IIR2_initial_fwd(double, double, double*, double*, int, double); +extern int D_SYM_IIR2_initial_bwd(double, double, double*, double*, int, double); extern int D_IIR_forback1(double,double,double*,double*,int,int,int,double); extern int D_IIR_forback2(double,double,double*,double*,int,int,int,double); extern int D_separable_2Dconvolve_mirror(double*,double*,int,int,double*,double*,int,int,npy_intp*,npy_intp*); diff --git a/scipy/signal/_splines.py b/scipy/signal/_splines.py index c5236b71fe72..8acae68171e7 100644 --- a/scipy/signal/_splines.py +++ b/scipy/signal/_splines.py @@ -2,8 +2,8 @@ import numpy as np from ._arraytools import axis_slice, axis_reverse -from ._signaltools import lfiltic, lfilter -from ._spline import symiirorder1_ic +from ._signaltools import lfiltic, lfilter, sosfilt +from ._spline import symiirorder1_ic, symiirorder2_ic_fwd, symiirorder2_ic_bwd __all__ = ['symiirorder1'] @@ -58,3 +58,64 @@ def symiirorder1(signal, c0, z1, precision=-1.0): zi = lfiltic(b, a, np.atleast_1d(out_last)) out, _ = lfilter(b, a, axis_slice(y1, -2, step=-1), zi=zi) return np.r_[axis_reverse(out), out_last] + + +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. + """ + if r >= 1.0: + raise ValueError('r must be less than 1.0') + + if not input.flags.c_contiguous: + input = input.copy() + + rsq = r * r + a2 = 2 * r * np.cos(omega) + a3 = -rsq + cs = np.atleast_1d(1 - 2 * r * np.cos(omega) + rsq) + + # 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) + b = cs + a = np.r_[1, -a2, -a3] + zi = lfiltic(b, a, ic_fwd) + sos = np.r_[cs, 0, 0, 1, -a2, -a3] + y_fwd, _ = sosfilt(sos, input[2:], zi=zi) + y_fwd = np.r_[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) + zi = lfiltic(b, a, ic_bwd) + y, _ = sosfilt(sos, axis_slice(y_fwd, -3, step=-1), zi=zi) + return np.r_[axis_reverse(y), ic_bwd] diff --git a/scipy/signal/spline.py b/scipy/signal/spline.py index 5c7767d87831..d45e7e51d80c 100644 --- a/scipy/signal/spline.py +++ b/scipy/signal/spline.py @@ -7,7 +7,7 @@ from . import _spline __all__ = [ # noqa: F822 - 'cspline2d', 'qspline2d', 'sepfir2d', 'symiirorder2'] + 'cspline2d', 'qspline2d', 'sepfir2d'] def __dir__(): From 74f607d97bdc47b68292ee07cc7a4a19a71c3859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Fri, 15 Mar 2024 19:20:29 -0500 Subject: [PATCH 018/500] FIX: Add missing datatypes, remove symiirorder functions from C module and fix initial state handling --- scipy/signal/__init__.py | 4 +--- scipy/signal/_bspline_util.c.in | 10 +++++++--- scipy/signal/_bsplines.py | 2 +- scipy/signal/_splinemodule.c | 18 ++++++++---------- scipy/signal/_splinemodule.h | 6 ------ scipy/signal/_splines.py | 22 ++++++++++++---------- 6 files changed, 29 insertions(+), 33 deletions(-) diff --git a/scipy/signal/__init__.py b/scipy/signal/__init__.py index c68ae7516b14..c9df277f23a2 100644 --- a/scipy/signal/__init__.py +++ b/scipy/signal/__init__.py @@ -312,9 +312,7 @@ from ._spline import ( cspline2d, qspline2d, - sepfir2d, - symiirorder1, - symiirorder2, + sepfir2d ) from ._splines import * diff --git a/scipy/signal/_bspline_util.c.in b/scipy/signal/_bspline_util.c.in index 706f84ed1d1d..fcabe92946b6 100644 --- a/scipy/signal/_bspline_util.c.in +++ b/scipy/signal/_bspline_util.c.in @@ -290,7 +290,8 @@ int {{SUB}}_SYM_IIR2_initial_fwd(double r, double omega, precision *= precision; {{TYP}} *xptr = x; int stridex = 1; - int stridey = 1; + {{TYP}} err; + {{TYP}} diff; do { yp[0] = yp0; @@ -304,7 +305,7 @@ int {{SUB}}_SYM_IIR2_initial_fwd(double r, double omega, if (k >= N) {return -3;} /* sum did not converge */ yp[0] = yp0; - yp1 = {{SUB}}_hc(0, cs, r, omega) * (*(x+stridex)); + {{TYP}} yp1 = {{SUB}}_hc(0, cs, r, omega) * (*(x+stridex)); yp1 += {{SUB}}_hc(1, cs, r, omega) * x[0]; k = 0; xptr = x; @@ -336,6 +337,9 @@ int {{SUB}}_SYM_IIR2_initial_bwd(double r, double omega, int stridex = 1; int stridey = 1; + {{TYP}} err; + {{TYP}} diff; + // yptr = y + (N-1)*stridey; {{TYP}} *yptr = yp + stridey; {{TYP}} *xptr = x + (N - 1) * stridex; @@ -351,7 +355,7 @@ int {{SUB}}_SYM_IIR2_initial_bwd(double r, double omega, if (k >= N) {return -3;} /* sum did not converge */ *yptr = yp0; - yp1 = 0.0; + {{TYP}} yp1 = 0.0; k = 0; yptr -= stridey; /* Initialize in next-to-last slot in output array */ xptr = x + (N - 1) * stridex; diff --git a/scipy/signal/_bsplines.py b/scipy/signal/_bsplines.py index ae5515a90a5c..c9b136da7c55 100644 --- a/scipy/signal/_bsplines.py +++ b/scipy/signal/_bsplines.py @@ -3,7 +3,7 @@ r_, atleast_1d, sqrt, exp, greater, cos, add, sin) # From splinemodule.c -from ._spline import cspline2d, sepfir2d, symiirorder1_ic +from ._spline import cspline2d, sepfir2d from ._signaltools import lfilter, sosfilt, lfiltic from scipy.interpolate import BSpline diff --git a/scipy/signal/_splinemodule.c b/scipy/signal/_splinemodule.c index 8d0f3daeccfb..fc46e68bea2f 100644 --- a/scipy/signal/_splinemodule.c +++ b/scipy/signal/_splinemodule.c @@ -338,7 +338,6 @@ static PyObject *IIRsymorder1_ic(PyObject *NPY_UNUSED(dummy), PyObject *args) double precision = -1.0; int thetype, ret; npy_intp M, N; - npy_intp outstrides, instrides; PyArray_Descr* dtype; if (!PyArg_ParseTuple(args, "OD|d", &sig, &z1, &precision)) @@ -467,8 +466,7 @@ static PyObject *IIRsymorder2_ic_fwd(PyObject *NPY_UNUSED(dummy), PyObject *args npy_intp* in_size; double r, omega; double precision = -1.0; - int thetype, N, ret; - npy_intp outstrides, instrides; + int thetype, ret; npy_intp N; PyArray_Descr* dtype; @@ -482,7 +480,7 @@ static PyObject *IIRsymorder2_ic_fwd(PyObject *NPY_UNUSED(dummy), PyObject *args if (a_sig == NULL) goto fail; dtype = PyArray_DescrFromType(thetype); - int sz = 2; + npy_intp sz = 2; out = (PyArrayObject *)PyArray_Empty(1, &sz, dtype, 0); if (out == NULL) goto fail; @@ -501,7 +499,7 @@ static PyObject *IIRsymorder2_ic_fwd(PyObject *NPY_UNUSED(dummy), PyObject *args case NPY_DOUBLE: { if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-11; - ret = S_SYM_IIR2_initial_fwd(r, omega, (double *)PyArray_DATA(a_sig), + ret = D_SYM_IIR2_initial_fwd(r, omega, (double *)PyArray_DATA(a_sig), (double *)PyArray_DATA(out), N, precision); } @@ -569,8 +567,7 @@ static PyObject *IIRsymorder2_ic_bwd(PyObject *NPY_UNUSED(dummy), PyObject *args npy_intp* in_size; double r, omega; double precision = -1.0; - int thetype, N, ret; - npy_intp outstrides, instrides; + int thetype, ret; npy_intp N; PyArray_Descr* dtype; @@ -584,7 +581,7 @@ static PyObject *IIRsymorder2_ic_bwd(PyObject *NPY_UNUSED(dummy), PyObject *args if (a_sig == NULL) goto fail; dtype = PyArray_DescrFromType(thetype); - int sz = 2; + npy_intp sz = 2; out = (PyArrayObject *)PyArray_Empty(1, &sz, dtype, 0); if (out == NULL) goto fail; @@ -603,7 +600,7 @@ static PyObject *IIRsymorder2_ic_bwd(PyObject *NPY_UNUSED(dummy), PyObject *args case NPY_DOUBLE: { if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-11; - ret = S_SYM_IIR2_initial_bwd(r, omega, (double *)PyArray_DATA(a_sig), + ret = D_SYM_IIR2_initial_bwd(r, omega, (double *)PyArray_DATA(a_sig), (double *)PyArray_DATA(out), N, precision); } @@ -636,9 +633,10 @@ static struct PyMethodDef toolbox_module_methods[] = { {"cspline2d", cspline2d, METH_VARARGS, doc_cspline2d}, {"qspline2d", qspline2d, METH_VARARGS, doc_qspline2d}, {"sepfir2d", FIRsepsym2d, METH_VARARGS, doc_FIRsepsym2d}, - {"symiirorder2", IIRsymorder2, METH_VARARGS, doc_IIRsymorder2}, + // {"symiirorder2", IIRsymorder2, METH_VARARGS, doc_IIRsymorder2}, {"symiirorder1_ic", IIRsymorder1_ic, METH_VARARGS, doc_IIRsymorder1_ic}, {"symiirorder2_ic_fwd", IIRsymorder2_ic_fwd, METH_VARARGS, doc_IIRsymorder2_ic_fwd}, + {"symiirorder2_ic_bwd", IIRsymorder2_ic_bwd, METH_VARARGS,doc_IIRsymorder2_ic_bwd }, {NULL, NULL, 0, NULL} /* sentinel */ }; diff --git a/scipy/signal/_splinemodule.h b/scipy/signal/_splinemodule.h index f12cc1a2b67d..637791679424 100644 --- a/scipy/signal/_splinemodule.h +++ b/scipy/signal/_splinemodule.h @@ -12,8 +12,6 @@ extern int S_quadratic_spline2D(float*,float*,int,int,double,npy_intp*,npy_intp* extern int S_SYM_IIR1_initial(float, float*, float*, int, int, float); extern int S_SYM_IIR2_initial_fwd(double, double, float*, float*, int, float); extern int S_SYM_IIR2_initial_bwd(double, double, float*, float*, int, float); -extern int S_IIR_forback1(float,float,float*,float*,int,int,int,float); -extern int S_IIR_forback2(double,double,float*,float*,int,int,int,float); extern int S_separable_2Dconvolve_mirror(float*,float*,int,int,float*,float*,int,int,npy_intp*,npy_intp*); extern int D_cubic_spline2D(double*,double*,int,int,double,npy_intp*,npy_intp*,double); @@ -21,15 +19,11 @@ extern int D_quadratic_spline2D(double*,double*,int,int,double,npy_intp*,npy_int extern int D_SYM_IIR1_initial(double, double*, double*, int, int, double); extern int D_SYM_IIR2_initial_fwd(double, double, double*, double*, int, double); extern int D_SYM_IIR2_initial_bwd(double, double, double*, double*, int, double); -extern int D_IIR_forback1(double,double,double*,double*,int,int,int,double); -extern int D_IIR_forback2(double,double,double*,double*,int,int,int,double); extern int D_separable_2Dconvolve_mirror(double*,double*,int,int,double*,double*,int,int,npy_intp*,npy_intp*); #ifdef __GNUC__ extern int C_SYM_IIR1_initial(__complex__ float, __complex__ float*, __complex__ float*, int, int, float); -extern int C_IIR_forback1(__complex__ float,__complex__ float,__complex__ float*,__complex__ float*,int,int,int,float); extern int C_separable_2Dconvolve_mirror(__complex__ float*,__complex__ float*,int,int,__complex__ float*,__complex__ float*,int,int,npy_intp*,npy_intp*); extern int Z_SYM_IIR1_initial(__complex__ double, __complex__ double*, __complex__ double*, int, int, double); -extern int Z_IIR_forback1(__complex__ double,__complex__ double,__complex__ double*,__complex__ double*,int,int,int,double); extern int Z_separable_2Dconvolve_mirror(__complex__ double*,__complex__ double*,int,int,__complex__ double*,__complex__ double*,int,int,npy_intp*,npy_intp*); #endif diff --git a/scipy/signal/_splines.py b/scipy/signal/_splines.py index 8acae68171e7..1f687fd9cfe4 100644 --- a/scipy/signal/_splines.py +++ b/scipy/signal/_splines.py @@ -5,7 +5,7 @@ from ._signaltools import lfiltic, lfilter, sosfilt from ._spline import symiirorder1_ic, symiirorder2_ic_fwd, symiirorder2_ic_bwd -__all__ = ['symiirorder1'] +__all__ = ['symiirorder1', 'symiirorder2'] def symiirorder1(signal, c0, z1, precision=-1.0): @@ -45,6 +45,8 @@ def symiirorder1(signal, c0, z1, precision=-1.0): # 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) + zi = lfiltic(b, a, y0) y1, _ = lfilter(b, a, axis_slice(signal, 1), zi=zi) @@ -52,10 +54,10 @@ def symiirorder1(signal, c0, z1, precision=-1.0): # Compute backward symmetric condition and apply the system # c0 / (1 - z1 * z) - b = np.asarray([c0]) + b = np.asarray([c0], dtype=signal.dtype) out_last = -c0 / (z1 - 1.0) * y1[-1] - zi = lfiltic(b, a, np.atleast_1d(out_last)) + zi = lfiltic(b, a, np.atleast_1d(out_last).astype(signal.dtype)) out, _ = lfilter(b, a, axis_slice(y1, -2, step=-1), zi=zi) return np.r_[axis_reverse(out), out_last] @@ -105,17 +107,17 @@ def symiirorder2(input, r, omega, precision=-1.0): ic_fwd = symiirorder2_ic_fwd(input, r, omega, precision) # Apply first the system cs / (1 - a2 * z^-1 - a3 * z^-2) - b = cs - a = np.r_[1, -a2, -a3] - zi = lfiltic(b, a, ic_fwd) - sos = np.r_[cs, 0, 0, 1, -a2, -a3] - y_fwd, _ = sosfilt(sos, input[2:], zi=zi) + b = cs.astype(input.dtype) + a = np.r_[1, -a2, -a3].astype(input.dtype) + zi = lfiltic(b, a, ic_fwd[::-1]) + sos = np.atleast_2d(np.r_[cs, 0, 0, 1, -a2, -a3]).astype(input.dtype) + y_fwd, _ = sosfilt(sos, input[2:], zi=np.atleast_2d(zi)) + # Then compute the symmetric backward starting conditions y_fwd = np.r_[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) zi = lfiltic(b, a, ic_bwd) - y, _ = sosfilt(sos, axis_slice(y_fwd, -3, step=-1), zi=zi) + y, _ = sosfilt(sos, axis_slice(y_fwd, -3, step=-1), zi=np.atleast_2d(zi)) return np.r_[axis_reverse(y), ic_bwd] From ab7eb44b9e2902edb55a429b831ee0d9691a6804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Mon, 18 Mar 2024 20:02:10 -0500 Subject: [PATCH 019/500] ENH: Move qspline2d to use symiirorder1 --- scipy/signal/__init__.py | 1 - scipy/signal/_bspline_util.c.in | 60 --------------------- scipy/signal/_bsplines.py | 93 +++++++++++++++++++++++++++++++-- scipy/signal/_splinemodule.c | 84 +---------------------------- scipy/signal/_splinemodule.h | 2 - 5 files changed, 91 insertions(+), 149 deletions(-) diff --git a/scipy/signal/__init__.py b/scipy/signal/__init__.py index c9df277f23a2..00213417bf14 100644 --- a/scipy/signal/__init__.py +++ b/scipy/signal/__init__.py @@ -311,7 +311,6 @@ from ._spline import ( cspline2d, - qspline2d, sepfir2d ) diff --git a/scipy/signal/_bspline_util.c.in b/scipy/signal/_bspline_util.c.in index fcabe92946b6..1141d91f1159 100644 --- a/scipy/signal/_bspline_util.c.in +++ b/scipy/signal/_bspline_util.c.in @@ -754,63 +754,3 @@ int {{SUB}}_cubic_spline2D({{TYP}} *image, {{TYP}} *coeffs, int M, int N, double } {{endfor}} - -/* Find the quadratic spline coefficients of an image - image is M rows by N columns stored rowise in memory (vary column number - first). It will be replaced with the spline coefficients. - lambda is a smoothing parameter (lambda = 100 approximately corresponds - to a cutoff frequency of 0.1*(sample freq)) - must be zero for now. - strides is an integer array [rowstride, colstride] - telling how much memory in units of sizeof(DATA TYPE) bytes to skip - to get to the next element. -*/ - -/* to get the (smoothed) image back mirror-symmetric convolve with a length - three separable FIR filter [1.0, 6.0, 1.0]/ 8.0 -*/ - -{{for SUB, TYP in zip(RNAME, RTYPE)}} -int {{SUB}}_quadratic_spline2D({{TYP}} *image, {{TYP}} *coeffs, int M, int N, double lambda, - npy_intp *strides, npy_intp *cstrides, {{TYP}} precision) { - double r; - {{TYP}} *inptr; - {{TYP}} *coptr; - {{TYP}} *tmpmem; - {{TYP}} *tptr; - int m,n, retval=0; - - if (lambda > 0) return -2; - - tmpmem = malloc(N*M*sizeof({{TYP}})); - if (tmpmem == NULL) return -1; - - /* normal quadratic spline */ - r = -3 + 2*sqrt(2.0); - - /* Loop over rows */ - inptr = image; - tptr = tmpmem; - for (m = 0; m < M; m++) { - retval = {{SUB}}_IIR_forback1 (-r*8.0, r, inptr, tptr, N, strides[1], 1, precision); - if (retval < 0) break; - inptr += strides[0]; - tptr += N; - } - - if (retval >=0) { - /* Loop over columns */ - tptr = tmpmem; - coptr = coeffs; - for (n = 0; n < N; n++) { - retval = {{SUB}}_IIR_forback1 (-r*8.0, r, tptr, coptr, M, N, cstrides[0], precision); - if (retval < 0) break; - coptr += cstrides[1]; - tptr += 1; - } - } - free(tmpmem); - return retval; -} - -{{endfor}} diff --git a/scipy/signal/_bsplines.py b/scipy/signal/_bsplines.py index c9b136da7c55..9c05c089b07e 100644 --- a/scipy/signal/_bsplines.py +++ b/scipy/signal/_bsplines.py @@ -1,15 +1,20 @@ from numpy import (asarray, pi, zeros_like, array, arctan2, tan, ones, arange, floor, - r_, atleast_1d, sqrt, exp, greater, cos, add, sin) + r_, c_, atleast_1d, sqrt, exp, greater, cos, add, sin, + sqrt, moveaxis, abs) + +from scipy._lib._util import normalize_axis_index # From splinemodule.c -from ._spline import cspline2d, sepfir2d +from ._spline import cspline2d, sepfir2d, symiirorder1_ic +from ._arraytools import axis_slice, axis_reverse from ._signaltools import lfilter, sosfilt, lfiltic from scipy.interpolate import BSpline __all__ = ['spline_filter', 'gauss_spline', - 'cspline1d', 'qspline1d', 'cspline1d_eval', 'qspline1d_eval'] + 'cspline1d', 'qspline1d', 'qspline2d', + 'cspline1d_eval', 'qspline1d_eval'] def spline_filter(Iin, lmbda=5.0): @@ -369,6 +374,88 @@ def qspline1d(signal, lamb=0.0): return _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 _symiirorder1_nd(input, c0, z1, precision=-1.0, axis=-1): + 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) + + if abs(z1) >= 1: + raise ValueError('|z1| must be less than 1.0') + + zi = symiirorder1_ic(input, z1, precision) + + # Apply first the system 1 / (1 - z1 * z^-1) + b = ones(1, dtype=input.dtype) + a = r_[1, -z1] + zii = zi[:, None] * z1 + + y1, _ = lfilter(b, a, axis_slice(input, 1), zi=zii) + y1 = c_[zi, y1] + + # Compute backward symmetric condition and apply the system + # c0 / (1 - z1 * z) + zi = -c0 / (z1 - 1.0) * axis_slice(y1, -1) + zii = zi * z1 + + b = asarray([c0], dtype=input.dtype) + out, _ = lfilter(b, a, axis_slice(y1, -2, step=-1), zi=zii) + out = c_[out[:, ::-1], zi] + + 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 + ---------- + input : 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 lamb > 0: + raise ValueError('lambda must be negative or zero') + + # normal quadratic spline + r = -3 + 2 * sqrt(2.0) + c0 = -r * 8.0 + z1 = r + + out = _symiirorder1_nd(signal, c0, z1, precision, axis=-1) + out = _symiirorder1_nd(out, c0, z1, precision, axis=0) + return out + + def cspline1d_eval(cj, newx, dx=1.0, x0=0): """Evaluate a cubic spline at the new set of points. diff --git a/scipy/signal/_splinemodule.c b/scipy/signal/_splinemodule.c index fc46e68bea2f..6b667b7b5a48 100644 --- a/scipy/signal/_splinemodule.c +++ b/scipy/signal/_splinemodule.c @@ -98,86 +98,6 @@ static PyObject *cspline2d(PyObject *NPY_UNUSED(dummy), PyObject *args) } -static char doc_qspline2d[] = "out = qspline2d(input, lambda=0.0, precision=-1.0)\n" -"\n" -" Coefficients for 2-D quadratic (2nd order) B-spline:\n" -"\n" -" Return the second-order B-spline coefficients over a regularly spaced\n" -" input grid for the two-dimensional input image.\n" -"\n" -" Parameters\n" -" ----------\n" -" input : ndarray\n" -" The input signal.\n" -" lambda : float\n" -" Specifies the amount of smoothing in the transfer function.\n" -" precision : float\n" -" Specifies the precision for computing the infinite sum needed to apply mirror-\n" -" symmetric boundary conditions.\n" -"\n" -" Returns\n" -" -------\n" -" output : ndarray\n" -" The filtered signal.\n" -"\n" -" Examples\n" -" --------\n" -" Examples are given :ref:`in the tutorial `.\n" -"\n"; - -static PyObject *qspline2d(PyObject *NPY_UNUSED(dummy), PyObject *args) -{ - PyObject *image=NULL; - PyArrayObject *a_image=NULL, *ck=NULL; - double lambda = 0.0; - double precision = -1.0; - int thetype, M, N, retval=0; - npy_intp outstrides[2], instrides[2]; - - if (!PyArg_ParseTuple(args, "O|dd", &image, &lambda, &precision)) return NULL; - - if (lambda != 0.0) PYERR("Smoothing spline not yet implemented."); - - thetype = PyArray_ObjectType(image, NPY_FLOAT); - thetype = PyArray_MIN(thetype, NPY_DOUBLE); - a_image = (PyArrayObject *)PyArray_FromObject(image, thetype, 2, 2); - if (a_image == NULL) goto fail; - - ck = (PyArrayObject *)PyArray_SimpleNew(2, PyArray_DIMS(a_image), thetype); - if (ck == NULL) goto fail; - M = PyArray_DIMS(a_image)[0]; - N = PyArray_DIMS(a_image)[1]; - - convert_strides(PyArray_STRIDES(a_image), instrides, PyArray_ITEMSIZE(a_image), 2); - outstrides[0] = N; - outstrides[1] = 1; - - if (thetype == NPY_FLOAT) { - if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-3; - retval = S_quadratic_spline2D((float *)PyArray_DATA(a_image), - (float *)PyArray_DATA(ck), - M, N, lambda, instrides, outstrides, precision); - } - else if (thetype == NPY_DOUBLE) { - if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-6; - retval = D_quadratic_spline2D((double *)PyArray_DATA(a_image), - (double *)PyArray_DATA(ck), - M, N, lambda, instrides, outstrides, precision); - } - - if (retval == -3) PYERR("Precision too high. Error did not converge."); - if (retval < 0) PYERR("Problem occurred inside routine"); - - Py_DECREF(a_image); - return PyArray_Return(ck); - - fail: - Py_XDECREF(a_image); - Py_XDECREF(ck); - return NULL; - -} - static char doc_FIRsepsym2d[] = "out = sepfir2d(input, hrow, hcol)\n" "\n" " Convolve with a 2-D separable FIR filter.\n" @@ -345,7 +265,7 @@ static PyObject *IIRsymorder1_ic(PyObject *NPY_UNUSED(dummy), PyObject *args) thetype = PyArray_ObjectType(sig, NPY_FLOAT); thetype = PyArray_MIN(thetype, NPY_CDOUBLE); - a_sig = (PyArrayObject *)PyArray_FromObject(sig, thetype, 1, 1); + a_sig = (PyArrayObject *)PyArray_FromObject(sig, thetype, 1, 2); if (a_sig == NULL) goto fail; @@ -631,9 +551,7 @@ static PyObject *IIRsymorder2_ic_bwd(PyObject *NPY_UNUSED(dummy), PyObject *args static struct PyMethodDef toolbox_module_methods[] = { {"cspline2d", cspline2d, METH_VARARGS, doc_cspline2d}, - {"qspline2d", qspline2d, METH_VARARGS, doc_qspline2d}, {"sepfir2d", FIRsepsym2d, METH_VARARGS, doc_FIRsepsym2d}, - // {"symiirorder2", IIRsymorder2, METH_VARARGS, doc_IIRsymorder2}, {"symiirorder1_ic", IIRsymorder1_ic, METH_VARARGS, doc_IIRsymorder1_ic}, {"symiirorder2_ic_fwd", IIRsymorder2_ic_fwd, METH_VARARGS, doc_IIRsymorder2_ic_fwd}, {"symiirorder2_ic_bwd", IIRsymorder2_ic_bwd, METH_VARARGS,doc_IIRsymorder2_ic_bwd }, diff --git a/scipy/signal/_splinemodule.h b/scipy/signal/_splinemodule.h index 637791679424..e0a14e6d6337 100644 --- a/scipy/signal/_splinemodule.h +++ b/scipy/signal/_splinemodule.h @@ -8,14 +8,12 @@ static void convert_strides(npy_intp*,npy_intp*,int,int); extern int S_cubic_spline2D(float*,float*,int,int,double,npy_intp*,npy_intp*,float); -extern int S_quadratic_spline2D(float*,float*,int,int,double,npy_intp*,npy_intp*,float); extern int S_SYM_IIR1_initial(float, float*, float*, int, int, float); extern int S_SYM_IIR2_initial_fwd(double, double, float*, float*, int, float); extern int S_SYM_IIR2_initial_bwd(double, double, float*, float*, int, float); extern int S_separable_2Dconvolve_mirror(float*,float*,int,int,float*,float*,int,int,npy_intp*,npy_intp*); extern int D_cubic_spline2D(double*,double*,int,int,double,npy_intp*,npy_intp*,double); -extern int D_quadratic_spline2D(double*,double*,int,int,double,npy_intp*,npy_intp*,double); extern int D_SYM_IIR1_initial(double, double*, double*, int, int, double); extern int D_SYM_IIR2_initial_fwd(double, double, double*, double*, int, double); extern int D_SYM_IIR2_initial_bwd(double, double, double*, double*, int, double); From 9f840831a684751619624959cd2c62e4ea94a216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 19 Mar 2024 20:49:11 -0500 Subject: [PATCH 020/500] ENH: Extend symiirorder1/2 to handle 2D inputs --- scipy/signal/_bspline_util.c.in | 71 ++++++++++++++------------------- scipy/signal/_bsplines.py | 23 +---------- scipy/signal/_splinemodule.c | 51 ++++++++++++++--------- scipy/signal/_splinemodule.h | 8 ++-- scipy/signal/_splines.py | 69 +++++++++++++++++++++++--------- 5 files changed, 118 insertions(+), 104 deletions(-) diff --git a/scipy/signal/_bspline_util.c.in b/scipy/signal/_bspline_util.c.in index 1141d91f1159..8cc4980069a8 100644 --- a/scipy/signal/_bspline_util.c.in +++ b/scipy/signal/_bspline_util.c.in @@ -240,39 +240,35 @@ int {{SUB}}_IIR_forback1({{TYP}} c0, {{TYP}} z1, {{TYP}} *x, {{TYP}} *y, int {{SUB}}_SYM_IIR1_initial({{TYP}} z1, {{TYP}} *x, {{TYP}} *yp0, int M, int N, {{RTYP}} precision) { - {{TYP}} *cur_yp0 = NULL; {{TYP}} powz1, diff; {{RTYP}} err; int k; if (ABSQ(z1) >= 1.0) return -2; /* z1 not less than 1 */ - if ((cur_yp0 = malloc(M*sizeof({{TYP}})))==NULL) return -1; - /* Fix starting value assuming mirror-symmetric boundary conditions. */ for(int i = 0; i < M; i++) { - cur_yp0[i] = x[N * i]; + yp0[i] = x[N * i]; } powz1 = 1.0; k = 0; precision *= precision; do { - memcpy(yp0, cur_yp0, M * sizeof({{TYP}})); powz1 *= z1; for(int i = 0; i < M; i++) { - cur_yp0[i] += powz1 * x[N * i + k]; + yp0[i] += powz1 * x[N * i + k]; } diff = powz1; err = ABSQ(diff); k++; } while((err > precision) && (k < N)); + if (k >= N){ /* sum did not converge */ return -3; } - memcpy(yp0, cur_yp0, M * sizeof({{TYP}})); - free(cur_yp0); + return 0; } {{if SUB in CNAME}} @@ -282,94 +278,85 @@ int {{SUB}}_SYM_IIR1_initial({{TYP}} z1, {{TYP}} *x, {{TYP}} *yp0, {{for SUB, TYP in zip(RNAME, RTYPE)}} int {{SUB}}_SYM_IIR2_initial_fwd(double r, double omega, - {{TYP}} *x, {{TYP}} *yp, int N, {{TYP}} precision) { + {{TYP}} *x, {{TYP}} *yp, int M, int N, {{TYP}} precision) { /* Fix starting values assuming mirror-symmetric boundary conditions. */ {{TYP}} cs = 1 - 2 * r * cos(omega) + r * r; - {{TYP}} yp0 = {{SUB}}_hc(0, cs, r, omega) * x[0]; + + for(int i = 0; i < M; i++) { + yp[2 * i] = {{SUB}}_hc(0, cs, r, omega) * x[N * i]; + } + int k = 0; precision *= precision; - {{TYP}} *xptr = x; - int stridex = 1; + {{TYP}} err; {{TYP}} diff; do { - yp[0] = yp0; diff = {{SUB}}_hc(k+1, cs, r, omega); - yp0 += diff * (*xptr); + for(int i = 0; i < M; i++) { + yp[2 * i] += diff * x[N * i + k]; + } err = diff * diff; - xptr += stridex; k++; } while((err > precision) && (k < N)); if (k >= N) {return -3;} /* sum did not converge */ - yp[0] = yp0; - {{TYP}} yp1 = {{SUB}}_hc(0, cs, r, omega) * (*(x+stridex)); - yp1 += {{SUB}}_hc(1, cs, r, omega) * x[0]; + for(int i = 0; i < M; i++) { + yp[2 * i + 1] = {{SUB}}_hc(0, cs, r, omega) * x[N * i + 1]; + yp[2 * i + 1] += {{SUB}}_hc(1, cs, r, omega) * x[N * i]; + } + k = 0; - xptr = x; do { - yp[1] = yp1; diff = {{SUB}}_hc(k+2, cs, r, omega); - yp1 += diff * (*xptr); + for(int i = 0; i < M; i++) { + yp[2 * i + 1] += diff * x[N * i + k]; + } err = diff * diff; - xptr += stridex; k++; } while((err > precision) && (k < N)); if (k >= N) {return -3;} /* sum did not converge */ - yp[1] = yp1; - return 0; } {{endfor}} {{for SUB, TYP in zip(RNAME, RTYPE)}} int {{SUB}}_SYM_IIR2_initial_bwd(double r, double omega, - {{TYP}} *x, {{TYP}} *yp, int N, {{TYP}} precision) { + {{TYP}} *x, {{TYP}} *yp, int M, int N, {{TYP}} precision) { double rsq = r * r; {{TYP}} cs = 1 - 2 * r * cos(omega) + rsq; /* Fix starting values assuming mirror-symmetric boundary conditions. */ - {{TYP}} yp0 = 0.0; int k = 0; - int stridex = 1; - int stridey = 1; {{TYP}} err; {{TYP}} diff; - // yptr = y + (N-1)*stridey; - {{TYP}} *yptr = yp + stridey; - {{TYP}} *xptr = x + (N - 1) * stridex; do { - *yptr = yp0; diff = ({{SUB}}_hs(k, cs, rsq, omega) + {{SUB}}_hs(k+1, cs, rsq, omega)); - yp0 += diff * (*xptr); + for(int i = 0; i < M; i++) { + yp[2 * i] += diff * x[N * i + N - 1 - k]; + } err = diff * diff; - xptr -= stridex; k++; } while((err > precision) && (k < N)); if (k >= N) {return -3;} /* sum did not converge */ - *yptr = yp0; - {{TYP}} yp1 = 0.0; k = 0; - yptr -= stridey; /* Initialize in next-to-last slot in output array */ - xptr = x + (N - 1) * stridex; do { - *yptr = yp1; diff = ({{SUB}}_hs(k-1, cs, rsq, omega) + {{SUB}}_hs(k+2, cs, rsq, omega)); - yp1 += diff * (*xptr); + for(int i = 0; i < M; i++) { + yp[2 * i + 1] += diff * x[N * i + N - 1 - k]; + } err = diff * diff; - xptr -= stridex; k++; } while((err > precision) && (k < N)); if (k >= N) {return -3;} /* sum did not converge */ - *yptr = yp1; return 0; } diff --git a/scipy/signal/_bsplines.py b/scipy/signal/_bsplines.py index 9c05c089b07e..613804dfefb9 100644 --- a/scipy/signal/_bsplines.py +++ b/scipy/signal/_bsplines.py @@ -7,6 +7,7 @@ # From splinemodule.c from ._spline import cspline2d, sepfir2d, symiirorder1_ic +from ._splines import symiirorder1 from ._arraytools import axis_slice, axis_reverse from ._signaltools import lfilter, sosfilt, lfiltic @@ -390,27 +391,7 @@ def _symiirorder1_nd(input, c0, z1, precision=-1.0, axis=-1): if input.ndim > 1: input, input_shape = collapse_2d(input, axis) - if abs(z1) >= 1: - raise ValueError('|z1| must be less than 1.0') - - zi = symiirorder1_ic(input, z1, precision) - - # Apply first the system 1 / (1 - z1 * z^-1) - b = ones(1, dtype=input.dtype) - a = r_[1, -z1] - zii = zi[:, None] * z1 - - y1, _ = lfilter(b, a, axis_slice(input, 1), zi=zii) - y1 = c_[zi, y1] - - # Compute backward symmetric condition and apply the system - # c0 / (1 - z1 * z) - zi = -c0 / (z1 - 1.0) * axis_slice(y1, -1) - zii = zi * z1 - - b = asarray([c0], dtype=input.dtype) - out, _ = lfilter(b, a, axis_slice(y1, -2, step=-1), zi=zii) - out = c_[out[:, ::-1], zi] + out = symiirorder1(input, c0, z1, precision=precision) if input_ndim > 1: out = out.reshape(input_shape) diff --git a/scipy/signal/_splinemodule.c b/scipy/signal/_splinemodule.c index 6b667b7b5a48..b8286213d959 100644 --- a/scipy/signal/_splinemodule.c +++ b/scipy/signal/_splinemodule.c @@ -278,8 +278,9 @@ static PyObject *IIRsymorder1_ic(PyObject *NPY_UNUSED(dummy), PyObject *args) N = in_size[1]; } + const npy_intp sz[2] = {M, 1}; dtype = PyArray_DescrFromType(thetype); - out = (PyArrayObject *)PyArray_Empty(1, &M, dtype, 0); + out = (PyArrayObject *)PyArray_Empty(2, sz, dtype, 0); if (out == NULL) goto fail; switch (thetype) { @@ -387,7 +388,7 @@ static PyObject *IIRsymorder2_ic_fwd(PyObject *NPY_UNUSED(dummy), PyObject *args double r, omega; double precision = -1.0; int thetype, ret; - npy_intp N; + npy_intp N, M; PyArray_Descr* dtype; if (!PyArg_ParseTuple(args, "Odd|d", &sig, &r, &omega, &precision)) @@ -395,24 +396,30 @@ static PyObject *IIRsymorder2_ic_fwd(PyObject *NPY_UNUSED(dummy), PyObject *args thetype = PyArray_ObjectType(sig, NPY_FLOAT); thetype = PyArray_MIN(thetype, NPY_DOUBLE); - a_sig = (PyArrayObject *)PyArray_FromObject(sig, thetype, 1, 1); + a_sig = (PyArrayObject *)PyArray_FromObject(sig, thetype, 1, 2); if (a_sig == NULL) goto fail; - dtype = PyArray_DescrFromType(thetype); - npy_intp sz = 2; - out = (PyArrayObject *)PyArray_Empty(1, &sz, dtype, 0); - if (out == NULL) goto fail; - in_size = PyArray_DIMS(a_sig); + M = 1; N = in_size[0]; + if(PyArray_NDIM(a_sig) > 1) { + M = in_size[0]; + N = in_size[1]; + } + + dtype = PyArray_DescrFromType(thetype); + const npy_intp sz[2] = {M, 2}; + out = (PyArrayObject *)PyArray_Empty(2, sz, dtype, 0); + if (out == NULL) goto fail; + switch (thetype) { case NPY_FLOAT: { if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-6; ret = S_SYM_IIR2_initial_fwd(r, omega, (float *)PyArray_DATA(a_sig), - (float *)PyArray_DATA(out), N, + (float *)PyArray_DATA(out), M, N, (float )precision); } break; @@ -420,7 +427,7 @@ static PyObject *IIRsymorder2_ic_fwd(PyObject *NPY_UNUSED(dummy), PyObject *args { if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-11; ret = D_SYM_IIR2_initial_fwd(r, omega, (double *)PyArray_DATA(a_sig), - (double *)PyArray_DATA(out), N, + (double *)PyArray_DATA(out), M, N, precision); } break; @@ -488,7 +495,7 @@ static PyObject *IIRsymorder2_ic_bwd(PyObject *NPY_UNUSED(dummy), PyObject *args double r, omega; double precision = -1.0; int thetype, ret; - npy_intp N; + npy_intp M, N; PyArray_Descr* dtype; if (!PyArg_ParseTuple(args, "Odd|d", &sig, &r, &omega, &precision)) @@ -496,24 +503,30 @@ static PyObject *IIRsymorder2_ic_bwd(PyObject *NPY_UNUSED(dummy), PyObject *args thetype = PyArray_ObjectType(sig, NPY_FLOAT); thetype = PyArray_MIN(thetype, NPY_DOUBLE); - a_sig = (PyArrayObject *)PyArray_FromObject(sig, thetype, 1, 1); + a_sig = (PyArrayObject *)PyArray_FromObject(sig, thetype, 1, 2); if (a_sig == NULL) goto fail; - dtype = PyArray_DescrFromType(thetype); - npy_intp sz = 2; - out = (PyArrayObject *)PyArray_Empty(1, &sz, dtype, 0); - if (out == NULL) goto fail; - in_size = PyArray_DIMS(a_sig); + M = 1; N = in_size[0]; + if(PyArray_NDIM(a_sig) > 1) { + M = in_size[0]; + N = in_size[1]; + } + + dtype = PyArray_DescrFromType(thetype); + const npy_intp sz[2] = {M, 2}; + out = (PyArrayObject *)PyArray_Zeros(2, sz, dtype, 0); + if (out == NULL) goto fail; + switch (thetype) { case NPY_FLOAT: { if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-6; ret = S_SYM_IIR2_initial_bwd(r, omega, (float *)PyArray_DATA(a_sig), - (float *)PyArray_DATA(out), N, + (float *)PyArray_DATA(out), M, N, (float )precision); } break; @@ -521,7 +534,7 @@ static PyObject *IIRsymorder2_ic_bwd(PyObject *NPY_UNUSED(dummy), PyObject *args { if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-11; ret = D_SYM_IIR2_initial_bwd(r, omega, (double *)PyArray_DATA(a_sig), - (double *)PyArray_DATA(out), N, + (double *)PyArray_DATA(out), M, N, precision); } break; diff --git a/scipy/signal/_splinemodule.h b/scipy/signal/_splinemodule.h index e0a14e6d6337..ead5fa7ae00e 100644 --- a/scipy/signal/_splinemodule.h +++ b/scipy/signal/_splinemodule.h @@ -9,14 +9,14 @@ static void convert_strides(npy_intp*,npy_intp*,int,int); extern int S_cubic_spline2D(float*,float*,int,int,double,npy_intp*,npy_intp*,float); extern int S_SYM_IIR1_initial(float, float*, float*, int, int, float); -extern int S_SYM_IIR2_initial_fwd(double, double, float*, float*, int, float); -extern int S_SYM_IIR2_initial_bwd(double, double, float*, float*, int, float); +extern int S_SYM_IIR2_initial_fwd(double, double, float*, float*, int, int, float); +extern int S_SYM_IIR2_initial_bwd(double, double, float*, float*, int, int, float); extern int S_separable_2Dconvolve_mirror(float*,float*,int,int,float*,float*,int,int,npy_intp*,npy_intp*); extern int D_cubic_spline2D(double*,double*,int,int,double,npy_intp*,npy_intp*,double); extern int D_SYM_IIR1_initial(double, double*, double*, int, int, double); -extern int D_SYM_IIR2_initial_fwd(double, double, double*, double*, int, double); -extern int D_SYM_IIR2_initial_bwd(double, double, double*, double*, int, double); +extern int D_SYM_IIR2_initial_fwd(double, double, double*, double*, int, int, double); +extern int D_SYM_IIR2_initial_bwd(double, double, double*, double*, int, int, double); extern int D_separable_2Dconvolve_mirror(double*,double*,int,int,double*,double*,int,int,npy_intp*,npy_intp*); #ifdef __GNUC__ diff --git a/scipy/signal/_splines.py b/scipy/signal/_splines.py index 1f687fd9cfe4..71cd3d022745 100644 --- a/scipy/signal/_splines.py +++ b/scipy/signal/_splines.py @@ -25,7 +25,8 @@ def symiirorder1(signal, c0, z1, precision=-1.0): Parameters ---------- input : ndarray - The input signal. + 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 : @@ -40,6 +41,14 @@ def symiirorder1(signal, c0, z1, precision=-1.0): if np.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) @@ -47,19 +56,27 @@ def symiirorder1(signal, c0, z1, precision=-1.0): a = np.r_[1, -z1] a = a.astype(signal.dtype) - zi = lfiltic(b, a, y0) + # Compute the initial state for lfilter. + zii = y0 * z1 - y1, _ = lfilter(b, a, axis_slice(signal, 1), zi=zi) - y1 = np.r_[y0, y1] + 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) * y1[-1] + out_last = -c0 / (z1 - 1.0) * axis_slice(y1, -1) + + # Compute the initial state for lfilter. + zii = out_last * z1 - zi = lfiltic(b, a, np.atleast_1d(out_last).astype(signal.dtype)) - out, _ = lfilter(b, a, axis_slice(y1, -2, step=-1), zi=zi) - return np.r_[axis_reverse(out), out_last] + 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 out def symiirorder2(input, r, omega, precision=-1.0): @@ -95,29 +112,45 @@ def symiirorder2(input, r, omega, precision=-1.0): 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') + if not input.flags.c_contiguous: input = input.copy() + squeeze_dim = False + if input.ndim == 1: + input = input[None, :] + squeeze_dim = True + rsq = r * r a2 = 2 * r * np.cos(omega) a3 = -rsq cs = np.atleast_1d(1 - 2 * r * np.cos(omega) + rsq) + sos = np.atleast_2d(np.r_[cs, 0, 0, 1, -a2, -a3]).astype(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) - b = cs.astype(input.dtype) - a = np.r_[1, -a2, -a3].astype(input.dtype) - zi = lfiltic(b, a, ic_fwd[::-1]) - sos = np.atleast_2d(np.r_[cs, 0, 0, 1, -a2, -a3]).astype(input.dtype) - y_fwd, _ = sosfilt(sos, input[2:], zi=np.atleast_2d(zi)) - # Then compute the symmetric backward starting conditions - y_fwd = np.r_[ic_fwd, y_fwd] + # Compute the initial conditions in the form expected by sosfilt + # coef = np.asarray([[a3, a2], [0, a3]], dtype=input.dtype) + coef = np.r_[a3, a2, 0, a3].reshape(2, 2).astype(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) - zi = lfiltic(b, a, ic_bwd) - y, _ = sosfilt(sos, axis_slice(y_fwd, -3, step=-1), zi=np.atleast_2d(zi)) - return np.r_[axis_reverse(y), ic_bwd] + # 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 out From 93fe2cf75a59d6d8bf314a90eb044381c34f0d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 20 Mar 2024 16:03:22 -0500 Subject: [PATCH 021/500] ENH: Move cspline2d to use symiirorder2 --- scipy/signal/__init__.py | 1 - scipy/signal/_bspline_util.c.in | 410 -------------------------------- scipy/signal/_bsplines.py | 66 ++++- scipy/signal/_splinemodule.c | 81 ------- scipy/signal/_splinemodule.h | 2 - 5 files changed, 57 insertions(+), 503 deletions(-) diff --git a/scipy/signal/__init__.py b/scipy/signal/__init__.py index 00213417bf14..f56cf4ef1fd3 100644 --- a/scipy/signal/__init__.py +++ b/scipy/signal/__init__.py @@ -310,7 +310,6 @@ from ._upfirdn import upfirdn from ._spline import ( - cspline2d, sepfir2d ) diff --git a/scipy/signal/_bspline_util.c.in b/scipy/signal/_bspline_util.c.in index 8cc4980069a8..811f6c585f10 100644 --- a/scipy/signal/_bspline_util.c.in +++ b/scipy/signal/_bspline_util.c.in @@ -22,20 +22,6 @@ #define ABSQ(a) ( (a*CONJ(a))) #endif -void -compute_root_from_lambda(double lambda, double *r, double *omega) -{ - double xi; - double tmp, tmp2; - - tmp = sqrt(3 + 144*lambda); - xi = 1 - 96*lambda + 24*lambda * tmp; - *omega = atan(sqrt((144*lambda - 1.0)/xi)); - tmp2 = sqrt(xi); - *r = (24*lambda - 1 - tmp2)/(24*lambda) * sqrt((48*lambda + 24*lambda*tmp))/tmp2; - return; -} - {{py: @@ -57,182 +43,6 @@ static {{TYP}} {{SUB}}_hs(int, {{TYP}}, double, double); {{endfor}} -/* Implement the following difference equation */ -/* y[n] = a1 * x[n] + a2 * y[n-1] */ -/* with a given starting value loaded into the array */ - -{{for SUB, TYP in zip(NAMES, TYPES)}} -{{if SUB in CNAME}} -#ifdef __GNUC__ -{{endif}} -void {{SUB}}_IIR_order1({{TYP}} a1, {{TYP}} a2, {{TYP}} *x, {{TYP}} *y, - int N, int stridex, int stridey) -{ - {{TYP}} *yvec = y + stridey; - {{TYP}} *xvec = x + stridex; - int n; - - for (n = 1; n < N; n++) { - *yvec = *xvec * a1 + *(yvec - stridey) * a2; - yvec += stridey; - xvec += stridex; - } -} -{{if SUB in CNAME}} -#endif -{{endif}} -{{endfor}} - - -/* Implement the following difference equation */ -/* y[n] = a1 * x[n] + a2 * y[n-1] + a3 * y[n-2] */ -/* with two starting values loaded into the array */ - -{{for SUB, TYP in zip(NAMES, TYPES)}} -{{if SUB in CNAME}} -#ifdef __GNUC__ -{{endif}} -void {{SUB}}_IIR_order2({{TYP}} a1, {{TYP}} a2, {{TYP}} a3, - {{TYP}} *x, {{TYP}} *y, int N, int stridex, int stridey) -{ - {{TYP}} *yvec = y + 2*stridey; - {{TYP}} *xvec = x + 2*stridex; - int n; - - for (n = 2; n < N; n++) { - *yvec = *xvec * a1 + *(yvec - stridey) * a2 + *(yvec - 2*stridey) * a3; - yvec += stridey; - xvec += stridex; - } -} -{{if SUB in CNAME}} -#endif -{{endif}} -{{endfor}} - - -/* Implement a second order IIR difference equation using a cascade - of first order sections. Suppose the transfer function is - cs - H(z) = ------------------- - (1-z1/z) ( 1-z2/z) - - then the following pair is implemented with one starting value loaded in - the output array and the starting value for the intermediate array - passed in as yp0. - - y1[n] = x[n] + z1 y1[n-1] - yp[n] = cs y1[n] + z2 yp[n-1] - -*/ - - -{{for SUB, TYP in zip(NAMES, TYPES)}} -{{if SUB in CNAME}} -#ifdef __GNUC__ -{{endif}} -void {{SUB}}_IIR_order2_cascade({{TYP}} cs, {{TYP}} z1, {{TYP}} z2, - {{TYP}} y1_0, {{TYP}} *x, {{TYP}} *yp, - int N, int stridex, int stridey) -{ - {{TYP}} *yvec = yp + stridey; - {{TYP}} *xvec = x + stridex; - int n; - - for (n = 1; n < N; n++) { - y1_0 = *xvec + y1_0 * z1; - *yvec = cs * y1_0 + *(yvec - stridey) * z2; - yvec += stridey; - xvec += stridex; - } -} -{{if SUB in CNAME}} -#endif -{{endif}} -{{endfor}} - -/* 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 the following transfer function: - c0 - H(z) = --------------------------- - (1-z1/z) (1 - z1 z) - - with the following difference equations: - - yp[n] = x[n] + z1 yp[n-1] - with starting condition: - yp[0] = x[0] + Sum(z1^(k+1) x[k],k=0..Infinity) - - and - - y[n] = z1 y[n+1] + c0 yp[n] - with starting condition: - y[N-1] = z1 / (z1-1) yp[N-1] - - The resulting signal will have mirror symmetric boundary conditions as well. - - If memory could not be allocated for the temporary vector yp, the - function returns -1 otherwise it returns 0. - - z1 should be less than 1; - -*/ - -{{for SUB, TYP, RTYP in zip(NAMES, TYPES, RTYPES)}} -{{if SUB in CNAME}} -#ifdef __GNUC__ -{{endif}} -int {{SUB}}_IIR_forback1({{TYP}} c0, {{TYP}} z1, {{TYP}} *x, {{TYP}} *y, - int N, int stridex, int stridey, {{RTYP}} precision) -{ - {{TYP}} *yp = NULL; - {{TYP}} *xptr = x; - {{TYP}} yp0, powz1, diff; - {{RTYP}} err; - int k; - - if (ABSQ(z1) >= 1.0) return -2; /* z1 not less than 1 */ - - /* Initialize memory for loop */ - if ((yp = malloc(N*sizeof({{TYP}})))==NULL) return -1; - - /* Fix starting value assuming mirror-symmetric boundary conditions. */ - yp0 = x[0]; - powz1 = 1.0; - k = 0; - precision *= precision; - do { - yp[0] = yp0; - powz1 *= z1; - yp0 += powz1 * (*xptr); - diff = powz1; - err = ABSQ(diff); - xptr += stridex; - k++; - } while((err > precision) && (k < N)); - if (k >= N){ - /* sum did not converge */ - free(yp); - return -3; - } - yp[0] = yp0; - - {{SUB}}_IIR_order1(1.0, z1, x, yp, N, stridex, 1); - - *(y + (N - 1)*stridey) = -c0 / (z1 - 1.0) * yp[N-1]; - - {{SUB}}_IIR_order1(c0, z1, yp + N - 1, y + (N - 1)*stridey, N, -1, -stridey); - - free(yp); - return 0; -} -{{if SUB in CNAME}} -#endif -{{endif}} -{{endfor}} - - {{for SUB, TYP, RTYP in zip(NAMES, TYPES, RTYPES)}} {{if SUB in CNAME}} #ifdef __GNUC__ @@ -521,223 +331,3 @@ int {{SUB}}_separable_2Dconvolve_mirror({{TYP}} *in, {{TYP}} *out, } {{endfor}} - -/* 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 - - with the following difference equations: - - yp[n] = cs*x[n] - b1 yp[n-1] - b2 yp[n-2] - with starting conditions: - yp[0] = hc[0] x[0] + Sum(hc[k+1]*x[k],k=0..Infinity) - yp[1] = hc[0] x[1] + hc[1] x[0] + Sum(hc[k+2] x[k], k=0..Infinity) - - and - - y[n] = cs*yp[n] - b1 y[n+1] -b2 y[n+2] - with starting conditions: - y[N-1] = Sum((hs[k] + hs[k+1])x[N-1-k],k=0..Infinity) - y[N-2] = Sum((hs[k-1] + hs[k+2])x[N-1-k],k=0..Infinity) - - The resulting signal will have mirror symmetric boundary conditions as well. - - If memory could not be allocated for the temporary vector yp, the - function returns -1 otherwise it returns 0. - - z1 should be less than 1; - -*/ - -{{for SUB, TYP in zip(RNAME, RTYPE)}} -int {{SUB}}_IIR_forback2 (double r, double omega, {{TYP}} *x, {{TYP}} *y, - int N, int stridex, int stridey, {{TYP}} precision) { - {{TYP}} cs; - {{TYP}} *yp = NULL; - {{TYP}} *yptr; - {{TYP}} *xptr; - {{TYP}} yp0; - {{TYP}} yp1; - double rsq; - {{TYP}} diff; - {{TYP}} err; - {{TYP}} a2, a3; - int k; - - if (r >= 1.0) return -2; /* z1 not less than 1 */ - - /* Initialize memory for loop */ - if ((yp = malloc(N*sizeof({{TYP}})))==NULL) return -1; - - rsq = r * r; - a2 = 2 * r * cos(omega); - a3 = -rsq; - cs = 1 - 2 * r * cos(omega) + rsq; - - /* Fix starting values assuming mirror-symmetric boundary conditions. */ - yp0 = {{SUB}}_hc(0, cs, r, omega) * x[0]; - k = 0; - precision *= precision; - xptr = x; - do { - yp[0] = yp0; - diff = {{SUB}}_hc(k+1, cs, r, omega); - yp0 += diff * (*xptr); - err = diff * diff; - xptr += stridex; - k++; - } while((err > precision) && (k < N)); - if (k >= N) {free(yp); return -3;} /* sum did not converge */ - yp[0] = yp0; - - yp1 = {{SUB}}_hc(0, cs, r, omega) * (*(x+stridex)); - yp1 += {{SUB}}_hc(1, cs, r, omega) * x[0]; - k = 0; - xptr = x; - do { - yp[1] = yp1; - diff = {{SUB}}_hc(k+2, cs, r, omega); - yp1 += diff * (*xptr); - err = diff * diff; - xptr += stridex; - k++; - } while((err > precision) && (k < N)); - if (k >= N) {free(yp); return -3;} /* sum did not converge */ - yp[1] = yp1; - - {{SUB}}_IIR_order2(cs, a2, a3, x, yp, N, stridex, 1); - - /* Fix starting values assuming mirror-symmetric boundary conditions. */ - yp0 = 0.0; - k = 0; - yptr = y + (N-1)*stridey; - xptr = x + (N-1)*stridex; - do { - *yptr = yp0; - diff = ({{SUB}}_hs(k, cs, rsq, omega) + {{SUB}}_hs(k+1, cs, rsq, omega)); - yp0 += diff * (*xptr); - err = diff * diff; - xptr -= stridex; - k++; - } while((err > precision) && (k < N)); - if (k >= N) {free(yp); return -3;} /* sum did not converge */ - *yptr = yp0; - - yp1 = 0.0; - k = 0; - yptr -= stridey; /* Initialize in next-to-last slot in output array */ - xptr = x + (N-1)*stridex; - do { - *yptr = yp1; - diff = ({{SUB}}_hs(k-1, cs, rsq, omega) + {{SUB}}_hs(k+2, cs, rsq, omega)); - yp1 += diff * (*xptr); - err = diff * diff; - xptr -= stridex; - k++; - } while((err > precision) && (k < N)); - if (k >= N) {free(yp); return -3;} /* sum did not converge */ - *yptr = yp1; - - {{SUB}}_IIR_order2(cs, a2, a3, yp+N-1, yptr+stridey, N, -1, -stridey); - - free(yp); - return 0; -} - -{{endfor}} - -/* Find the cubic spline coefficients of an image - image is M rows by N columns stored rowise in memory (vary column number - first). It will be replaced with the spline coefficients. - lambda is a smoothing parameter (lambda = 100 approximately corresponds - to a cutoff frequency of 0.1*(sample freq)) - strides is an integer array [rowstride, colstride] - telling how much memory in units of sizeof(DATA TYPE) bytes to skip - to get to the next element. -*/ - -/* to get the (smoothed) image back mirror-symmetric convolve with a length - three separable FIR filter [1.0, 4.0, 1.0]/ 6.0 -*/ - -{{for SUB, TYP in zip(RNAME, RTYPE)}} -int {{SUB}}_cubic_spline2D({{TYP}} *image, {{TYP}} *coeffs, int M, int N, double lambda, - npy_intp *strides, npy_intp *cstrides, {{TYP}} precision) { - double r, omega; - {{TYP}} *inptr; - {{TYP}} *coptr; - {{TYP}} *tmpmem; - {{TYP}} *tptr; - int m, n, retval=0; - - tmpmem = malloc(N*M*sizeof({{TYP}})); - if (tmpmem == NULL) return -1; - - if (lambda <= 1.0 / 144.0) { - /* normal cubic spline */ - r = -2 + sqrt(3.0); - - /* Loop over rows */ - inptr = image; - tptr = tmpmem; - for (m = 0; m < M; m++) { - retval = {{SUB}}_IIR_forback1 (-r*6.0, r, inptr, tptr, N, strides[1], 1, precision); - if (retval < 0) break; - inptr += strides[0]; - tptr += N; - } - - if (retval >=0) { - /* Loop over columns */ - tptr = tmpmem; - coptr = coeffs; - for (n = 0; n < N; n++) { - retval = {{SUB}}_IIR_forback1 (-r*6.0, r, tptr, coptr, M, N, cstrides[0], precision); - if (retval < 0) break; - coptr += cstrides[1]; - tptr += 1; - } - } - free(tmpmem); - return retval; - } - - /* Smoothing spline */ - - /* Compute r and omega from lambda */ - compute_root_from_lambda(lambda, &r, &omega); - - /* Loop over rows */ - inptr = image; - tptr = tmpmem; - for (m = 0; m < M; m++) { - retval = {{SUB}}_IIR_forback2 (r, omega, inptr, tptr, N, strides[1], - 1, precision); - if (retval < 0) break; - inptr += strides[0]; - tptr += N; - } - /* Loop over columns */ - tptr = tmpmem; - coptr = coeffs; - for (n = 0; n < N; n++) { - retval = {{SUB}}_IIR_forback2 (r, omega, tptr, coptr, M, N, - cstrides[0], precision); - if (retval < 0) break; - coptr += cstrides[1]; - tptr += 1; - } - - free(tmpmem); - return retval; -} - -{{endfor}} diff --git a/scipy/signal/_bsplines.py b/scipy/signal/_bsplines.py index 613804dfefb9..f816cb0770da 100644 --- a/scipy/signal/_bsplines.py +++ b/scipy/signal/_bsplines.py @@ -1,20 +1,19 @@ from numpy import (asarray, pi, zeros_like, array, arctan2, tan, ones, arange, floor, r_, c_, atleast_1d, sqrt, exp, greater, cos, add, sin, - sqrt, moveaxis, abs) + sqrt, moveaxis, abs, arctan) from scipy._lib._util import normalize_axis_index # From splinemodule.c -from ._spline import cspline2d, sepfir2d, symiirorder1_ic -from ._splines import symiirorder1 -from ._arraytools import axis_slice, axis_reverse +from ._spline import sepfir2d +from ._splines import symiirorder1, symiirorder2 from ._signaltools import lfilter, sosfilt, lfiltic from scipy.interpolate import BSpline __all__ = ['spline_filter', 'gauss_spline', - 'cspline1d', 'qspline1d', 'qspline2d', + 'cspline1d', 'qspline1d', 'qspline2d', 'cspline2d', 'cspline1d_eval', 'qspline1d_eval'] @@ -274,6 +273,16 @@ def _quadratic_coeff(signal): return output * 8.0 +def compute_root_from_lambda(lamb): + tmp = sqrt(3 + 144 * lamb) + xi = 1 - 96 * lamb + 24 * lamb * tmp + omega = arctan(sqrt((144 * lamb - 1.0) / xi)) + tmp2 = sqrt(xi) + r = ((24 * lamb - 1 - tmp2) / (24 * lamb) * + sqrt((48*lamb + 24 * lamb * tmp)) / tmp2) + return r, omega + + def cspline1d(signal, lamb=0.0): """ Compute cubic spline coefficients for rank-1 array. @@ -384,14 +393,14 @@ def collapse_2d(x, axis): return x, x_shape -def _symiirorder1_nd(input, c0, z1, precision=-1.0, axis=-1): +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 = symiirorder1(input, c0, z1, precision=precision) + out = func(input, *args, **kwargs) if input_ndim > 1: out = out.reshape(input_shape) @@ -432,8 +441,47 @@ def qspline2d(signal, lamb=0.0, precision=-1.0): c0 = -r * 8.0 z1 = r - out = _symiirorder1_nd(signal, c0, z1, precision, axis=-1) - out = _symiirorder1_nd(out, c0, z1, precision, axis=0) + 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 + ---------- + input : 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 lamb <= 1 / 144.0: + # Normal cubic spline + r = -2 + 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 out diff --git a/scipy/signal/_splinemodule.c b/scipy/signal/_splinemodule.c index b8286213d959..6d901a708bbf 100644 --- a/scipy/signal/_splinemodule.c +++ b/scipy/signal/_splinemodule.c @@ -18,86 +18,6 @@ convert_strides(npy_intp* instrides,npy_intp* convstrides,int size,int N) } } - -static char doc_cspline2d[] = "out = cspline2d(input, lambda=0.0, precision=-1.0)\n" -"\n" -" Coefficients for 2-D cubic (3rd order) B-spline.\n" -"\n" -" Return the third-order B-spline coefficients over a regularly spaced\n" -" input grid for the two-dimensional input image.\n" -"\n" -" Parameters\n" -" ----------\n" -" input : ndarray\n" -" The input signal.\n" -" lambda : float\n" -" Specifies the amount of smoothing in the transfer function.\n" -" precision : float\n" -" Specifies the precision for computing the infinite sum needed to apply mirror-\n" -" symmetric boundary conditions.\n" -"\n" -" Returns\n" -" -------\n" -" output : ndarray\n" -" The filtered signal.\n" -"\n" -" Examples\n" -" --------\n" -" Examples are given :ref:`in the tutorial `.\n" -"\n"; - - -static PyObject *cspline2d(PyObject *NPY_UNUSED(dummy), PyObject *args) -{ - PyObject *image=NULL; - PyArrayObject *a_image=NULL, *ck=NULL; - double lambda = 0.0; - double precision = -1.0; - int thetype, M, N, retval=0; - npy_intp outstrides[2], instrides[2]; - - if (!PyArg_ParseTuple(args, "O|dd", &image, &lambda, &precision)) return NULL; - - thetype = PyArray_ObjectType(image, NPY_FLOAT); - thetype = PyArray_MIN(thetype, NPY_DOUBLE); - a_image = (PyArrayObject *)PyArray_FromObject(image, thetype, 2, 2); - if (a_image == NULL) goto fail; - - ck = (PyArrayObject *)PyArray_SimpleNew(2, PyArray_DIMS(a_image), thetype); - if (ck == NULL) goto fail; - M = PyArray_DIMS(a_image)[0]; - N = PyArray_DIMS(a_image)[1]; - - convert_strides(PyArray_STRIDES(a_image), instrides, PyArray_ITEMSIZE(a_image), 2); - outstrides[0] = N; - outstrides[1] = 1; - - if (thetype == NPY_FLOAT) { - if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-3; - retval = S_cubic_spline2D((float *)PyArray_DATA(a_image), - (float *)PyArray_DATA(ck), - M, N, lambda, instrides, outstrides, precision); - } - else if (thetype == NPY_DOUBLE) { - if ((precision <= 0.0) || (precision > 1.0)) precision = 1e-6; - retval = D_cubic_spline2D((double *)PyArray_DATA(a_image), - (double *)PyArray_DATA(ck), - M, N, lambda, instrides, outstrides, precision); - } - - if (retval == -3) PYERR("Precision too high. Error did not converge."); - if (retval < 0) PYERR("Problem occurred inside routine"); - - Py_DECREF(a_image); - return PyArray_Return(ck); - - fail: - Py_XDECREF(a_image); - Py_XDECREF(ck); - return NULL; - -} - static char doc_FIRsepsym2d[] = "out = sepfir2d(input, hrow, hcol)\n" "\n" " Convolve with a 2-D separable FIR filter.\n" @@ -563,7 +483,6 @@ static PyObject *IIRsymorder2_ic_bwd(PyObject *NPY_UNUSED(dummy), PyObject *args static struct PyMethodDef toolbox_module_methods[] = { - {"cspline2d", cspline2d, METH_VARARGS, doc_cspline2d}, {"sepfir2d", FIRsepsym2d, METH_VARARGS, doc_FIRsepsym2d}, {"symiirorder1_ic", IIRsymorder1_ic, METH_VARARGS, doc_IIRsymorder1_ic}, {"symiirorder2_ic_fwd", IIRsymorder2_ic_fwd, METH_VARARGS, doc_IIRsymorder2_ic_fwd}, diff --git a/scipy/signal/_splinemodule.h b/scipy/signal/_splinemodule.h index ead5fa7ae00e..d4919be5ed3e 100644 --- a/scipy/signal/_splinemodule.h +++ b/scipy/signal/_splinemodule.h @@ -7,13 +7,11 @@ static void convert_strides(npy_intp*,npy_intp*,int,int); -extern int S_cubic_spline2D(float*,float*,int,int,double,npy_intp*,npy_intp*,float); extern int S_SYM_IIR1_initial(float, float*, float*, int, int, float); extern int S_SYM_IIR2_initial_fwd(double, double, float*, float*, int, int, float); extern int S_SYM_IIR2_initial_bwd(double, double, float*, float*, int, int, float); extern int S_separable_2Dconvolve_mirror(float*,float*,int,int,float*,float*,int,int,npy_intp*,npy_intp*); -extern int D_cubic_spline2D(double*,double*,int,int,double,npy_intp*,npy_intp*,double); extern int D_SYM_IIR1_initial(double, double*, double*, int, int, double); extern int D_SYM_IIR2_initial_fwd(double, double, double*, double*, int, int, double); extern int D_SYM_IIR2_initial_bwd(double, double, double*, double*, int, int, double); From b0cf648b3c32c236ac50a14024c348cd82b5c1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 20 Mar 2024 16:11:15 -0500 Subject: [PATCH 022/500] FIX: Remove cspline2d and qspline2d from the spline namespace --- scipy/signal/spline.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scipy/signal/spline.py b/scipy/signal/spline.py index d45e7e51d80c..7afd0d0a14be 100644 --- a/scipy/signal/spline.py +++ b/scipy/signal/spline.py @@ -6,8 +6,7 @@ from . import _spline -__all__ = [ # noqa: F822 - 'cspline2d', 'qspline2d', 'sepfir2d'] +__all__ = ['sepfir2d'] # noqa: F822 def __dir__(): From 353c026e257710ca57b6b1d3bc3ac2882e1d3d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 20 Mar 2024 16:25:42 -0500 Subject: [PATCH 023/500] STY: Address lint issues --- scipy/signal/_bsplines.py | 6 +++--- scipy/signal/_splines.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scipy/signal/_bsplines.py b/scipy/signal/_bsplines.py index f816cb0770da..fb6abb9bb033 100644 --- a/scipy/signal/_bsplines.py +++ b/scipy/signal/_bsplines.py @@ -1,7 +1,7 @@ from numpy import (asarray, pi, zeros_like, array, arctan2, tan, ones, arange, floor, - r_, c_, atleast_1d, sqrt, exp, greater, cos, add, sin, - sqrt, moveaxis, abs, arctan) + r_, atleast_1d, sqrt, exp, greater, cos, add, sin, + moveaxis, abs, arctan) from scipy._lib._util import normalize_axis_index @@ -279,7 +279,7 @@ def compute_root_from_lambda(lamb): omega = arctan(sqrt((144 * lamb - 1.0) / xi)) tmp2 = sqrt(xi) r = ((24 * lamb - 1 - tmp2) / (24 * lamb) * - sqrt((48*lamb + 24 * lamb * tmp)) / tmp2) + sqrt(48*lamb + 24 * lamb * tmp) / tmp2) return r, omega diff --git a/scipy/signal/_splines.py b/scipy/signal/_splines.py index 71cd3d022745..a96b3c6530a5 100644 --- a/scipy/signal/_splines.py +++ b/scipy/signal/_splines.py @@ -2,7 +2,7 @@ import numpy as np from ._arraytools import axis_slice, axis_reverse -from ._signaltools import lfiltic, lfilter, sosfilt +from ._signaltools import lfilter, sosfilt from ._spline import symiirorder1_ic, symiirorder2_ic_fwd, symiirorder2_ic_bwd __all__ = ['symiirorder1', 'symiirorder2'] From 9d05f4f0246bf4cbe3a601969fc4bb25794eaebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Thu, 21 Mar 2024 21:01:00 -0500 Subject: [PATCH 024/500] TST: Add tests for symiirorder1_ic --- scipy/signal/tests/meson.build | 1 + scipy/signal/tests/test_splines.py | 75 ++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 scipy/signal/tests/test_splines.py diff --git a/scipy/signal/tests/meson.build b/scipy/signal/tests/meson.build index 231058748a10..bff448fafab8 100644 --- a/scipy/signal/tests/meson.build +++ b/scipy/signal/tests/meson.build @@ -17,6 +17,7 @@ py3.install_sources([ 'test_short_time_fft.py', 'test_signaltools.py', 'test_spectral.py', + 'test_splines.py', 'test_upfirdn.py', 'test_waveforms.py', 'test_wavelets.py', diff --git a/scipy/signal/tests/test_splines.py b/scipy/signal/tests/test_splines.py new file mode 100644 index 000000000000..c9edfcc274c2 --- /dev/null +++ b/scipy/signal/tests/test_splines.py @@ -0,0 +1,75 @@ +# pylint: disable=missing-docstring +import numpy as np +from numpy import array +from numpy.testing import (assert_allclose, assert_array_equal, + assert_almost_equal, assert_raises) +import pytest +from pytest import raises + +from scipy.signal._spline import ( + symiirorder1_ic, symiirorder2_ic_fwd, symiirorder2_ic_bwd) +import scipy.signal._splines as sp +from scipy import signal + + +class TestSymIIR: + @pytest.mark.parametrize( + 'dtype', [np.float32, np.float64, np.complex64, np.complex128]) + @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) + def test_symiir1_ic(self, dtype, precision): + c_precision = precision + if precision == -1.0: + if dtype in {np.float32, np.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 < log(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(np.ceil(np.log(c_precision) / np.log(b))) + expected = np.asarray([[(1 - b ** n_exp) / (1 - b)]], dtype=dtype) + expected = 1 + b * expected + + # Create a step signal of size n + 1 + x = np.ones(n_exp + 1, dtype=dtype) + assert_allclose(symiirorder1_ic(x, b, precision), expected, + atol=2e-6, rtol=2e-7) + + # Check the conditions for a 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 = array(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 ** -np.arange(n_exp + 1, dtype=dtype) + assert_allclose(symiirorder1_ic(x, b, precision), expected, + atol=2e-6, rtol=2e-7) + + def test_symiir1_ic_fails(self): + # Test that symiirorder1_ic fails whenever \sum_{n = 1}^{n} b^n > eps + b = 0.85 + # Create a step signal of size 100 + x = np.ones(100, dtype=np.float64) + + # Compute the closed form for the geometrical series + precision = 1 / (1 - b) + assert_raises(ValueError, symiirorder1_ic, x, b, precision) + + # Test that symiirorder1_ic fails when |z1| >= 1 + assert_raises(ValueError, symiirorder1_ic, x, 1.0, -1) + assert_raises(ValueError, symiirorder1_ic, x, 2.0, -1) From 08dd618cbc9b975624cac3e27528b1f933ea97a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Mon, 25 Mar 2024 18:47:01 -0500 Subject: [PATCH 025/500] TST: Add test for symiirorder1 --- scipy/signal/tests/test_splines.py | 71 +++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/scipy/signal/tests/test_splines.py b/scipy/signal/tests/test_splines.py index c9edfcc274c2..083e018eb25d 100644 --- a/scipy/signal/tests/test_splines.py +++ b/scipy/signal/tests/test_splines.py @@ -8,7 +8,7 @@ from scipy.signal._spline import ( symiirorder1_ic, symiirorder2_ic_fwd, symiirorder2_ic_bwd) -import scipy.signal._splines as sp +from scipy.signal._splines import symiirorder1 from scipy import signal @@ -73,3 +73,72 @@ def test_symiir1_ic_fails(self): # Test that symiirorder1_ic fails when |z1| >= 1 assert_raises(ValueError, symiirorder1_ic, x, 1.0, -1) assert_raises(ValueError, symiirorder1_ic, x, 2.0, -1) + + @pytest.mark.parametrize( + 'dtype', [np.float32, np.float64, np.complex64, np.complex128]) + @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) + def test_symiir1(self, dtype, precision): + c_precision = precision + if precision == -1.0: + if dtype in {np.float32, np.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 an unit step over 200 samples. + c0 = 0.15 + z1 = 0.85 + n = 200 + signal = np.ones(n, dtype=dtype) + + # Find the initial condition. See test_symiir1_ic for a detailed + # explanation + n_exp = int(np.ceil(np.log(c_precision) / np.log(z1))) + initial = np.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 an 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 = np.arange(n, dtype=dtype) + comp1 = initial * z1**pos + + # -z1 / (1 - z1) * z1**(k - 1) * u[k - 1] + comp2 = np.zeros(n, dtype=dtype) + comp2[1:] = -z1 / (1 - z1) * z1**pos[:-1] + + # 1 / (1 - z1) * u[k - 1] + comp3 = np.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 = np.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 = exp_out[::-1] + + out = symiirorder1(signal, c0, z1, precision) + assert_allclose(out, exp_out, atol=4e-6, rtol=6e-7) From 82d31cd294084a875ff6e75a7f6b47f4acb8dd7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Thu, 28 Mar 2024 16:54:57 -0500 Subject: [PATCH 026/500] TST: Add test for symiirorder2_ic_fwd --- scipy/signal/tests/test_splines.py | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/scipy/signal/tests/test_splines.py b/scipy/signal/tests/test_splines.py index 083e018eb25d..b699994d3448 100644 --- a/scipy/signal/tests/test_splines.py +++ b/scipy/signal/tests/test_splines.py @@ -142,3 +142,68 @@ def test_symiir1(self, dtype, precision): out = symiirorder1(signal, c0, z1, precision) assert_allclose(out, exp_out, atol=4e-6, rtol=6e-7) + + @pytest.mark.parametrize( + 'dtype', [np.float32, np.float64]) + @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) + def test_symiir2_initial_fwd(self, dtype, precision): + c_precision = precision + if precision == -1.0: + if dtype in {np.float32, np.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 = np.asarray(0.5, dtype=dtype) + omega = np.asarray(np.pi / 3.0, dtype=dtype) + cs = 1 - 2 * r * np.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 = np.ceil(np.log(c_precision / np.sin(omega)) / np.log(c_precision)) + lb = np.ceil(np.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 * np.cos(omega) - + r**2 - + r**(n_exp + 2) * np.sin(omega * (n_exp + 3)) / np.sin(omega) + + r**(n_exp + 3) * np.sin(omega * (n_exp + 2)) / np.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 = np.ceil(np.log(c_precision / np.sin(omega)) / np.log(c_precision)) + lb = np.ceil(np.pi / omega) - 3 + n_exp = min(ub, lb) + + fwd_initial_2 = ( + cs + cs * 2 * r * np.cos(omega) + + (r**2 * np.sin(3 * omega) - + r**3 * np.sin(2 * omega) - + r**(n_exp + 3) * np.sin(omega * (n_exp + 4)) + + r**(n_exp + 4) * np.sin(omega * (n_exp + 3))) / np.sin(omega)) + + expected = np.r_[fwd_initial_1, fwd_initial_2] + + n = 100 + signal = np.ones(n, dtype=dtype) + + out = symiirorder2_ic_fwd(signal, r, omega, precision) + assert_allclose(out, expected, atol=4e-6, rtol=6e-7) From e5ff15fcf4187eec7d8a70c1d871931bb6728e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Mon, 1 Apr 2024 19:58:57 -0500 Subject: [PATCH 027/500] TST: Add remaining tests for symiirorder2 --- scipy/signal/tests/test_splines.py | 101 +++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 4 deletions(-) diff --git a/scipy/signal/tests/test_splines.py b/scipy/signal/tests/test_splines.py index b699994d3448..a53e44da50be 100644 --- a/scipy/signal/tests/test_splines.py +++ b/scipy/signal/tests/test_splines.py @@ -1,17 +1,27 @@ # pylint: disable=missing-docstring import numpy as np from numpy import array -from numpy.testing import (assert_allclose, assert_array_equal, - assert_almost_equal, assert_raises) +from numpy.testing import assert_allclose, assert_raises import pytest from pytest import raises from scipy.signal._spline import ( symiirorder1_ic, symiirorder2_ic_fwd, symiirorder2_ic_bwd) -from scipy.signal._splines import symiirorder1 +from scipy.signal._splines import symiirorder1, symiirorder2 from scipy import signal +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)) + + class TestSymIIR: @pytest.mark.parametrize( 'dtype', [np.float32, np.float64, np.complex64, np.complex128]) @@ -200,10 +210,93 @@ def test_symiir2_initial_fwd(self, dtype, precision): r**(n_exp + 3) * np.sin(omega * (n_exp + 4)) + r**(n_exp + 4) * np.sin(omega * (n_exp + 3))) / np.sin(omega)) - expected = np.r_[fwd_initial_1, fwd_initial_2] + expected = np.r_[fwd_initial_1, fwd_initial_2][None, :] n = 100 signal = np.ones(n, dtype=dtype) out = symiirorder2_ic_fwd(signal, r, omega, precision) assert_allclose(out, expected, atol=4e-6, rtol=6e-7) + + @pytest.mark.parametrize( + 'dtype', [np.float32, np.float64]) + @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) + def test_symiir2_initial_bwd(self, dtype, precision): + c_precision = precision + if precision == -1.0: + if dtype in {np.float32, np.complex64}: + c_precision = 1e-6 + else: + c_precision = 1e-11 + + r = np.asarray(0.5, dtype=dtype) + omega = np.asarray(np.pi / 3.0, dtype=dtype) + cs = 1 - 2 * r * np.cos(omega) + r * r + a2 = 2 * r * np.cos(omega) + a3 = -r * r + + n = 100 + signal = np.ones(n, dtype=dtype) + + # Compute initial forward conditions + ic = symiirorder2_ic_fwd(signal, r, omega, precision) + out = np.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 = np.zeros(2, dtype=dtype) + idx = np.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 = np.where(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 = np.cumsum(diff * out[:1:-1]) + pos = np.where(diff ** 2 < c_precision)[0] + ic2[1] = ic2_1_all[pos[0]] + + out_ic = symiirorder2_ic_bwd(out, r, omega, precision)[0] + assert_allclose(out_ic, ic2, atol=4e-6, rtol=6e-7) + + @pytest.mark.parametrize( + 'dtype', [np.float32, np.float64]) + @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) + def test_symiir2(self, dtype, precision): + r = np.asarray(0.5, dtype=dtype) + omega = np.asarray(np.pi / 3.0, dtype=dtype) + cs = 1 - 2 * r * np.cos(omega) + r * r + a2 = 2 * r * np.cos(omega) + a3 = -r * r + + n = 100 + signal = np.ones(n, dtype=dtype) + + # Compute initial forward conditions + ic = symiirorder2_ic_fwd(signal, r, omega, precision) + out1 = np.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(out1, r, omega, precision)[0] + + # Apply the system cs / (1 - a2 * z - a3 * z^2)) in backwards + exp = np.empty(n, dtype=dtype) + exp[-2:] = ic2[::-1] + + 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) + assert_allclose(out, exp, atol=4e-6, rtol=6e-7) From 87e45660ef9922c7cb8ae790af3034993d7a20e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Mon, 1 Apr 2024 20:02:15 -0500 Subject: [PATCH 028/500] STY: Address linting issues --- scipy/signal/tests/test_splines.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scipy/signal/tests/test_splines.py b/scipy/signal/tests/test_splines.py index a53e44da50be..d041cfefdd53 100644 --- a/scipy/signal/tests/test_splines.py +++ b/scipy/signal/tests/test_splines.py @@ -3,12 +3,10 @@ from numpy import array from numpy.testing import assert_allclose, assert_raises import pytest -from pytest import raises from scipy.signal._spline import ( symiirorder1_ic, symiirorder2_ic_fwd, symiirorder2_ic_bwd) from scipy.signal._splines import symiirorder1, symiirorder2 -from scipy import signal def _compute_symiirorder2_bwd_hs(k, cs, rsq, omega): From 67f338ac55f388659928887a522fc3106ce09d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 2 Apr 2024 12:06:46 -0500 Subject: [PATCH 029/500] FIX: Properly compute precision for qspline and cspline --- scipy/signal/_bsplines.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scipy/signal/_bsplines.py b/scipy/signal/_bsplines.py index fb6abb9bb033..116828bddcba 100644 --- a/scipy/signal/_bsplines.py +++ b/scipy/signal/_bsplines.py @@ -432,6 +432,11 @@ def qspline2d(signal, lamb=0.0, precision=-1.0): output : ndarray The filtered signal. """ + if precision == -1.0: + if signal.dtype.char in {'f', 'F'}: + precision = 1e-3 + else: + precision = 1e-6 if lamb > 0: raise ValueError('lambda must be negative or zero') @@ -468,6 +473,12 @@ def cspline2d(signal, lamb=0.0, precision=-1.0): output : ndarray The filtered signal. """ + if precision == -1.0: + if signal.dtype.char in {'f', 'F'}: + precision = 1e-3 + else: + precision = 1e-6 + if lamb <= 1 / 144.0: # Normal cubic spline r = -2 + sqrt(3.0) From 70cf38857a52aee3eba5b33ad37f5fc12e9226b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 2 Apr 2024 16:35:30 -0500 Subject: [PATCH 030/500] ENH: Add typing stubs for spline C module --- scipy/signal/_spline.pyi | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 scipy/signal/_spline.pyi diff --git a/scipy/signal/_spline.pyi b/scipy/signal/_spline.pyi new file mode 100644 index 000000000000..c4225577db7e --- /dev/null +++ b/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: + ... From e3f38d5146ffbf8572b474f1f322255318364384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 3 Apr 2024 10:07:19 -0500 Subject: [PATCH 031/500] FIX: Add typing stub to meson build --- scipy/signal/meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scipy/signal/meson.build b/scipy/signal/meson.build index 344a7907f230..580b2664feeb 100644 --- a/scipy/signal/meson.build +++ b/scipy/signal/meson.build @@ -83,7 +83,6 @@ py3.install_sources([ '__init__.py', '_arraytools.py', '_bsplines.py', - '_splines.py', '_czt.py', '_filter_design.py', '_fir_filter_design.py', @@ -95,6 +94,8 @@ py3.install_sources([ '_short_time_fft.py', '_signaltools.py', '_spectral_py.py', + '_spline.pyi', + '_splines.py', '_upfirdn.py', '_waveforms.py', '_wavelets.py', From e41cf1e2271575f0a0ff433232a1dc4201bbacea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 10 Apr 2024 17:41:51 -0500 Subject: [PATCH 032/500] FIX: Address compatibility with integer inputs --- scipy/signal/_splines.py | 6 ++++++ scipy/signal/tests/test_splines.py | 22 +++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/scipy/signal/_splines.py b/scipy/signal/_splines.py index a96b3c6530a5..6ae54afa8061 100644 --- a/scipy/signal/_splines.py +++ b/scipy/signal/_splines.py @@ -49,6 +49,9 @@ def symiirorder1(signal, c0, z1, precision=-1.0): signal = signal[None, :] squeeze_dim = True + if np.issubdtype(signal.dtype, np.integer): + signal = signal.astype(np.promote_types(signal.dtype, np.float32)) + y0 = symiirorder1_ic(signal, z1, precision) # Apply first the system 1 / (1 - z1 * z^-1) @@ -123,6 +126,9 @@ def symiirorder2(input, r, omega, precision=-1.0): input = input[None, :] squeeze_dim = True + if np.issubdtype(input.dtype, np.integer): + input = input.astype(np.promote_types(input.dtype, np.float32)) + rsq = r * r a2 = 2 * r * np.cos(omega) a3 = -rsq diff --git a/scipy/signal/tests/test_splines.py b/scipy/signal/tests/test_splines.py index d041cfefdd53..bc4fbb3d3c4d 100644 --- a/scipy/signal/tests/test_splines.py +++ b/scipy/signal/tests/test_splines.py @@ -26,7 +26,7 @@ class TestSymIIR: @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) def test_symiir1_ic(self, dtype, precision): c_precision = precision - if precision == -1.0: + if precision <= 0.0 or precision > 1.0: if dtype in {np.float32, np.complex64}: c_precision = 1e-6 else: @@ -40,7 +40,7 @@ def test_symiir1_ic(self, dtype, precision): # 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 < log(precision), which + # 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) @@ -87,7 +87,7 @@ def test_symiir1_ic_fails(self): @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) def test_symiir1(self, dtype, precision): c_precision = precision - if precision == -1.0: + if precision <= 0.0 or precision > 1.0: if dtype in {np.float32, np.complex64}: c_precision = 1e-6 else: @@ -156,7 +156,7 @@ def test_symiir1(self, dtype, precision): @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) def test_symiir2_initial_fwd(self, dtype, precision): c_precision = precision - if precision == -1.0: + if precision <= 0.0 or precision > 1.0: if dtype in {np.float32, np.complex64}: c_precision = 1e-6 else: @@ -221,7 +221,7 @@ def test_symiir2_initial_fwd(self, dtype, precision): @pytest.mark.parametrize('precision', [-1.0, 0.7, 0.5, 0.25, 0.0075]) def test_symiir2_initial_bwd(self, dtype, precision): c_precision = precision - if precision == -1.0: + if precision <= 0.0 or precision > 1.0: if dtype in {np.float32, np.complex64}: c_precision = 1e-6 else: @@ -298,3 +298,15 @@ def test_symiir2(self, dtype, precision): out = symiirorder2(signal, r, omega, precision) assert_allclose(out, exp, atol=4e-6, rtol=6e-7) + + def test_symiir1_integer_input(self): + s = np.where(np.arange(100) % 2, -1, 1) + expected = symiirorder1(s.astype(float), 0.5, 0.5) + out = symiirorder1(s, 0.5, 0.5) + assert_allclose(out, expected) + + def test_symiir2_integer_input(self): + s = np.where(np.arange(100) % 2, -1, 1) + expected = symiirorder2(s.astype(float), 0.5, np.pi / 3.0) + out = symiirorder2(s, 0.5, np.pi / 3.0) + assert_allclose(out, expected) \ No newline at end of file From ed85ac13c3d539d0d942bffaae2324d17ba489bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 10 Apr 2024 18:11:34 -0500 Subject: [PATCH 033/500] FIX: Replace dtype.char by checking in a list --- scipy/signal/_bsplines.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scipy/signal/_bsplines.py b/scipy/signal/_bsplines.py index 116828bddcba..58fd5d362079 100644 --- a/scipy/signal/_bsplines.py +++ b/scipy/signal/_bsplines.py @@ -1,7 +1,7 @@ from numpy import (asarray, pi, zeros_like, array, arctan2, tan, ones, arange, floor, r_, atleast_1d, sqrt, exp, greater, cos, add, sin, - moveaxis, abs, arctan) + moveaxis, abs, arctan, complex64, float32) from scipy._lib._util import normalize_axis_index @@ -432,8 +432,8 @@ def qspline2d(signal, lamb=0.0, precision=-1.0): output : ndarray The filtered signal. """ - if precision == -1.0: - if signal.dtype.char in {'f', 'F'}: + if precision < 0.0 or precision >= 1.0: + if signal.dtype in [float32, complex64]: precision = 1e-3 else: precision = 1e-6 @@ -473,8 +473,8 @@ def cspline2d(signal, lamb=0.0, precision=-1.0): output : ndarray The filtered signal. """ - if precision == -1.0: - if signal.dtype.char in {'f', 'F'}: + if precision < 0.0 or precision >= 1.0: + if signal.dtype in [float32, complex64]: precision = 1e-3 else: precision = 1e-6 From 7084ceedfeb12c07c3c0ae1af109cfd477abc679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 10 Apr 2024 20:31:09 -0500 Subject: [PATCH 034/500] DOC: Improve docstrings and comments in C template file --- scipy/signal/_bspline_util.c.in | 65 +++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/scipy/signal/_bspline_util.c.in b/scipy/signal/_bspline_util.c.in index 811f6c585f10..26bb6fb29e4c 100644 --- a/scipy/signal/_bspline_util.c.in +++ b/scipy/signal/_bspline_util.c.in @@ -38,7 +38,9 @@ RTYPES = RTYPE + RTYPE }} {{for SUB, TYP in zip(RNAME, RTYPE)}} +// Approximate steady state conditions for the system cs / (1 - a2 * z^-1 - a3 * z^-2) static {{TYP}} {{SUB}}_hc(int, {{TYP}}, double, double); +// Approximate steady state conditions for the system cs / (1 - a2 * z - a3 * z^2) static {{TYP}} {{SUB}}_hs(int, {{TYP}}, double, double); {{endfor}} @@ -86,6 +88,30 @@ int {{SUB}}_SYM_IIR1_initial({{TYP}} z1, {{TYP}} *x, {{TYP}} *yp0, {{endif}} {{endfor}} +/** +Compute the starting initial conditions for the system +cs / (1 - a2 * z^-1 - a3 * z^-2) against signals x, where: + +a2 = 2 * r * cos(omega) +a3 = - r ** 2 +cs = 1 - 2 * r * cos(omega) + r ** 2 + +Arguments +--------- +x: double* or float* + 2D strided pointer signal of size (M, N). When M > 1, multiple signals + will be processed independently. +yp: double* or float* + Strided output state condition pointer of size (M, 2). + yp[:, 0] will contain the initial conditions y[n - 1], + whereas yp[:, 1] will contain the initial conditions y[n - 2]. +M: int + Number of signals to compute initial conditions for. +N: int + Length of the signals. +precision: double* or float* + Precision up to which the initial conditions will be computed. +**/ {{for SUB, TYP in zip(RNAME, RTYPE)}} int {{SUB}}_SYM_IIR2_initial_fwd(double r, double omega, {{TYP}} *x, {{TYP}} *yp, int M, int N, {{TYP}} precision) { @@ -93,6 +119,7 @@ int {{SUB}}_SYM_IIR2_initial_fwd(double r, double omega, {{TYP}} cs = 1 - 2 * r * cos(omega) + r * r; for(int i = 0; i < M; i++) { + // Compute starting condition y[n - 1] yp[2 * i] = {{SUB}}_hc(0, cs, r, omega) * x[N * i]; } @@ -105,6 +132,7 @@ int {{SUB}}_SYM_IIR2_initial_fwd(double r, double omega, do { diff = {{SUB}}_hc(k+1, cs, r, omega); for(int i = 0; i < M; i++) { + // Keep computing starting condition y[n - 1] yp[2 * i] += diff * x[N * i + k]; } err = diff * diff; @@ -114,6 +142,7 @@ int {{SUB}}_SYM_IIR2_initial_fwd(double r, double omega, if (k >= N) {return -3;} /* sum did not converge */ for(int i = 0; i < M; i++) { + // Compute starting condition y[n - 2] yp[2 * i + 1] = {{SUB}}_hc(0, cs, r, omega) * x[N * i + 1]; yp[2 * i + 1] += {{SUB}}_hc(1, cs, r, omega) * x[N * i]; } @@ -122,6 +151,7 @@ int {{SUB}}_SYM_IIR2_initial_fwd(double r, double omega, do { diff = {{SUB}}_hc(k+2, cs, r, omega); for(int i = 0; i < M; i++) { + // Keep computing starting condition y[n - 2] yp[2 * i + 1] += diff * x[N * i + k]; } err = diff * diff; @@ -133,6 +163,30 @@ int {{SUB}}_SYM_IIR2_initial_fwd(double r, double omega, } {{endfor}} +/** +Compute the starting initial conditions for the system (ran in backwards) +cs / (1 - a2 * z - a3 * z^2) against signal x, where: + +a2 = 2 * r * cos(omega) +a3 = - r ** 2 +cs = 1 - 2 * r * cos(omega) + r ** 2 + +Arguments +--------- +x: double* or float* + 2D strided pointer signal of size (M, N). When M > 1, multiple signals + will be processed independently. +yp: double* or float* + Output state condition pointer of size (M, 2), yp[:, 0] will contain the + initial conditions y[n + 1], whereas yp[:, 1] will contain the initial + condition y[n + 2]. +M: int + Number of signals to compute initial conditions for. +N: int + Length of the signals. +precision: double* or float* + Precision up to which the initial conditions will be computed. +**/ {{for SUB, TYP in zip(RNAME, RTYPE)}} int {{SUB}}_SYM_IIR2_initial_bwd(double r, double omega, {{TYP}} *x, {{TYP}} *yp, int M, int N, {{TYP}} precision) { @@ -148,6 +202,7 @@ int {{SUB}}_SYM_IIR2_initial_bwd(double r, double omega, do { diff = ({{SUB}}_hs(k, cs, rsq, omega) + {{SUB}}_hs(k+1, cs, rsq, omega)); for(int i = 0; i < M; i++) { + // Compute initial condition y[n + 1] yp[2 * i] += diff * x[N * i + N - 1 - k]; } err = diff * diff; @@ -160,6 +215,7 @@ int {{SUB}}_SYM_IIR2_initial_bwd(double r, double omega, do { diff = ({{SUB}}_hs(k-1, cs, rsq, omega) + {{SUB}}_hs(k+2, cs, rsq, omega)); for(int i = 0; i < M; i++) { + // Compute initial condition y[n + 2] yp[2 * i + 1] += diff * x[N * i + N - 1 - k]; } err = diff * diff; @@ -293,6 +349,10 @@ int {{SUB}}_separable_2Dconvolve_mirror({{TYP}} *in, {{TYP}} *out, {{endif}} {{endfor}} +/** +Approximate the steady state of a two-pole filter in polar form for a +step input. +**/ {{for SUB, TYP in zip(RNAME, RTYPE)}} {{TYP}} {{SUB}}_hc(int k, {{TYP}} cs, double r, double omega) { @@ -305,6 +365,11 @@ int {{SUB}}_separable_2Dconvolve_mirror({{TYP}} *in, {{TYP}} *out, } {{endfor}} + +/** +Approximate the steady state of a two-pole filer in polar form ran in backwards +for a step input. +**/ {{for SUB, TYP in zip(RNAME, RTYPE)}} {{TYP}} {{SUB}}_hs(int k, {{TYP}} cs, double rsq, double omega) { From cb1fb36dd34491486286f297c6a9ce6dd2bcf375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Thu, 11 Apr 2024 14:15:07 -0500 Subject: [PATCH 035/500] FIX: Cast directly to float64 --- scipy/signal/_splines.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/signal/_splines.py b/scipy/signal/_splines.py index 6ae54afa8061..90f6c56befd1 100644 --- a/scipy/signal/_splines.py +++ b/scipy/signal/_splines.py @@ -50,7 +50,7 @@ def symiirorder1(signal, c0, z1, precision=-1.0): squeeze_dim = True if np.issubdtype(signal.dtype, np.integer): - signal = signal.astype(np.promote_types(signal.dtype, np.float32)) + signal = signal.astype(np.float64) y0 = symiirorder1_ic(signal, z1, precision) @@ -127,7 +127,7 @@ def symiirorder2(input, r, omega, precision=-1.0): squeeze_dim = True if np.issubdtype(input.dtype, np.integer): - input = input.astype(np.promote_types(input.dtype, np.float32)) + input = input.astype(np.float64) rsq = r * r a2 = 2 * r * np.cos(omega) From e78f77594c997fc9d84e5b8853d98466d3429de5 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 15 Apr 2024 07:16:27 -0700 Subject: [PATCH 036/500] CI: fail slow tests --- .github/workflows/windows.yml | 4 ++-- environment.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 16d0ad14367e..b79434eabab2 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -89,7 +89,7 @@ jobs: run: | # 1.23.5 is currently the oldest numpy usable on cp3.10 according # to pyproject.toml - python -m pip install numpy==1.23.5 cython pybind11 pythran meson-python meson ninja pytest pytest-xdist pytest-timeout pooch rich_click click doit pydevtool hypothesis "scipy-openblas32<=0.3.23.293.2" + python -m pip install numpy==1.23.5 cython pybind11 pythran meson-python meson ninja pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich_click click doit pydevtool hypothesis "scipy-openblas32<=0.3.23.293.2" - name: Build run: | @@ -97,7 +97,7 @@ jobs: - name: Test run: | - python dev.py test -j2 --mode full + python dev.py test -j2 --mode full -- --durations=10 --timeout=60 --fail-slow=5 ############################################################################# diff --git a/environment.yml b/environment.yml index 84c31d1c4a22..2f8dc77fb79c 100644 --- a/environment.yml +++ b/environment.yml @@ -27,6 +27,7 @@ dependencies: - pytest-cov - pytest-xdist - pytest-timeout + - pytest-fail-slow - asv >=0.6 - hypothesis - array-api-strict From 88827ea006172b3a78190baebe5e891bef7a352c Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 15 Apr 2024 22:22:24 -0700 Subject: [PATCH 037/500] CI: make pytest-fail-slow an optional dependency [skip ci] --- pyproject.toml | 1 + requirements/test.txt | 1 + scipy/conftest.py | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 0a5efd36115e..842b999dcdbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ test = [ "pytest", "pytest-cov", "pytest-timeout", + "pytest-fail-slow", "pytest-xdist", "asv", "mpmath", diff --git a/requirements/test.txt b/requirements/test.txt index 211e83a8b472..e8cdf322e24e 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -3,6 +3,7 @@ pytest pytest-cov pytest-timeout +pytest-fail-slow pytest-xdist asv mpmath diff --git a/scipy/conftest.py b/scipy/conftest.py index a1073633e144..5db209827b8a 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -27,6 +27,13 @@ def pytest_configure(config): except Exception: config.addinivalue_line( "markers", 'timeout: mark a test for a non-default timeout') + try: + # This is a more reliable test of whether pytest_fail_slow is installed + # When I uninstalled it, `import pytest_fail_slow` didn't fail! + from pytest_fail_slow import parse_duration # noqa:F401 + except Exception: + config.addinivalue_line( + "markers", 'fail_slow: mark a test for a non-default timeout failure') config.addinivalue_line("markers", "skip_xp_backends(*backends, reasons=None, np_only=False, cpu_only=False): " "mark the desired skip configuration for the `skip_xp_backends` fixture.") From 71a3c3c7d7c94f05781bb95513fe11225c8f408c Mon Sep 17 00:00:00 2001 From: thalassemia <67928790+thalassemia@users.noreply.github.com> Date: Wed, 17 Apr 2024 12:59:32 -0700 Subject: [PATCH 038/500] BLD: Accelerate wheels for macOS 14+ [wheel build] --- .github/workflows/macos.yml | 15 ++-- .github/workflows/wheels.yml | 121 +++++++++++++++--------------- tools/wheels/cibw_test_command.sh | 2 +- 3 files changed, 72 insertions(+), 66 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f52aa73f6ca3..a63e88cfdf66 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -165,14 +165,14 @@ jobs: sudo xcode-select -s /Applications/Xcode_15.2.app git submodule update --init - # for some reason gfortran is not on the path - GFORTRAN_LOC=$(brew --prefix gfortran)/bin/gfortran + GFORTRAN_LOC=$(which gfortran-13) ln -s $GFORTRAN_LOC gfortran export PATH=$PWD:$PATH - # make sure we have openblas + # make sure we have openblas and gfortran dylibs bash tools/wheels/cibw_before_build_macos.sh $PWD - export DYLD_LIBRARY_PATH=/usr/local/gfortran/lib:/opt/arm64-builds/lib + GFORTRAN_LIB=$(dirname `gfortran --print-file-name libgfortran.dylib`) + export DYLD_LIBRARY_PATH=$GFORTRAN_LIB:/opt/arm64-builds/lib export PKG_CONFIG_PATH=/opt/arm64-builds/lib/pkgconfig pip install click doit pydevtool rich_click meson cython pythran pybind11 ninja numpy @@ -211,11 +211,14 @@ jobs: sudo xcode-select -s /Applications/Xcode_15.2.app git submodule update --init - # for some reason gfortran is not on the path - GFORTRAN_LOC=$(brew --prefix gfortran)/bin/gfortran + GFORTRAN_LOC=$(which gfortran-13) ln -s $GFORTRAN_LOC gfortran export PATH=$PWD:$PATH + # Ensure we have gfortran dylib + GFORTRAN_LIB=$(dirname `gfortran --print-file-name libgfortran.dylib`) + export DYLD_LIBRARY_PATH=$GFORTRAN_LIB + pip install click doit pydevtool rich_click meson cython pythran pybind11 ninja numpy python dev.py build -C-Dblas=accelerate diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 3ed6e64e6759..331f4e08f099 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -59,7 +59,9 @@ jobs: echo github.ref ${{ github.ref }} build_wheels: - name: Build wheel for ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }} ${{ matrix.buildplat[2] }} + name: Wheel, ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }} + ${{ matrix.buildplat[2] }} ${{ matrix.buildplat[3] }} + ${{ matrix.buildplat[4] }} needs: get_commit_message if: >- contains(needs.get_commit_message.outputs.message, '1') || @@ -77,11 +79,13 @@ jobs: # should also be able to do multi-archs on a single entry, e.g. # [windows-2019, win*, "AMD64 x86"]. However, those two require a different compiler setup # so easier to separate out here. - - [ubuntu-22.04, manylinux, x86_64] - - [ubuntu-22.04, musllinux, x86_64] - - [macos-11, macosx, x86_64] - - [macos-14, macosx, arm64] - - [windows-2019, win, AMD64] + - [ubuntu-22.04, manylinux, x86_64, "", ""] + - [ubuntu-22.04, musllinux, x86_64, "", ""] + - [macos-12, macosx, x86_64, openblas, "10.9"] + - [macos-13, macosx, x86_64, accelerate, "14.0"] + - [macos-14, macosx, arm64, openblas, "12.0"] + - [macos-14, macosx, arm64, accelerate, "14.0"] + - [windows-2019, win, AMD64, "", ""] python: [["cp310", "3.10"], ["cp311", "3.11"], ["cp312", "3.12"]] # python[0] is used to specify the python versions made by cibuildwheel @@ -112,63 +116,53 @@ jobs: if: ${{ runner.os == 'Windows' && env.IS_32_BIT == 'false' }} - name: Setup macOS - if: matrix.buildplat[0] == 'macos-11' || matrix.buildplat[0] == 'macos-14' + if: startsWith( matrix.buildplat[0], 'macos-' ) run: | - if [[ ${{ matrix.buildplat[2] }} == 'arm64' ]]; then - # macosx_arm64 - - # use homebrew gfortran - sudo xcode-select -s /Applications/Xcode_15.2.app - # for some reason gfortran is not on the path - GFORTRAN_LOC=$(brew --prefix gfortran)/bin/gfortran - ln -s $GFORTRAN_LOC gfortran + if [[ ${{ matrix.buildplat[3] }} == 'accelerate' ]]; then + echo CIBW_CONFIG_SETTINGS=\"setup-args=-Dblas=accelerate\" >> "$GITHUB_ENV" + # Always use preinstalled gfortran for Accelerate builds + ln -s $(which gfortran-13) gfortran export PATH=$PWD:$PATH echo "PATH=$PATH" >> "$GITHUB_ENV" - - # location of the gfortran's libraries - GFORTRAN_LIB=$(dirname `gfortran --print-file-name libgfortran.dylib`) - - CIBW="MACOSX_DEPLOYMENT_TARGET=12.0\ - MACOS_DEPLOYMENT_TARGET=12.0\ - LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH\ - _PYTHON_HOST_PLATFORM=macosx-12.0-arm64\ - PIP_PRE=1\ - PIP_NO_BUILD_ISOLATION=false\ - PKG_CONFIG_PATH=/opt/arm64-builds/lib/pkgconfig\ - PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" - echo "CIBW_ENVIRONMENT_MACOS=$CIBW" >> "$GITHUB_ENV" - - CIBW="sudo xcode-select -s /Applications/Xcode_15.2.app" - echo "CIBW_BEFORE_ALL=$CIBW" >> $GITHUB_ENV - - echo "REPAIR_PATH=/opt/arm64-builds/lib" >> "$GITHUB_ENV" - - CIBW="DYLD_LIBRARY_PATH=$GFORTRAN_LIB:/opt/arm64-builds/lib delocate-listdeps {wheel} &&\ - DYLD_LIBRARY_PATH=$GFORTRAN_LIB:/opt/arm64-builds/lib delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}" - echo "CIBW_REPAIR_WHEEL_COMMAND_MACOS=$CIBW" >> "$GITHUB_ENV" - + LIB_PATH=$(dirname $(gfortran --print-file-name libgfortran.dylib)) + fi + # Add libraries installed by cibw_before_build_macos.sh to path + if [[ ${{ matrix.buildplat[2] }} == 'arm64' ]]; then + LIB_PATH=$LIB_PATH:/opt/arm64-builds/lib + else + LIB_PATH=$LIB_PATH:/usr/local/lib + fi + if [[ ${{ matrix.buildplat[4] }} == '10.9' ]]; then + # Newest version of Xcode that supports macOS 10.9 + XCODE_VER='13.4.1' else - # macosx_x86_64 with OpenBLAS - # setting SDKROOT necessary when using the gfortran compiler - # installed in cibw_before_build_macos.sh - # MACOS_DEPLOYMENT_TARGET is set because of - # https://github.com/mesonbuild/meson-python/pull/309. Once - # an update is released, then that environment variable can - # be removed. - CIBW="MACOSX_DEPLOYMENT_TARGET=10.9\ - MACOS_DEPLOYMENT_TARGET=10.9\ - SDKROOT=/Applications/Xcode_11.7.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk\ - LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH\ - _PYTHON_HOST_PLATFORM=macosx-10.9-x86_64\ - PIP_PRE=1\ - PIP_NO_BUILD_ISOLATION=false\ - PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" - echo "CIBW_ENVIRONMENT_MACOS=$CIBW" >> "$GITHUB_ENV" - - CIBW="DYLD_LIBRARY_PATH=/usr/local/lib delocate-listdeps {wheel} &&\ - DYLD_LIBRARY_PATH=/usr/local/lib delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}" - echo "CIBW_REPAIR_WHEEL_COMMAND_MACOS=$CIBW" >> "$GITHUB_ENV" + XCODE_VER='15.2' + fi + CIBW="sudo xcode-select -s /Applications/Xcode_${XCODE_VER}.app" + echo "CIBW_BEFORE_ALL=$CIBW" >> $GITHUB_ENV + # setting SDKROOT necessary when using the gfortran compiler + # installed in cibw_before_build_macos.sh + sudo xcode-select -s /Applications/Xcode_${XCODE_VER}.app + CIBW="MACOSX_DEPLOYMENT_TARGET=${{ matrix.buildplat[4] }}\ + LD_LIBRARY_PATH=$LIB_PATH:$LD_LIBRARY_PATH\ + SDKROOT=$(xcrun --sdk macosx --show-sdk-path)\ + PIP_PRE=1\ + PIP_NO_BUILD_ISOLATION=false\ + PKG_CONFIG_PATH=$LIB_PATH/pkgconfig\ + PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" + echo "CIBW_ENVIRONMENT_MACOS=$CIBW" >> "$GITHUB_ENV" + + echo "REPAIR_PATH=$LIB_PATH" >> "$GITHUB_ENV" + GFORTRAN_LIB="\$(dirname \$(gfortran --print-file-name libgfortran.dylib))" + CIBW="DYLD_LIBRARY_PATH=$GFORTRAN_LIB:$LIB_PATH delocate-listdeps {wheel} &&\ + DYLD_LIBRARY_PATH=$GFORTRAN_LIB:$LIB_PATH delocate-wheel --require-archs \ + {delocate_archs} -w {dest_dir} {wheel}" + # Rename x86 Accelerate wheel to test on macOS 13 runner + if [[ ${{ matrix.buildplat[0] }} == 'macos-13' && ${{ matrix.buildplat[4] }} == '14.0' ]]; then + CIBW+=" && mv {dest_dir}/\$(basename {wheel}) \ + {dest_dir}/\$(echo \$(basename {wheel}) | sed 's/14_0/13_0/')" fi + echo "CIBW_REPAIR_WHEEL_COMMAND_MACOS=$CIBW" >> "$GITHUB_ENV" - name: Build wheels uses: pypa/cibuildwheel@v2.17.0 @@ -195,10 +189,18 @@ jobs: PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple PIP_NO_BUILD_ISOLATION=false + - name: Rename after test (macOS x86 Accelerate only) + # Rename x86 Accelerate wheel back so it targets macOS >= 14 + if: matrix.buildplat[0] == 'macos-13' && matrix.buildplat[4] == '14.0' + run: | + mv ./wheelhouse/*.whl $(find ./wheelhouse -type f -name '*.whl' | sed 's/13_0/14_0/') + - uses: actions/upload-artifact@v4 with: path: ./wheelhouse/*.whl - name: ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }}-${{ matrix.buildplat[2] }} + name: ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }} + ${{ matrix.buildplat[2] }} ${{ matrix.buildplat[3] }} + ${{ matrix.buildplat[4] }} - uses: conda-incubator/setup-miniconda@v3 with: @@ -209,6 +211,7 @@ jobs: # build and test the wheel auto-update-conda: true python-version: "3.10" + miniconda-version: "latest" - name: Upload wheels if: success() diff --git a/tools/wheels/cibw_test_command.sh b/tools/wheels/cibw_test_command.sh index 519b13cdef53..2eb214b3582b 100644 --- a/tools/wheels/cibw_test_command.sh +++ b/tools/wheels/cibw_test_command.sh @@ -3,7 +3,7 @@ set -xe PROJECT_DIR="$1" # python $PROJECT_DIR/tools/wheels/check_license.py -if [[ $(uname) == "Linux" || $(uname) == "Darwin" ]] ; then +if [[ $(uname) == "Linux" ]] ; then python $PROJECT_DIR/tools/openblas_support.py --check_version fi echo $? From 11b509c8979ddedde8f9952eb1a60d130f2889ac Mon Sep 17 00:00:00 2001 From: thalassemia <67928790+thalassemia@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:00:27 -0700 Subject: [PATCH 039/500] DOC: Update wheel build toolchain info [wheel build] --- doc/source/dev/toolchain.rst | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/doc/source/dev/toolchain.rst b/doc/source/dev/toolchain.rst index 449178232439..a7e15e53dcce 100644 --- a/doc/source/dev/toolchain.rst +++ b/doc/source/dev/toolchain.rst @@ -138,15 +138,17 @@ Official Builds Currently, SciPy wheels are being built as follows: -================ ============================== ============================== ============================= - Platform `CI`_ `Base`_ `Images`_ Compilers Comment -================ ============================== ============================== ============================= -Linux x86 ``ubuntu-22.04`` GCC 10.2.1 ``cibuildwheel`` -Linux arm ``docker-builder-arm64`` GCC 11.3.0 ``cibuildwheel`` -OSX x86_64 ``macos-11`` clang-13/gfortran 11.3 ``cibuildwheel`` -OSX arm64 ``macos-14`` clang-14/gfortran 13.0 ``cibuildwheel`` -Windows ``windows-2019`` GCC 10.3 (`rtools`_) ``cibuildwheel`` -================ ============================== ============================== ============================= +========================= ============================== ==================================== ============================= + Platform `CI`_ `Base`_ `Images`_ Compilers Comment +========================= ============================== ==================================== ============================= + Linux x86 ``ubuntu-22.04`` GCC 10.2.1 ``cibuildwheel`` + Linux arm ``docker-builder-arm64`` GCC 11.3.0 ``cibuildwheel`` + OSX x86_64 (OpenBLAS) ``macos-12`` Apple clang 13.1.6/gfortran 11.3.0 ``cibuildwheel`` + OSX x86_64 (Accelerate) ``macos-13`` Apple clang 15.0.0/gfortran 13.2.0 ``cibuildwheel`` + OSX arm64 (OpenBLAS) ``macos-14`` Apple clang 15.0.0/gfortran 12.1.0 ``cibuildwheel`` + OSX arm64 (Accelerate) ``macos-14`` Apple clang 15.0.0/gfortran 13.2.0 ``cibuildwheel`` + Windows ``windows-2019`` GCC 10.3.0 (`rtools`_) ``cibuildwheel`` +========================= ============================== ==================================== ============================= .. _CI: https://github.com/actions/runner-images .. _Base: https://cirrus-ci.org/guide/docker-builder-vm/#under-the-hood From ec6b0c1f78aed70d720f49f4de6ceda043b00fac Mon Sep 17 00:00:00 2001 From: lucascolley Date: Tue, 23 Apr 2024 10:28:42 +0100 Subject: [PATCH 040/500] DOC/DEV: add core-dev page on vendored code [docs only] --- doc/source/dev/core-dev/index.rst | 2 ++ doc/source/dev/core-dev/vendored-code.rst.inc | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 doc/source/dev/core-dev/vendored-code.rst.inc diff --git a/doc/source/dev/core-dev/index.rst b/doc/source/dev/core-dev/index.rst index 51b8bb3fe4d1..38f91118fba3 100644 --- a/doc/source/dev/core-dev/index.rst +++ b/doc/source/dev/core-dev/index.rst @@ -16,6 +16,8 @@ SciPy Core Developer Guide .. include:: deprecations.rst.inc +.. include:: vendored-code.rst.inc + .. include:: distributing.rst.inc .. include:: releasing.rst.inc diff --git a/doc/source/dev/core-dev/vendored-code.rst.inc b/doc/source/dev/core-dev/vendored-code.rst.inc new file mode 100644 index 000000000000..ac316109b292 --- /dev/null +++ b/doc/source/dev/core-dev/vendored-code.rst.inc @@ -0,0 +1,27 @@ +.. _vendored-code: + +Vendored Code +============= +Many parts of the SciPy codebase are maintained elsewhere, and vendored in SciPy. +Some of these parts are vendored as git submodules, for example, ``boost_math``. + +Other parts are not vendored as git submodules, despite having a maintained upstream. +This is mainly for historical reasons, and it is possible that some of these parts +will see patches contributed upstream and become git submodules in the future. + +Maintainers should be careful to *not* accept contributions +(especially trivial changes) into parts of SciPy where the code is actively maintained +upstream. Instead, they should direct contributors to the upstream repo. +Currently, this includes the following parts of the codebase: + +- ARPACK_, at ``scipy/sparse/linalg/_eigen/arpack/ARPACK`` +- SuperLU_, at ``scipy/sparse/linalg/_dsolve/SuperLU`` +- QHull_, at ``scipy/spatial/qhull`` +- trlib_, at ``scipy/optimize/_trlib`` +- UNU.RAN_, at ``scipy/stats/_unuran`` + +.. _ARPACK: https://github.com/opencollab/arpack-ng +.. _SuperLU: https://github.com/xiaoyeli/superlu +.. _QHull: https://github.com/qhull/qhull +.. _trlib: https://github.com/felixlen/trlib +.. _UNU.RAN: https://statmath.wu.ac.at/unuran/ From e33e6986eb898f01b59e32acca07b1862bcec505 Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Tue, 23 Apr 2024 14:57:33 +0100 Subject: [PATCH 041/500] DEP: linalg: remove turbo / eigvals kwargs from linalg.{eigh,eigvalsh} and switch to kwarg-only --- scipy/linalg/_decomp.py | 61 ++++--------------------------- scipy/linalg/tests/test_decomp.py | 48 ------------------------ 2 files changed, 8 insertions(+), 101 deletions(-) diff --git a/scipy/linalg/_decomp.py b/scipy/linalg/_decomp.py index 6b8a1e4e20f9..c520d6b04b6b 100644 --- a/scipy/linalg/_decomp.py +++ b/scipy/linalg/_decomp.py @@ -16,8 +16,6 @@ 'eig_banded', 'eigvals_banded', 'eigh_tridiagonal', 'eigvalsh_tridiagonal', 'hessenberg', 'cdf2rdf'] -import warnings - import numpy as np from numpy import (array, isfinite, inexact, nonzero, iscomplexobj, flatnonzero, conj, asarray, argsort, empty, @@ -26,7 +24,6 @@ from scipy._lib._util import _asarray_validated from ._misc import LinAlgError, _datacopied, norm from .lapack import get_lapack_funcs, _compute_lwork -from scipy._lib.deprecation import _NoValue, _deprecate_positional_args _I = np.array(1j, dtype='F') @@ -283,11 +280,9 @@ def eig(a, b=None, left=False, right=True, overwrite_a=False, return w, vr -@_deprecate_positional_args(version="1.14.0") def eigh(a, b=None, *, lower=True, eigvals_only=False, overwrite_a=False, - overwrite_b=False, turbo=_NoValue, eigvals=_NoValue, type=1, - check_finite=True, subset_by_index=None, subset_by_value=None, - driver=None): + overwrite_b=False, type=1, check_finite=True, subset_by_index=None, + subset_by_value=None, driver=None): """ Solve a standard or generalized eigenvalue problem for a complex Hermitian or real symmetric matrix. @@ -355,16 +350,6 @@ def eigh(a, b=None, *, lower=True, eigvals_only=False, overwrite_a=False, 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. - turbo : bool, optional, deprecated - .. deprecated:: 1.5.0 - `eigh` keyword argument `turbo` is deprecated in favour of - ``driver=gvd`` keyword instead and will be removed in SciPy - 1.14.0. - eigvals : tuple (lo, hi), optional, deprecated - .. deprecated:: 1.5.0 - `eigh` keyword argument `eigvals` is deprecated in favour of - `subset_by_index` keyword instead and will be removed in SciPy - 1.14.0. Returns ------- @@ -460,17 +445,6 @@ def eigh(a, b=None, *, lower=True, eigvals_only=False, overwrite_a=False, (5, 1) """ - if turbo is not _NoValue: - warnings.warn("Keyword argument 'turbo' is deprecated in favour of '" - "driver=gvd' keyword instead and will be removed in " - "SciPy 1.14.0.", - DeprecationWarning, stacklevel=2) - if eigvals is not _NoValue: - warnings.warn("Keyword argument 'eigvals' is deprecated in favour of " - "'subset_by_index' keyword instead and will be removed " - "in SciPy 1.14.0.", - DeprecationWarning, stacklevel=2) - # set lower uplo = 'L' if lower else 'U' # Set job for Fortran routines @@ -516,19 +490,12 @@ def eigh(a, b=None, *, lower=True, eigvals_only=False, overwrite_a=False, cplx = True if iscomplexobj(b1) else (cplx or False) drv_args.update({'overwrite_b': overwrite_b, 'itype': type}) - # backwards-compatibility handling - subset_by_index = subset_by_index if (eigvals in (None, _NoValue)) else eigvals - subset = (subset_by_index is not None) or (subset_by_value is not None) # Both subsets can't be given if subset_by_index and subset_by_value: raise ValueError('Either index or value subset can be requested.') - # Take turbo into account if all conditions are met otherwise ignore - if turbo not in (None, _NoValue) and b is not None: - driver = 'gvx' if subset else 'gvd' - # Check indices if given if subset_by_index: lo, hi = (int(x) for x in subset_by_index) @@ -943,11 +910,9 @@ def eigvals(a, b=None, overwrite_a=False, check_finite=True, homogeneous_eigvals=homogeneous_eigvals) -@_deprecate_positional_args(version="1.14.0") def eigvalsh(a, b=None, *, lower=True, overwrite_a=False, - overwrite_b=False, turbo=_NoValue, eigvals=_NoValue, type=1, - check_finite=True, subset_by_index=None, subset_by_value=None, - driver=None): + overwrite_b=False, type=1, check_finite=True, subset_by_index=None, + subset_by_value=None, driver=None): """ Solves a standard or generalized eigenvalue problem for a complex Hermitian or real symmetric matrix. @@ -1010,15 +975,6 @@ def eigvalsh(a, b=None, *, lower=True, overwrite_a=False, "evd", "evr", "evx" for standard problems and "gv", "gvd", "gvx" for generalized (where b is not None) problems. See the Notes section of `scipy.linalg.eigh`. - turbo : bool, optional, deprecated - .. deprecated:: 1.5.0 - 'eigvalsh' keyword argument `turbo` is deprecated in favor of - ``driver=gvd`` option and will be removed in SciPy 1.14.0. - - eigvals : tuple (lo, hi), optional - .. deprecated:: 1.5.0 - 'eigvalsh' keyword argument `eigvals` is deprecated in favor of - `subset_by_index` option and will be removed in SciPy 1.14.0. Returns ------- @@ -1066,11 +1022,10 @@ def eigvalsh(a, b=None, *, lower=True, overwrite_a=False, array([-3.74637491, -0.76263923, 6.08502336, 12.42399079]) """ - return eigh(a, b=b, lower=lower, eigvals_only=True, - overwrite_a=overwrite_a, overwrite_b=overwrite_b, - turbo=turbo, eigvals=eigvals, type=type, - check_finite=check_finite, subset_by_index=subset_by_index, - subset_by_value=subset_by_value, driver=driver) + return eigh(a, b=b, lower=lower, eigvals_only=True, overwrite_a=overwrite_a, + overwrite_b=overwrite_b, type=type, check_finite=check_finite, + subset_by_index=subset_by_index, subset_by_value=subset_by_value, + driver=driver) def eigvals_banded(a_band, lower=False, overwrite_a_band=False, diff --git a/scipy/linalg/tests/test_decomp.py b/scipy/linalg/tests/test_decomp.py index 04d8576e7007..5e171965a4bd 100644 --- a/scipy/linalg/tests/test_decomp.py +++ b/scipy/linalg/tests/test_decomp.py @@ -844,31 +844,15 @@ def test_wrong_inputs(self): # Both value and index subsets requested assert_raises(ValueError, eigh, np.ones([3, 3]), np.ones([3, 3]), subset_by_value=[1, 2], subset_by_index=[2, 4]) - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "Keyword argument 'eigvals") - assert_raises(ValueError, eigh, np.ones([3, 3]), np.ones([3, 3]), - subset_by_value=[1, 2], eigvals=[2, 4]) # Invalid upper index spec assert_raises(ValueError, eigh, np.ones([3, 3]), np.ones([3, 3]), subset_by_index=[0, 4]) - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "Keyword argument 'eigvals") - assert_raises(ValueError, eigh, np.ones([3, 3]), np.ones([3, 3]), - eigvals=[0, 4]) # Invalid lower index assert_raises(ValueError, eigh, np.ones([3, 3]), np.ones([3, 3]), subset_by_index=[-2, 2]) - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "Keyword argument 'eigvals") - assert_raises(ValueError, eigh, np.ones([3, 3]), np.ones([3, 3]), - eigvals=[-2, 2]) # Invalid index spec #2 assert_raises(ValueError, eigh, np.ones([3, 3]), np.ones([3, 3]), subset_by_index=[2, 0]) - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "Keyword argument 'eigvals") - assert_raises(ValueError, eigh, np.ones([3, 3]), np.ones([3, 3]), - subset_by_index=[2, 0]) # Invalid value spec assert_raises(ValueError, eigh, np.ones([3, 3]), np.ones([3, 3]), subset_by_value=[2, 0]) @@ -964,38 +948,6 @@ def test_eigvalsh_new_args(self): assert_equal(len(w3), 2) assert_allclose(w3, np.array([1.2, 1.3])) - @pytest.mark.parametrize("method", [eigh, eigvalsh]) - def test_deprecation_warnings(self, method): - with pytest.warns(DeprecationWarning, - match="Keyword argument 'turbo'"): - method(np.zeros((2, 2)), turbo=True) - with pytest.warns(DeprecationWarning, - match="Keyword argument 'eigvals'"): - method(np.zeros((2, 2)), eigvals=[0, 1]) - with pytest.deprecated_call(match="use keyword arguments"): - method(np.zeros((2,2)), np.eye(2, 2), True) - - def test_deprecation_results(self): - a = _random_hermitian_matrix(3) - b = _random_hermitian_matrix(3, posdef=True) - - # check turbo gives same result as driver='gvd' - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "Keyword argument 'turbo'") - w_dep, v_dep = eigh(a, b, turbo=True) - w, v = eigh(a, b, driver='gvd') - assert_allclose(w_dep, w) - assert_allclose(v_dep, v) - - # check eigvals gives the same result as subset_by_index - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "Keyword argument 'eigvals'") - w_dep, v_dep = eigh(a, eigvals=[0, 1]) - w, v = eigh(a, subset_by_index=[0, 1]) - assert_allclose(w_dep, w) - assert_allclose(v_dep, v) - - @pytest.mark.parametrize('dt', [int, float, np.float32, complex, np.complex64]) def test_empty(self, dt): a = np.empty((0, 0), dtype=dt) From dcd9e935bda596ce9c2176bf24e5ce0301fdab64 Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Wed, 24 Apr 2024 07:36:40 +0100 Subject: [PATCH 042/500] DEP: linalg: remove cond / rcond kwargs from linalg.pinv and switch to kwarg-only --- scipy/linalg/_basic.py | 30 +----------------------------- scipy/linalg/tests/test_basic.py | 16 ---------------- 2 files changed, 1 insertion(+), 45 deletions(-) diff --git a/scipy/linalg/_basic.py b/scipy/linalg/_basic.py index f64c29ef013e..84e4cafdc4a6 100644 --- a/scipy/linalg/_basic.py +++ b/scipy/linalg/_basic.py @@ -14,7 +14,6 @@ from . import _decomp, _decomp_svd from ._solve_toeplitz import levinson from ._cythonized_array_utils import find_det_from_lu -from scipy._lib.deprecation import _NoValue, _deprecate_positional_args __all__ = ['solve', 'solve_triangular', 'solveh_banded', 'solve_banded', 'solve_toeplitz', 'solve_circulant', 'inv', 'det', 'lstsq', @@ -1345,9 +1344,7 @@ def lstsq(a, b, cond=None, overwrite_a=False, overwrite_b=False, lstsq.default_lapack_driver = 'gelsd' -@_deprecate_positional_args(version="1.14") -def pinv(a, *, atol=None, rtol=None, return_rank=False, check_finite=True, - cond=_NoValue, rcond=_NoValue): +def pinv(a, *, atol=None, rtol=None, return_rank=False, check_finite=True): """ Compute the (Moore-Penrose) pseudo-inverse of a matrix. @@ -1381,20 +1378,6 @@ def pinv(a, *, atol=None, rtol=None, return_rank=False, check_finite=True, Whether to check that the input matrix contains 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. - cond, rcond : float, optional - In older versions, these values were meant to be used as ``atol`` with - ``rtol=0``. If both were given ``rcond`` overwrote ``cond`` and hence - the code was not correct. Thus using these are strongly discouraged and - the tolerances above are recommended instead. In fact, if provided, - atol, rtol takes precedence over these keywords. - - .. deprecated:: 1.7.0 - Deprecated in favor of ``rtol`` and ``atol`` parameters above and - will be removed in SciPy 1.14.0. - - .. versionchanged:: 1.3.0 - Previously the default cutoff value was just ``eps*f`` where ``f`` - was ``1e3`` for single precision and ``1e6`` for double precision. Returns ------- @@ -1465,17 +1448,6 @@ def pinv(a, *, atol=None, rtol=None, return_rank=False, check_finite=True, t = u.dtype.char.lower() maxS = np.max(s, initial=0.) - if rcond is not _NoValue or cond is not _NoValue: - warn('Use of the "cond" and "rcond" keywords are deprecated and ' - 'will be removed in SciPy 1.14.0. Use "atol" and ' - '"rtol" keywords instead', DeprecationWarning, stacklevel=2) - - # backwards compatible only atol and rtol are both missing - if ((rcond not in (_NoValue, None) or cond not in (_NoValue, None)) - and (atol is None) and (rtol is None)): - atol = rcond if rcond not in (_NoValue, None) else cond - rtol = 0. - atol = 0. if atol is None else atol rtol = max(a.shape) * np.finfo(t).eps if (rtol is None) else rtol diff --git a/scipy/linalg/tests/test_basic.py b/scipy/linalg/tests/test_basic.py index a99b127f4780..4449ad83f772 100644 --- a/scipy/linalg/tests/test_basic.py +++ b/scipy/linalg/tests/test_basic.py @@ -20,7 +20,6 @@ from scipy.linalg._testutils import assert_no_overwrite from scipy._lib._testutils import check_free_memory, IS_MUSL from scipy.linalg.blas import HAS_ILP64 -from scipy._lib.deprecation import _NoValue REAL_DTYPES = (np.float32, np.float64, np.longdouble) COMPLEX_DTYPES = (np.complex64, np.complex128, np.clongdouble) @@ -1535,21 +1534,6 @@ def test_atol_rtol(self): assert_allclose(np.linalg.norm(adiff1), 4.233, rtol=0.01) assert_allclose(np.linalg.norm(adiff2), 4.233, rtol=0.01) - @pytest.mark.parametrize("cond", [1, None, _NoValue]) - @pytest.mark.parametrize("rcond", [1, None, _NoValue]) - def test_cond_rcond_deprecation(self, cond, rcond): - if cond is _NoValue and rcond is _NoValue: - # the defaults if cond/rcond aren't set -> no warning - pinv(np.ones((2,2)), cond=cond, rcond=rcond) - else: - # at least one of cond/rcond has a user-supplied value -> warn - with pytest.deprecated_call(match='"cond" and "rcond"'): - pinv(np.ones((2,2)), cond=cond, rcond=rcond) - - def test_positional_deprecation(self): - with pytest.deprecated_call(match="use keyword arguments"): - pinv(np.ones((2,2)), 0., 1e-10) - @pytest.mark.parametrize('dt', [float, np.float32, complex, np.complex64]) def test_empty(self, dt): a = np.empty((0, 0), dtype=dt) From 3b1c1f06d728a421748b43c5c65c9ca72f444f77 Mon Sep 17 00:00:00 2001 From: Matti Picus Date: Wed, 24 Apr 2024 22:31:38 +1000 Subject: [PATCH 043/500] update openblas to 0.3.27 --- tools/openblas_support.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/openblas_support.py b/tools/openblas_support.py index bdb42f3da230..37b60dd69e55 100644 --- a/tools/openblas_support.py +++ b/tools/openblas_support.py @@ -13,8 +13,8 @@ from urllib.request import urlopen, Request from urllib.error import HTTPError -OPENBLAS_V = '0.3.26.dev' -OPENBLAS_LONG = 'v0.3.26-382-gb1e8ba50' +OPENBLAS_V = '0.3.27' +OPENBLAS_LONG = 'v0.3.27' BASE_LOC = 'https://anaconda.org/multibuild-wheels-staging/openblas-libs' NIGHTLY_BASE_LOC = ( 'https://anaconda.org/scientific-python-nightly-wheels/openblas-libs' From 91ac9d79249c58ff9ce3ead47092a003c0e429e6 Mon Sep 17 00:00:00 2001 From: lucascolley Date: Wed, 24 Apr 2024 22:01:27 +0100 Subject: [PATCH 044/500] DEV: add unicode check to pre-commit-hook [lint only] --- tools/pre-commit-hook.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/pre-commit-hook.py b/tools/pre-commit-hook.py index 5d78b77fa304..4b25e2074e9f 100755 --- a/tools/pre-commit-hook.py +++ b/tools/pre-commit-hook.py @@ -20,6 +20,14 @@ linter = [f for f in linters if os.path.exists(f)][0] +unicode_checks = [ + '../../tools/unicode-check.py', + 'tools/unicode-check.py', + 'unicode-check.py' # in case pre-commit hook is run from tools dir +] + +unicode_check = [f for f in unicode_checks if os.path.exists(f)][0] + # names of files that were staged # add '*.pxd', '*.pxi' once cython-lint supports it @@ -86,3 +94,8 @@ print(' ./tools/pre-commit-hook.py --fix') sys.exit(p.returncode) + +p = subprocess.run(unicode_check, cwd=work_dir) + +if p.returncode != 0: + sys.exit(p.returncode) From d4c46ded1f6269b0451b072f04d9a546cf736136 Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Thu, 25 Apr 2024 10:34:52 +0100 Subject: [PATCH 045/500] DEP: signal: remove nyq / Hz kwargs in firwin* and switch to kwarg-only --- scipy/signal/_fir_filter_design.py | 78 +++----------------- scipy/signal/tests/test_fir_filter_design.py | 55 -------------- 2 files changed, 11 insertions(+), 122 deletions(-) diff --git a/scipy/signal/_fir_filter_design.py b/scipy/signal/_fir_filter_design.py index 3982ff749cc0..3d1c9c13dc26 100644 --- a/scipy/signal/_fir_filter_design.py +++ b/scipy/signal/_fir_filter_design.py @@ -10,7 +10,6 @@ from scipy.special import sinc from scipy.linalg import (toeplitz, hankel, solve, LinAlgError, LinAlgWarning, lstsq) -from scipy._lib.deprecation import _NoValue, _deprecate_positional_args from scipy.signal._arraytools import _validate_fs from . import _sigtools @@ -19,25 +18,6 @@ 'firwin', 'firwin2', 'remez', 'firls', 'minimum_phase'] -def _get_fs(fs, nyq): - """ - Utility for replacing the argument 'nyq' (with default 1) with 'fs'. - """ - if nyq is _NoValue and fs is None: - fs = 2 - elif nyq is not _NoValue: - if fs is not None: - raise ValueError("Values cannot be given for both 'nyq' and 'fs'.") - msg = ("Keyword argument 'nyq' is deprecated in favour of 'fs' and " - "will be removed in SciPy 1.14.0.") - warnings.warn(msg, DeprecationWarning, stacklevel=3) - if nyq is None: - fs = 2 - else: - fs = 2*nyq - return fs - - # Some notes on function parameters: # # `cutoff` and `width` are given as numbers between 0 and 1. These are @@ -268,9 +248,8 @@ def kaiserord(ripple, width): return int(ceil(numtaps)), beta -@_deprecate_positional_args(version="1.14") def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, - scale=True, nyq=_NoValue, fs=None): + scale=True, fs=None): """ FIR filter design using the window method. @@ -320,13 +299,6 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, `fs/2` (i.e the filter is a single band highpass filter); center of first passband otherwise - nyq : float, optional, deprecated - This is the Nyquist frequency. Each frequency in `cutoff` must be - between 0 and `nyq`. Default is 1. - - .. deprecated:: 1.0.0 - `firwin` keyword argument `nyq` is deprecated in favour of `fs` and - will be removed in SciPy 1.14.0. fs : float, optional The sampling frequency of the signal. Each frequency in `cutoff` must be between 0 and ``fs/2``. Default is 2. @@ -397,8 +369,9 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, # 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 * _get_fs(fs, nyq) + nyq = 0.5 * fs cutoff = np.atleast_1d(cutoff) / float(nyq) @@ -493,8 +466,7 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, # Original version of firwin2 from scipy ticket #457, submitted by "tash". # # Rewritten by Warren Weckesser, 2010. -@_deprecate_positional_args(version="1.14") -def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', nyq=_NoValue, +def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', antisymmetric=False, fs=None): """ FIR filter design using the window method. @@ -529,13 +501,6 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', nyq=_NoValue, Window function to use. Default is "hamming". See `scipy.signal.get_window` for the complete list of possible values. If None, no window function is applied. - nyq : float, optional, deprecated - This is the Nyquist frequency. Each frequency in `freq` must be - between 0 and `nyq`. Default is 1. - - .. deprecated:: 1.0.0 - `firwin2` keyword argument `nyq` is deprecated in favour of `fs` and - will be removed in SciPy 1.14.0. antisymmetric : bool, optional Whether resulting impulse response is symmetric/antisymmetric. See Notes for more details. @@ -603,7 +568,8 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', nyq=_NoValue, """ fs = _validate_fs(fs, allow_none=True) - nyq = 0.5 * _get_fs(fs, nyq) + fs = 2 if fs is None else fs + nyq = 0.5 * fs if len(freq) != len(gain): raise ValueError('freq and gain must be of same length.') @@ -697,8 +663,7 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', nyq=_NoValue, return out -@_deprecate_positional_args(version="1.14") -def remez(numtaps, bands, desired, *, weight=None, Hz=_NoValue, type='bandpass', +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. @@ -723,12 +688,6 @@ def remez(numtaps, bands, desired, *, weight=None, Hz=_NoValue, type='bandpass', 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`. - Hz : scalar, optional, deprecated - The sampling frequency in Hz. Default is 1. - - .. deprecated:: 1.0.0 - `remez` keyword argument `Hz` is deprecated in favour of `fs` and - will be removed in SciPy 1.14.0. type : {'bandpass', 'differentiator', 'hilbert'}, optional The type of filter: @@ -857,15 +816,7 @@ def remez(numtaps, bands, desired, *, weight=None, Hz=_NoValue, type='bandpass', """ fs = _validate_fs(fs, allow_none=True) - if Hz is _NoValue and fs is None: - fs = 1.0 - elif Hz is not _NoValue: - if fs is not None: - raise ValueError("Values cannot be given for both 'Hz' and 'fs'.") - msg = ("'remez' keyword argument 'Hz' is deprecated in favour of 'fs'" - " and will be removed in SciPy 1.14.0.") - warnings.warn(msg, DeprecationWarning, stacklevel=2) - fs = Hz + fs = 1.0 if fs is None else fs # Convert type try: @@ -883,8 +834,7 @@ def remez(numtaps, bands, desired, *, weight=None, Hz=_NoValue, type='bandpass', maxiter, grid_density) -@_deprecate_positional_args(version="1.14") -def firls(numtaps, bands, desired, *, weight=None, nyq=_NoValue, fs=None): +def firls(numtaps, bands, desired, *, weight=None, fs=None): """ FIR filter design using least-squares error minimization. @@ -914,13 +864,6 @@ def firls(numtaps, bands, desired, *, weight=None, nyq=_NoValue, fs=None): A relative weighting to give to each band region when solving the least squares problem. `weight` has to be half the size of `bands`. - nyq : float, optional, deprecated - This is the Nyquist frequency. Each frequency in `bands` must be - between 0 and `nyq` (inclusive). Default is 1. - - .. deprecated:: 1.0.0 - `firls` keyword argument `nyq` is deprecated in favour of `fs` and - will be removed in SciPy 1.14.0. fs : float, optional The sampling frequency of the signal. Each frequency in `bands` must be between 0 and ``fs/2`` (inclusive). Default is 2. @@ -1002,7 +945,8 @@ def firls(numtaps, bands, desired, *, weight=None, nyq=_NoValue, fs=None): """ fs = _validate_fs(fs, allow_none=True) - nyq = 0.5 * _get_fs(fs, nyq) + fs = 2 if fs is None else fs + nyq = 0.5 * fs numtaps = int(numtaps) if numtaps % 2 == 0 or numtaps < 1: diff --git a/scipy/signal/tests/test_fir_filter_design.py b/scipy/signal/tests/test_fir_filter_design.py index ea50d402d7c6..d9fc22982965 100644 --- a/scipy/signal/tests/test_fir_filter_design.py +++ b/scipy/signal/tests/test_fir_filter_design.py @@ -234,11 +234,6 @@ def test_fs_nyq(self): freqs, response = freqz(taps, worN=np.pi*freq_samples/nyquist) assert_array_almost_equal(np.abs(response), [0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0], decimal=5) - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "Keyword argument 'nyq'") - taps2 = firwin(ntaps, cutoff=[300, 700], window=('kaiser', beta), - pass_zero=False, scale=False, nyq=nyquist) - assert_allclose(taps2, taps) def test_bad_cutoff(self): """Test that invalid cutoff argument raises ValueError.""" @@ -256,10 +251,6 @@ def test_bad_cutoff(self): # 2D array not allowed. assert_raises(ValueError, firwin, 99, [[0.1, 0.2],[0.3, 0.4]]) # cutoff values must be less than nyq. - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "Keyword argument 'nyq'") - assert_raises(ValueError, firwin, 99, 50.0, nyq=40) - assert_raises(ValueError, firwin, 99, [10, 20, 30], nyq=25) assert_raises(ValueError, firwin, 99, 50.0, fs=80) assert_raises(ValueError, firwin, 99, [10, 20, 30], fs=50) @@ -282,12 +273,6 @@ def test_bad_pass_zero(self): with assert_raises(ValueError, match='must have at least two'): firwin(41, [0.5], pass_zero=pass_zero) - def test_firwin_deprecations(self): - with pytest.deprecated_call(match="argument 'nyq' is deprecated"): - firwin(1, 1, nyq=10) - with pytest.deprecated_call(match="use keyword arguments"): - firwin(58, 0.1, 0.03) - def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): firwin2(51, .5, 1, fs=np.array([10, 20])) @@ -427,10 +412,6 @@ def test_fs_nyq(self): taps1 = firwin2(80, [0.0, 0.5, 1.0], [1.0, 1.0, 0.0]) taps2 = firwin2(80, [0.0, 30.0, 60.0], [1.0, 1.0, 0.0], fs=120.0) assert_array_almost_equal(taps1, taps2) - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "Keyword argument 'nyq'") - taps2 = firwin2(80, [0.0, 30.0, 60.0], [1.0, 1.0, 0.0], nyq=60.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)) @@ -443,13 +424,6 @@ def test_input_modyfication(self): firwin2(80, freq1, [1.0, 1.0, 0.0, 0.0]) assert_equal(freq1, freq2) - def test_firwin2_deprecations(self): - with pytest.deprecated_call(match="argument 'nyq' is deprecated"): - firwin2(1, [0, 10], [1, 1], nyq=10) - with pytest.deprecated_call(match="use keyword arguments"): - # from test04 - firwin2(5, [0.0, 0.5, 0.5, 1.0], [1.0, 1.0, 0.0, 0.0], 8193, None) - class TestRemez: @@ -491,10 +465,6 @@ def test_compare(self): -0.003530911231040, 0.193140296954975, 0.373400753484939, 0.373400753484939, 0.193140296954975, -0.003530911231040, -0.075943803756711, -0.041314581814658, 0.024590270518440] - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "'remez'") - h = remez(12, [0, 0.3, 0.5, 1], [1, 0], Hz=2.) - assert_allclose(h, k) h = remez(12, [0, 0.3, 0.5, 1], [1, 0], fs=2.) assert_allclose(h, k) @@ -505,18 +475,8 @@ def test_compare(self): 0.129770906801075, -0.103908158578635, 0.073641298245579, -0.043276706138248, 0.016849978528150, 0.002879152556419, -0.014644062687875, 0.018704846485491, -0.038976016082299] - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "'remez'") - assert_allclose(remez(21, [0, 0.8, 0.9, 1], [0, 1], Hz=2.), h) assert_allclose(remez(21, [0, 0.8, 0.9, 1], [0, 1], fs=2.), h) - def test_remez_deprecations(self): - with pytest.deprecated_call(match="'remez' keyword argument 'Hz'"): - remez(12, [0, 0.3, 0.5, 1], [1, 0], Hz=2.) - with pytest.deprecated_call(match="use keyword arguments"): - # from test_hilbert - remez(11, [0.1, 0.4], [1], None) - def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): remez(11, .1, 1, fs=np.array([10, 20])) @@ -607,14 +567,6 @@ def test_compare(self): 1.156090832768218] assert_allclose(taps, known_taps) - with np.testing.suppress_warnings() as sup: - sup.filter(DeprecationWarning, "Keyword argument 'nyq'") - taps = firls(7, (0, 1, 2, 3, 4, 5), [1, 0, 0, 1, 1, 0], nyq=10) - assert_allclose(taps, known_taps) - - with pytest.raises(ValueError, match='between 0 and 1'): - firls(7, [0, 1], [0, 1], nyq=0.5) - def test_rank_deficient(self): # solve() runs but warns (only sometimes, so here we don't use match) x = firls(21, [0, 0.1, 0.9, 1], [1, 1, 0, 0]) @@ -633,13 +585,6 @@ def test_rank_deficient(self): assert mask.sum() > 3 assert_allclose(np.abs(h[mask]), 0., atol=1e-4) - def test_firls_deprecations(self): - with pytest.deprecated_call(match="argument 'nyq' is deprecated"): - firls(1, (0, 1), (0, 0), nyq=10) - with pytest.deprecated_call(match="use keyword arguments"): - # from test_firls - firls(11, [0, 0.1, 0.4, 0.5], [1, 1, 0, 0], None) - def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): firls(11, .1, 1, fs=np.array([10, 20])) From 73f719a37c57e1e64406f976de92e4574fdcf0de Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Tue, 23 Apr 2024 14:57:30 +0200 Subject: [PATCH 046/500] MAINT: vendor Tempita in `scipy/_build_utils` This is an unmodified copy from `Cython/Tempita/` as of commit `023d4af35` in Cython's master branch (21 April 2024). Tempita hasn't been maintained independently on PyPI for 10+ years; we've used the Cython version which is semi-public (undocumented) for a while, with the note that we should vendor it if that ever fails. It now failed once (see scipy#20535), and conceptually that makes sense - this is the only place where we don't invoke the `cython` executable but actually do `import Cython`. That can fail in a distro setup where Cython is installed for a different Python interpreter than the active one. The license file was taken over from the `maintenance/1.26.x` branch in the `numpy` repo - which came from the original upstream repo for Tempita (now disappeared) as discussed in numpy#8096. See scipy#20572 for more clarification about the MIT license that this code is under. --- LICENSES_bundled.txt | 5 + scipy/_build_utils/tempita.py | 4 +- scipy/_build_utils/tempita/LICENSE.txt | 20 + scipy/_build_utils/tempita/__init__.py | 4 + scipy/_build_utils/tempita/_looper.py | 156 ++++ scipy/_build_utils/tempita/_tempita.py | 1092 ++++++++++++++++++++++++ tools/lint.toml | 5 +- 7 files changed, 1282 insertions(+), 4 deletions(-) create mode 100644 scipy/_build_utils/tempita/LICENSE.txt create mode 100644 scipy/_build_utils/tempita/__init__.py create mode 100644 scipy/_build_utils/tempita/_looper.py create mode 100644 scipy/_build_utils/tempita/_tempita.py diff --git a/LICENSES_bundled.txt b/LICENSES_bundled.txt index 9a3943898822..4828b64e30aa 100644 --- a/LICENSES_bundled.txt +++ b/LICENSES_bundled.txt @@ -281,3 +281,8 @@ Name: array-api-compat Files: scipy/_lib/array-api-compat/* License: MIT For details, see scipy/_lib/array-api-compat/LICENCE + +Name: Tempita +Files: scipy/_build_utils/tempita/* +License: MIT + For details, see scipy/_build_utils/tempita/LICENCE.txt diff --git a/scipy/_build_utils/tempita.py b/scipy/_build_utils/tempita.py index b9e74032a026..a08374dd7c65 100644 --- a/scipy/_build_utils/tempita.py +++ b/scipy/_build_utils/tempita.py @@ -3,9 +3,7 @@ import os import argparse -from Cython import Tempita as tempita -# XXX: If this import ever fails (does it really?), vendor either -# cython.tempita or numpy/npy_tempita. +import tempita def process_tempita(fromfile, outfile=None): diff --git a/scipy/_build_utils/tempita/LICENSE.txt b/scipy/_build_utils/tempita/LICENSE.txt new file mode 100644 index 000000000000..0ba6f23c440f --- /dev/null +++ b/scipy/_build_utils/tempita/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008 Ian Bicking and Contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/scipy/_build_utils/tempita/__init__.py b/scipy/_build_utils/tempita/__init__.py new file mode 100644 index 000000000000..41a0ce3d0efa --- /dev/null +++ b/scipy/_build_utils/tempita/__init__.py @@ -0,0 +1,4 @@ +# The original Tempita implements all of its templating code here. +# Moved it to _tempita.py to make the compilation portable. + +from ._tempita import * diff --git a/scipy/_build_utils/tempita/_looper.py b/scipy/_build_utils/tempita/_looper.py new file mode 100644 index 000000000000..4864f2949605 --- /dev/null +++ b/scipy/_build_utils/tempita/_looper.py @@ -0,0 +1,156 @@ +""" +Helper for looping over sequences, particular in templates. + +Often in a loop in a template it's handy to know what's next up, +previously up, if this is the first or last item in the sequence, etc. +These can be awkward to manage in a normal Python loop, but using the +looper you can get a better sense of the context. Use like:: + + >>> for loop, item in looper(['a', 'b', 'c']): + ... print loop.number, item + ... if not loop.last: + ... print '---' + 1 a + --- + 2 b + --- + 3 c + +""" + +basestring_ = (bytes, str) + +__all__ = ['looper'] + + +class looper: + """ + Helper for looping (particularly in templates) + + Use this like:: + + for loop, item in looper(seq): + if loop.first: + ... + """ + + def __init__(self, seq): + self.seq = seq + + def __iter__(self): + return looper_iter(self.seq) + + def __repr__(self): + return '<%s for %r>' % ( + self.__class__.__name__, self.seq) + + +class looper_iter: + + def __init__(self, seq): + self.seq = list(seq) + self.pos = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.pos >= len(self.seq): + raise StopIteration + result = loop_pos(self.seq, self.pos), self.seq[self.pos] + self.pos += 1 + return result + + +class loop_pos: + + def __init__(self, seq, pos): + self.seq = seq + self.pos = pos + + def __repr__(self): + return '' % ( + self.seq[self.pos], self.pos) + + def index(self): + return self.pos + index = property(index) + + def number(self): + return self.pos + 1 + number = property(number) + + def item(self): + return self.seq[self.pos] + item = property(item) + + def __next__(self): + try: + return self.seq[self.pos + 1] + except IndexError: + return None + __next__ = property(__next__) + + def previous(self): + if self.pos == 0: + return None + return self.seq[self.pos - 1] + previous = property(previous) + + def odd(self): + return not self.pos % 2 + odd = property(odd) + + def even(self): + return self.pos % 2 + even = property(even) + + def first(self): + return self.pos == 0 + first = property(first) + + def last(self): + return self.pos == len(self.seq) - 1 + last = property(last) + + def length(self): + return len(self.seq) + length = property(length) + + def first_group(self, getter=None): + """ + Returns true if this item is the start of a new group, + where groups mean that some attribute has changed. The getter + can be None (the item itself changes), an attribute name like + ``'.attr'``, a function, or a dict key or list index. + """ + if self.first: + return True + return self._compare_group(self.item, self.previous, getter) + + def last_group(self, getter=None): + """ + Returns true if this item is the end of a new group, + where groups mean that some attribute has changed. The getter + can be None (the item itself changes), an attribute name like + ``'.attr'``, a function, or a dict key or list index. + """ + if self.last: + return True + return self._compare_group(self.item, self.__next__, getter) + + def _compare_group(self, item, other, getter): + if getter is None: + return item != other + elif (isinstance(getter, basestring_) + and getter.startswith('.')): + getter = getter[1:] + if getter.endswith('()'): + getter = getter[:-2] + return getattr(item, getter)() != getattr(other, getter)() + else: + return getattr(item, getter) != getattr(other, getter) + elif hasattr(getter, '__call__'): + return getter(item) != getter(other) + else: + return item[getter] != other[getter] diff --git a/scipy/_build_utils/tempita/_tempita.py b/scipy/_build_utils/tempita/_tempita.py new file mode 100644 index 000000000000..c5269f25ff39 --- /dev/null +++ b/scipy/_build_utils/tempita/_tempita.py @@ -0,0 +1,1092 @@ +""" +A small templating language + +This implements a small templating language. This language implements +if/elif/else, for/continue/break, expressions, and blocks of Python +code. The syntax is:: + + {{any expression (function calls etc)}} + {{any expression | filter}} + {{for x in y}}...{{endfor}} + {{if x}}x{{elif y}}y{{else}}z{{endif}} + {{py:x=1}} + {{py: + def foo(bar): + return 'baz' + }} + {{default var = default_value}} + {{# comment}} + +You use this with the ``Template`` class or the ``sub`` shortcut. +The ``Template`` class takes the template string and the name of +the template (for errors) and a default namespace. Then (like +``string.Template``) you can call the ``tmpl.substitute(**kw)`` +method to make a substitution (or ``tmpl.substitute(a_dict)``). + +``sub(content, **kw)`` substitutes the template immediately. You +can use ``__name='tmpl.html'`` to set the name of the template. + +If there are syntax errors ``TemplateError`` will be raised. +""" + + +import re +import sys +import os +import tokenize +from io import StringIO + +from ._looper import looper + +__all__ = ['TemplateError', 'Template', 'sub', 'bunch'] + +in_re = re.compile(r'\s+in\s+') +var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I) +basestring_ = (bytes, str) + +def coerce_text(v): + if not isinstance(v, basestring_): + if hasattr(v, '__str__'): + return str(v) + else: + return bytes(v) + return v + +class TemplateError(Exception): + """Exception raised while parsing a template + """ + + def __init__(self, message, position, name=None): + Exception.__init__(self, message) + self.position = position + self.name = name + + def __str__(self): + msg = ' '.join(self.args) + if self.position: + msg = '%s at line %s column %s' % ( + msg, self.position[0], self.position[1]) + if self.name: + msg += ' in %s' % self.name + return msg + + +class _TemplateContinue(Exception): + pass + + +class _TemplateBreak(Exception): + pass + + +def get_file_template(name, from_template): + path = os.path.join(os.path.dirname(from_template.name), name) + return from_template.__class__.from_filename( + path, namespace=from_template.namespace, + get_template=from_template.get_template) + + +class Template: + + default_namespace = { + 'start_braces': '{{', + 'end_braces': '}}', + 'looper': looper, + } + + default_encoding = 'utf8' + default_inherit = None + + def __init__(self, content, name=None, namespace=None, stacklevel=None, + get_template=None, default_inherit=None, line_offset=0, + delimiters=None, delimeters=None): + self.content = content + + # set delimiters + if delimeters: + import warnings + warnings.warn( + "'delimeters' kwarg is being deprecated in favor of correctly" + " spelled 'delimiters'. Please adjust your code.", + DeprecationWarning + ) + if delimiters is None: + delimiters = delimeters + if delimiters is None: + delimiters = (self.default_namespace['start_braces'], + self.default_namespace['end_braces']) + else: + #assert len(delimiters) == 2 and all([isinstance(delimiter, basestring) + # for delimiter in delimiters]) + self.default_namespace = self.__class__.default_namespace.copy() + self.default_namespace['start_braces'] = delimiters[0] + self.default_namespace['end_braces'] = delimiters[1] + self.delimiters = self.delimeters = delimiters # Keep a legacy read-only copy, but don't use it. + + self._unicode = isinstance(content, str) + if name is None and stacklevel is not None: + try: + caller = sys._getframe(stacklevel) + except ValueError: + pass + else: + globals = caller.f_globals + lineno = caller.f_lineno + if '__file__' in globals: + name = globals['__file__'] + if name.endswith('.pyc') or name.endswith('.pyo'): + name = name[:-1] + elif '__name__' in globals: + name = globals['__name__'] + else: + name = '' + if lineno: + name += ':%s' % lineno + self.name = name + self._parsed = parse(content, name=name, line_offset=line_offset, delimiters=self.delimiters) + if namespace is None: + namespace = {} + self.namespace = namespace + self.get_template = get_template + if default_inherit is not None: + self.default_inherit = default_inherit + + def from_filename(cls, filename, namespace=None, encoding=None, + default_inherit=None, get_template=get_file_template): + with open(filename, 'rb') as f: + c = f.read() + if encoding: + c = c.decode(encoding) + return cls(content=c, name=filename, namespace=namespace, + default_inherit=default_inherit, get_template=get_template) + + from_filename = classmethod(from_filename) + + def __repr__(self): + return '<%s %s name=%r>' % ( + self.__class__.__name__, + hex(id(self))[2:], self.name) + + def substitute(self, *args, **kw): + if args: + if kw: + raise TypeError( + "You can only give positional *or* keyword arguments") + if len(args) > 1: + raise TypeError( + "You can only give one positional argument") + if not hasattr(args[0], 'items'): + raise TypeError( + "If you pass in a single argument, you must pass in a dictionary-like object (with a .items() method); you gave %r" + % (args[0],)) + kw = args[0] + ns = kw + ns['__template_name__'] = self.name + if self.namespace: + ns.update(self.namespace) + result, defs, inherit = self._interpret(ns) + if not inherit: + inherit = self.default_inherit + if inherit: + result = self._interpret_inherit(result, defs, inherit, ns) + return result + + def _interpret(self, ns): + __traceback_hide__ = True + parts = [] + defs = {} + self._interpret_codes(self._parsed, ns, out=parts, defs=defs) + if '__inherit__' in defs: + inherit = defs.pop('__inherit__') + else: + inherit = None + return ''.join(parts), defs, inherit + + def _interpret_inherit(self, body, defs, inherit_template, ns): + __traceback_hide__ = True + if not self.get_template: + raise TemplateError( + 'You cannot use inheritance without passing in get_template', + position=None, name=self.name) + templ = self.get_template(inherit_template, self) + self_ = TemplateObject(self.name) + for name, value in defs.items(): + setattr(self_, name, value) + self_.body = body + ns = ns.copy() + ns['self'] = self_ + return templ.substitute(ns) + + def _interpret_codes(self, codes, ns, out, defs): + __traceback_hide__ = True + for item in codes: + if isinstance(item, basestring_): + out.append(item) + else: + self._interpret_code(item, ns, out, defs) + + def _interpret_code(self, code, ns, out, defs): + __traceback_hide__ = True + name, pos = code[0], code[1] + if name == 'py': + self._exec(code[2], ns, pos) + elif name == 'continue': + raise _TemplateContinue() + elif name == 'break': + raise _TemplateBreak() + elif name == 'for': + vars, expr, content = code[2], code[3], code[4] + expr = self._eval(expr, ns, pos) + self._interpret_for(vars, expr, content, ns, out, defs) + elif name == 'cond': + parts = code[2:] + self._interpret_if(parts, ns, out, defs) + elif name == 'expr': + parts = code[2].split('|') + base = self._eval(parts[0], ns, pos) + for part in parts[1:]: + func = self._eval(part, ns, pos) + base = func(base) + out.append(self._repr(base, pos)) + elif name == 'default': + var, expr = code[2], code[3] + if var not in ns: + result = self._eval(expr, ns, pos) + ns[var] = result + elif name == 'inherit': + expr = code[2] + value = self._eval(expr, ns, pos) + defs['__inherit__'] = value + elif name == 'def': + name = code[2] + signature = code[3] + parts = code[4] + ns[name] = defs[name] = TemplateDef(self, name, signature, body=parts, ns=ns, + pos=pos) + elif name == 'comment': + return + else: + assert 0, "Unknown code: %r" % name + + def _interpret_for(self, vars, expr, content, ns, out, defs): + __traceback_hide__ = True + for item in expr: + if len(vars) == 1: + ns[vars[0]] = item + else: + if len(vars) != len(item): + raise ValueError( + 'Need %i items to unpack (got %i items)' + % (len(vars), len(item))) + for name, value in zip(vars, item): + ns[name] = value + try: + self._interpret_codes(content, ns, out, defs) + except _TemplateContinue: + continue + except _TemplateBreak: + break + + def _interpret_if(self, parts, ns, out, defs): + __traceback_hide__ = True + # @@: if/else/else gets through + for part in parts: + assert not isinstance(part, basestring_) + name, pos = part[0], part[1] + if name == 'else': + result = True + else: + result = self._eval(part[2], ns, pos) + if result: + self._interpret_codes(part[3], ns, out, defs) + break + + def _eval(self, code, ns, pos): + __traceback_hide__ = True + try: + try: + value = eval(code, self.default_namespace, ns) + except SyntaxError as e: + raise SyntaxError( + 'invalid syntax in expression: %s' % code) + return value + except Exception as e: + if getattr(e, 'args', None): + arg0 = e.args[0] + else: + arg0 = coerce_text(e) + e.args = (self._add_line_info(arg0, pos),) + raise + + def _exec(self, code, ns, pos): + __traceback_hide__ = True + try: + exec(code, self.default_namespace, ns) + except Exception as e: + if e.args: + e.args = (self._add_line_info(e.args[0], pos),) + else: + e.args = (self._add_line_info(None, pos),) + raise + + def _repr(self, value, pos): + __traceback_hide__ = True + try: + if value is None: + return '' + if self._unicode: + try: + value = str(value) + except UnicodeDecodeError: + value = bytes(value) + else: + if not isinstance(value, basestring_): + value = coerce_text(value) + if (isinstance(value, str) + and self.default_encoding): + value = value.encode(self.default_encoding) + except Exception as e: + e.args = (self._add_line_info(e.args[0], pos),) + raise + else: + if self._unicode and isinstance(value, bytes): + if not self.default_encoding: + raise UnicodeDecodeError( + 'Cannot decode bytes value %r into unicode ' + '(no default_encoding provided)' % value) + try: + value = value.decode(self.default_encoding) + except UnicodeDecodeError as e: + raise UnicodeDecodeError( + e.encoding, + e.object, + e.start, + e.end, + e.reason + ' in string %r' % value) + elif not self._unicode and isinstance(value, str): + if not self.default_encoding: + raise UnicodeEncodeError( + 'Cannot encode unicode value %r into bytes ' + '(no default_encoding provided)' % value) + value = value.encode(self.default_encoding) + return value + + def _add_line_info(self, msg, pos): + msg = "%s at line %s column %s" % ( + msg, pos[0], pos[1]) + if self.name: + msg += " in file %s" % self.name + return msg + + +def sub(content, delimiters=None, **kw): + name = kw.get('__name') + delimeters = kw.pop('delimeters') if 'delimeters' in kw else None # for legacy code + tmpl = Template(content, name=name, delimiters=delimiters, delimeters=delimeters) + return tmpl.substitute(kw) + + +def paste_script_template_renderer(content, vars, filename=None): + tmpl = Template(content, name=filename) + return tmpl.substitute(vars) + + +class bunch(dict): + + def __init__(self, **kw): + for name, value in kw.items(): + setattr(self, name, value) + + def __setattr__(self, name, value): + self[name] = value + + def __getattr__(self, name): + try: + return self[name] + except KeyError: + raise AttributeError(name) + + def __getitem__(self, key): + if 'default' in self: + try: + return dict.__getitem__(self, key) + except KeyError: + return dict.__getitem__(self, 'default') + else: + return dict.__getitem__(self, key) + + def __repr__(self): + return '<%s %s>' % ( + self.__class__.__name__, + ' '.join(['%s=%r' % (k, v) for k, v in sorted(self.items())])) + + +class TemplateDef: + def __init__(self, template, func_name, func_signature, + body, ns, pos, bound_self=None): + self._template = template + self._func_name = func_name + self._func_signature = func_signature + self._body = body + self._ns = ns + self._pos = pos + self._bound_self = bound_self + + def __repr__(self): + return '' % ( + self._func_name, self._func_signature, + self._template.name, self._pos) + + def __str__(self): + return self() + + def __call__(self, *args, **kw): + values = self._parse_signature(args, kw) + ns = self._ns.copy() + ns.update(values) + if self._bound_self is not None: + ns['self'] = self._bound_self + out = [] + subdefs = {} + self._template._interpret_codes(self._body, ns, out, subdefs) + return ''.join(out) + + def __get__(self, obj, type=None): + if obj is None: + return self + return self.__class__( + self._template, self._func_name, self._func_signature, + self._body, self._ns, self._pos, bound_self=obj) + + def _parse_signature(self, args, kw): + values = {} + sig_args, var_args, var_kw, defaults = self._func_signature + extra_kw = {} + for name, value in kw.items(): + if not var_kw and name not in sig_args: + raise TypeError( + 'Unexpected argument %s' % name) + if name in sig_args: + values[sig_args] = value + else: + extra_kw[name] = value + args = list(args) + sig_args = list(sig_args) + while args: + while sig_args and sig_args[0] in values: + sig_args.pop(0) + if sig_args: + name = sig_args.pop(0) + values[name] = args.pop(0) + elif var_args: + values[var_args] = tuple(args) + break + else: + raise TypeError( + 'Extra position arguments: %s' + % ', '.join([repr(v) for v in args])) + for name, value_expr in defaults.items(): + if name not in values: + values[name] = self._template._eval( + value_expr, self._ns, self._pos) + for name in sig_args: + if name not in values: + raise TypeError( + 'Missing argument: %s' % name) + if var_kw: + values[var_kw] = extra_kw + return values + + +class TemplateObject: + + def __init__(self, name): + self.__name = name + self.get = TemplateObjectGetter(self) + + def __repr__(self): + return '<%s %s>' % (self.__class__.__name__, self.__name) + + +class TemplateObjectGetter: + + def __init__(self, template_obj): + self.__template_obj = template_obj + + def __getattr__(self, attr): + return getattr(self.__template_obj, attr, Empty) + + def __repr__(self): + return '<%s around %r>' % (self.__class__.__name__, self.__template_obj) + + +class _Empty: + def __call__(self, *args, **kw): + return self + + def __str__(self): + return '' + + def __repr__(self): + return 'Empty' + + def __unicode__(self): + return '' + + def __iter__(self): + return iter(()) + + def __bool__(self): + return False + +Empty = _Empty() +del _Empty + +############################################################ +## Lexing and Parsing +############################################################ + + +def lex(s, name=None, trim_whitespace=True, line_offset=0, delimiters=None): + """ + Lex a string into chunks: + + >>> lex('hey') + ['hey'] + >>> lex('hey {{you}}') + ['hey ', ('you', (1, 7))] + >>> lex('hey {{') + Traceback (most recent call last): + ... + TemplateError: No }} to finish last expression at line 1 column 7 + >>> lex('hey }}') + Traceback (most recent call last): + ... + TemplateError: }} outside expression at line 1 column 7 + >>> lex('hey {{ {{') + Traceback (most recent call last): + ... + TemplateError: {{ inside expression at line 1 column 10 + + """ + if delimiters is None: + delimiters = ( Template.default_namespace['start_braces'], + Template.default_namespace['end_braces'] ) + in_expr = False + chunks = [] + last = 0 + last_pos = (line_offset + 1, 1) + + token_re = re.compile(r'%s|%s' % (re.escape(delimiters[0]), + re.escape(delimiters[1]))) + for match in token_re.finditer(s): + expr = match.group(0) + pos = find_position(s, match.end(), last, last_pos) + if expr == delimiters[0] and in_expr: + raise TemplateError('%s inside expression' % delimiters[0], + position=pos, + name=name) + elif expr == delimiters[1] and not in_expr: + raise TemplateError('%s outside expression' % delimiters[1], + position=pos, + name=name) + if expr == delimiters[0]: + part = s[last:match.start()] + if part: + chunks.append(part) + in_expr = True + else: + chunks.append((s[last:match.start()], last_pos)) + in_expr = False + last = match.end() + last_pos = pos + if in_expr: + raise TemplateError('No %s to finish last expression' % delimiters[1], + name=name, position=last_pos) + part = s[last:] + if part: + chunks.append(part) + if trim_whitespace: + chunks = trim_lex(chunks) + return chunks + +statement_re = re.compile(r'^(?:if |elif |for |def |inherit |default |py:)') +single_statements = ['else', 'endif', 'endfor', 'enddef', 'continue', 'break'] +trail_whitespace_re = re.compile(r'\n\r?[\t ]*$') +lead_whitespace_re = re.compile(r'^[\t ]*\n') + + +def trim_lex(tokens): + r""" + Takes a lexed set of tokens, and removes whitespace when there is + a directive on a line by itself: + + >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False) + >>> tokens + [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny'] + >>> trim_lex(tokens) + [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y'] + """ + last_trim = None + for i, current in enumerate(tokens): + if isinstance(current, basestring_): + # we don't trim this + continue + item = current[0] + if not statement_re.search(item) and item not in single_statements: + continue + if not i: + prev = '' + else: + prev = tokens[i - 1] + if i + 1 >= len(tokens): + next_chunk = '' + else: + next_chunk = tokens[i + 1] + if (not isinstance(next_chunk, basestring_) + or not isinstance(prev, basestring_)): + continue + prev_ok = not prev or trail_whitespace_re.search(prev) + if i == 1 and not prev.strip(): + prev_ok = True + if last_trim is not None and last_trim + 2 == i and not prev.strip(): + prev_ok = 'last' + if (prev_ok + and (not next_chunk or lead_whitespace_re.search(next_chunk) + or (i == len(tokens) - 2 and not next_chunk.strip()))): + if prev: + if ((i == 1 and not prev.strip()) + or prev_ok == 'last'): + tokens[i - 1] = '' + else: + m = trail_whitespace_re.search(prev) + # +1 to leave the leading \n on: + prev = prev[:m.start() + 1] + tokens[i - 1] = prev + if next_chunk: + last_trim = i + if i == len(tokens) - 2 and not next_chunk.strip(): + tokens[i + 1] = '' + else: + m = lead_whitespace_re.search(next_chunk) + next_chunk = next_chunk[m.end():] + tokens[i + 1] = next_chunk + return tokens + + +def find_position(string, index, last_index, last_pos): + """Given a string and index, return (line, column)""" + lines = string.count('\n', last_index, index) + if lines > 0: + column = index - string.rfind('\n', last_index, index) + else: + column = last_pos[1] + (index - last_index) + return (last_pos[0] + lines, column) + + +def parse(s, name=None, line_offset=0, delimiters=None): + r""" + Parses a string into a kind of AST + + >>> parse('{{x}}') + [('expr', (1, 3), 'x')] + >>> parse('foo') + ['foo'] + >>> parse('{{if x}}test{{endif}}') + [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))] + >>> parse('series->{{for x in y}}x={{x}}{{endfor}}') + ['series->', ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])] + >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}') + [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])] + >>> parse('{{py:x=1}}') + [('py', (1, 3), 'x=1')] + >>> parse('{{if x}}a{{elif y}}b{{else}}c{{endif}}') + [('cond', (1, 3), ('if', (1, 3), 'x', ['a']), ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))] + + Some exceptions:: + + >>> parse('{{continue}}') + Traceback (most recent call last): + ... + TemplateError: continue outside of for loop at line 1 column 3 + >>> parse('{{if x}}foo') + Traceback (most recent call last): + ... + TemplateError: No {{endif}} at line 1 column 3 + >>> parse('{{else}}') + Traceback (most recent call last): + ... + TemplateError: else outside of an if block at line 1 column 3 + >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}') + Traceback (most recent call last): + ... + TemplateError: Unexpected endif at line 1 column 25 + >>> parse('{{if}}{{endif}}') + Traceback (most recent call last): + ... + TemplateError: if with no expression at line 1 column 3 + >>> parse('{{for x y}}{{endfor}}') + Traceback (most recent call last): + ... + TemplateError: Bad for (no "in") in 'x y' at line 1 column 3 + >>> parse('{{py:x=1\ny=2}}') + Traceback (most recent call last): + ... + TemplateError: Multi-line py blocks must start with a newline at line 1 column 3 + """ + if delimiters is None: + delimiters = ( Template.default_namespace['start_braces'], + Template.default_namespace['end_braces'] ) + tokens = lex(s, name=name, line_offset=line_offset, delimiters=delimiters) + result = [] + while tokens: + next_chunk, tokens = parse_expr(tokens, name) + result.append(next_chunk) + return result + + +def parse_expr(tokens, name, context=()): + if isinstance(tokens[0], basestring_): + return tokens[0], tokens[1:] + expr, pos = tokens[0] + expr = expr.strip() + if expr.startswith('py:'): + expr = expr[3:].lstrip(' \t') + if expr.startswith('\n') or expr.startswith('\r'): + expr = expr.lstrip('\r\n') + if '\r' in expr: + expr = expr.replace('\r\n', '\n') + expr = expr.replace('\r', '') + expr += '\n' + else: + if '\n' in expr: + raise TemplateError( + 'Multi-line py blocks must start with a newline', + position=pos, name=name) + return ('py', pos, expr), tokens[1:] + elif expr in ('continue', 'break'): + if 'for' not in context: + raise TemplateError( + 'continue outside of for loop', + position=pos, name=name) + return (expr, pos), tokens[1:] + elif expr.startswith('if '): + return parse_cond(tokens, name, context) + elif (expr.startswith('elif ') + or expr == 'else'): + raise TemplateError( + '%s outside of an if block' % expr.split()[0], + position=pos, name=name) + elif expr in ('if', 'elif', 'for'): + raise TemplateError( + '%s with no expression' % expr, + position=pos, name=name) + elif expr in ('endif', 'endfor', 'enddef'): + raise TemplateError( + 'Unexpected %s' % expr, + position=pos, name=name) + elif expr.startswith('for '): + return parse_for(tokens, name, context) + elif expr.startswith('default '): + return parse_default(tokens, name, context) + elif expr.startswith('inherit '): + return parse_inherit(tokens, name, context) + elif expr.startswith('def '): + return parse_def(tokens, name, context) + elif expr.startswith('#'): + return ('comment', pos, tokens[0][0]), tokens[1:] + return ('expr', pos, tokens[0][0]), tokens[1:] + + +def parse_cond(tokens, name, context): + start = tokens[0][1] + pieces = [] + context = context + ('if',) + while 1: + if not tokens: + raise TemplateError( + 'Missing {{endif}}', + position=start, name=name) + if (isinstance(tokens[0], tuple) + and tokens[0][0] == 'endif'): + return ('cond', start) + tuple(pieces), tokens[1:] + next_chunk, tokens = parse_one_cond(tokens, name, context) + pieces.append(next_chunk) + + +def parse_one_cond(tokens, name, context): + (first, pos), tokens = tokens[0], tokens[1:] + content = [] + if first.endswith(':'): + first = first[:-1] + if first.startswith('if '): + part = ('if', pos, first[3:].lstrip(), content) + elif first.startswith('elif '): + part = ('elif', pos, first[5:].lstrip(), content) + elif first == 'else': + part = ('else', pos, None, content) + else: + assert 0, "Unexpected token %r at %s" % (first, pos) + while 1: + if not tokens: + raise TemplateError( + 'No {{endif}}', + position=pos, name=name) + if (isinstance(tokens[0], tuple) + and (tokens[0][0] == 'endif' + or tokens[0][0].startswith('elif ') + or tokens[0][0] == 'else')): + return part, tokens + next_chunk, tokens = parse_expr(tokens, name, context) + content.append(next_chunk) + + +def parse_for(tokens, name, context): + first, pos = tokens[0] + tokens = tokens[1:] + context = ('for',) + context + content = [] + assert first.startswith('for '), first + if first.endswith(':'): + first = first[:-1] + first = first[3:].strip() + match = in_re.search(first) + if not match: + raise TemplateError( + 'Bad for (no "in") in %r' % first, + position=pos, name=name) + vars = first[:match.start()] + if '(' in vars: + raise TemplateError( + 'You cannot have () in the variable section of a for loop (%r)' + % vars, position=pos, name=name) + vars = tuple([ + v.strip() for v in first[:match.start()].split(',') + if v.strip()]) + expr = first[match.end():] + while 1: + if not tokens: + raise TemplateError( + 'No {{endfor}}', + position=pos, name=name) + if (isinstance(tokens[0], tuple) + and tokens[0][0] == 'endfor'): + return ('for', pos, vars, expr, content), tokens[1:] + next_chunk, tokens = parse_expr(tokens, name, context) + content.append(next_chunk) + + +def parse_default(tokens, name, context): + first, pos = tokens[0] + assert first.startswith('default ') + first = first.split(None, 1)[1] + parts = first.split('=', 1) + if len(parts) == 1: + raise TemplateError( + "Expression must be {{default var=value}}; no = found in %r" % first, + position=pos, name=name) + var = parts[0].strip() + if ',' in var: + raise TemplateError( + "{{default x, y = ...}} is not supported", + position=pos, name=name) + if not var_re.search(var): + raise TemplateError( + "Not a valid variable name for {{default}}: %r" + % var, position=pos, name=name) + expr = parts[1].strip() + return ('default', pos, var, expr), tokens[1:] + + +def parse_inherit(tokens, name, context): + first, pos = tokens[0] + assert first.startswith('inherit ') + expr = first.split(None, 1)[1] + return ('inherit', pos, expr), tokens[1:] + + +def parse_def(tokens, name, context): + first, start = tokens[0] + tokens = tokens[1:] + assert first.startswith('def ') + first = first.split(None, 1)[1] + if first.endswith(':'): + first = first[:-1] + if '(' not in first: + func_name = first + sig = ((), None, None, {}) + elif not first.endswith(')'): + raise TemplateError("Function definition doesn't end with ): %s" % first, + position=start, name=name) + else: + first = first[:-1] + func_name, sig_text = first.split('(', 1) + sig = parse_signature(sig_text, name, start) + context = context + ('def',) + content = [] + while 1: + if not tokens: + raise TemplateError( + 'Missing {{enddef}}', + position=start, name=name) + if (isinstance(tokens[0], tuple) + and tokens[0][0] == 'enddef'): + return ('def', start, func_name, sig, content), tokens[1:] + next_chunk, tokens = parse_expr(tokens, name, context) + content.append(next_chunk) + + +def parse_signature(sig_text, name, pos): + tokens = tokenize.generate_tokens(StringIO(sig_text).readline) + sig_args = [] + var_arg = None + var_kw = None + defaults = {} + + def get_token(pos=False): + try: + tok_type, tok_string, (srow, scol), (erow, ecol), line = next(tokens) + except StopIteration: + return tokenize.ENDMARKER, '' + if pos: + return tok_type, tok_string, (srow, scol), (erow, ecol) + else: + return tok_type, tok_string + while 1: + var_arg_type = None + tok_type, tok_string = get_token() + if tok_type == tokenize.ENDMARKER: + break + if tok_type == tokenize.OP and (tok_string == '*' or tok_string == '**'): + var_arg_type = tok_string + tok_type, tok_string = get_token() + if tok_type != tokenize.NAME: + raise TemplateError('Invalid signature: (%s)' % sig_text, + position=pos, name=name) + var_name = tok_string + tok_type, tok_string = get_token() + if tok_type == tokenize.ENDMARKER or (tok_type == tokenize.OP and tok_string == ','): + if var_arg_type == '*': + var_arg = var_name + elif var_arg_type == '**': + var_kw = var_name + else: + sig_args.append(var_name) + if tok_type == tokenize.ENDMARKER: + break + continue + if var_arg_type is not None: + raise TemplateError('Invalid signature: (%s)' % sig_text, + position=pos, name=name) + if tok_type == tokenize.OP and tok_string == '=': + nest_type = None + unnest_type = None + nest_count = 0 + start_pos = end_pos = None + parts = [] + while 1: + tok_type, tok_string, s, e = get_token(True) + if start_pos is None: + start_pos = s + end_pos = e + if tok_type == tokenize.ENDMARKER and nest_count: + raise TemplateError('Invalid signature: (%s)' % sig_text, + position=pos, name=name) + if (not nest_count and + (tok_type == tokenize.ENDMARKER or (tok_type == tokenize.OP and tok_string == ','))): + default_expr = isolate_expression(sig_text, start_pos, end_pos) + defaults[var_name] = default_expr + sig_args.append(var_name) + break + parts.append((tok_type, tok_string)) + if nest_count and tok_type == tokenize.OP and tok_string == nest_type: + nest_count += 1 + elif nest_count and tok_type == tokenize.OP and tok_string == unnest_type: + nest_count -= 1 + if not nest_count: + nest_type = unnest_type = None + elif not nest_count and tok_type == tokenize.OP and tok_string in ('(', '[', '{'): + nest_type = tok_string + nest_count = 1 + unnest_type = {'(': ')', '[': ']', '{': '}'}[nest_type] + return sig_args, var_arg, var_kw, defaults + + +def isolate_expression(string, start_pos, end_pos): + srow, scol = start_pos + srow -= 1 + erow, ecol = end_pos + erow -= 1 + lines = string.splitlines(True) + if srow == erow: + return lines[srow][scol:ecol] + parts = [lines[srow][scol:]] + parts.extend(lines[srow+1:erow]) + if erow < len(lines): + # It'll sometimes give (end_row_past_finish, 0) + parts.append(lines[erow][:ecol]) + return ''.join(parts) + +_fill_command_usage = """\ +%prog [OPTIONS] TEMPLATE arg=value + +Use py:arg=value to set a Python value; otherwise all values are +strings. +""" + + +def fill_command(args=None): + import sys + import optparse + import pkg_resources + import os + if args is None: + args = sys.argv[1:] + dist = pkg_resources.get_distribution('Paste') + parser = optparse.OptionParser( + version=coerce_text(dist), + usage=_fill_command_usage) + parser.add_option( + '-o', '--output', + dest='output', + metavar="FILENAME", + help="File to write output to (default stdout)") + parser.add_option( + '--env', + dest='use_env', + action='store_true', + help="Put the environment in as top-level variables") + options, args = parser.parse_args(args) + if len(args) < 1: + print('You must give a template filename') + sys.exit(2) + template_name = args[0] + args = args[1:] + vars = {} + if options.use_env: + vars.update(os.environ) + for value in args: + if '=' not in value: + print('Bad argument: %r' % value) + sys.exit(2) + name, value = value.split('=', 1) + if name.startswith('py:'): + name = name[:3] + value = eval(value) + vars[name] = value + if template_name == '-': + template_content = sys.stdin.read() + template_name = '' + else: + with open(template_name, 'rb') as f: + template_content = f.read() + template = Template(template_content, name=template_name) + result = template.substitute(vars) + if options.output: + with open(options.output, 'wb') as f: + f.write(result) + else: + sys.stdout.write(result) + +if __name__ == '__main__': + fill_command() diff --git a/tools/lint.toml b/tools/lint.toml index 69f7e9e2e812..c392dc59dd16 100644 --- a/tools/lint.toml +++ b/tools/lint.toml @@ -1,4 +1,7 @@ -exclude = ["scipy/datasets/_registry.py"] +exclude = [ + "scipy/_build_utils/tempita/*", + "scipy/datasets/_registry.py", +] line-length = 88 From 42da1b44f4f853ef6a2cf9f9ff1901aaa9877e4c Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Thu, 25 Apr 2024 14:22:05 +0200 Subject: [PATCH 047/500] DEV: ensure `python dev.py lint` honors the excluded files in lint.toml Co-authored-by: Lucas Colley --- tools/lint.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/lint.toml b/tools/lint.toml index c392dc59dd16..4470891b85b1 100644 --- a/tools/lint.toml +++ b/tools/lint.toml @@ -3,6 +3,8 @@ exclude = [ "scipy/datasets/_registry.py", ] +force-exclude = true + line-length = 88 # Assume Python 3.9 From 13e0cc50fadf1e7753990d21124351f97792e822 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Thu, 25 Apr 2024 15:31:35 +0200 Subject: [PATCH 048/500] BLD: move the `optimize` build steps earlier into the build sequence This makes the build more balanced, since HiGHS takes a long time to build and finishes last otherwise. --- scipy/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/meson.build b/scipy/meson.build index 8ba9c6e3ceea..56dba5de4001 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -538,6 +538,7 @@ subdir('sparse') subdir('stats') subdir('fft') subdir('io') +subdir('optimize') subdir('spatial') subdir('cluster') subdir('constants') @@ -547,6 +548,5 @@ subdir('signal') subdir('interpolate') subdir('ndimage') subdir('odr') -subdir('optimize') subdir('datasets') subdir('misc') From ebd1bc9125cf3d3b9d8685f1e8f961c6b78c5744 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Thu, 25 Apr 2024 17:45:57 +0200 Subject: [PATCH 049/500] BENCH: disable very slow benchmark in `optimize_milp.py` This was no slower after the update to HiGHS (verified locally) in PR 19255. However it takes very long to run and was already near the limit for CircleCI. The tracking issue for this already existed as well: scipy#19389 [skip actions] [skip cirrus] --- benchmarks/benchmarks/optimize_milp.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/benchmarks/benchmarks/optimize_milp.py b/benchmarks/benchmarks/optimize_milp.py index f59ebeb081e4..ff7cce24e52f 100644 --- a/benchmarks/benchmarks/optimize_milp.py +++ b/benchmarks/benchmarks/optimize_milp.py @@ -48,7 +48,7 @@ def setup(self, prob): self.integrality = integrality def time_milp(self, prob): - # TODO: fix this benchmark (timing out in Aug. 2023) + # TODO: fix this benchmark (timing out in Aug. 2023); see gh-19389 # res = milp(c=self.c, constraints=self.constraints, bounds=self.bounds, # integrality=self.integrality) # assert res.success @@ -57,8 +57,9 @@ def time_milp(self, prob): class MilpMagicSquare(Benchmark): - # TODO: re-add 6, timing out in Aug. 2023 - params = [[3, 4, 5]] + # TODO: look at 5,6 - timing out and disabled in Apr'24 (5) and Aug'23 (6) + # see gh-19389 for details + params = [[3, 4]] param_names = ['size'] def setup(self, n): From fb1db9d108fe9427565fa4a1ae6b1cef5d7e5d7c Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 25 Apr 2024 15:25:55 -0700 Subject: [PATCH 050/500] TST: stats.rv_continuous.fit: adjust fit XSLOW/XFAIL/skip sets --- scipy/stats/tests/test_continuous_basic.py | 79 +++++++++++++--------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/scipy/stats/tests/test_continuous_basic.py b/scipy/stats/tests/test_continuous_basic.py index 01eeea8657e8..cd114facbb51 100644 --- a/scipy/stats/tests/test_continuous_basic.py +++ b/scipy/stats/tests/test_continuous_basic.py @@ -51,21 +51,22 @@ xslow_test_moments = {'studentized_range', 'ksone', 'vonmises', 'vonmises_line', 'recipinvgauss', 'kstwo', 'kappa4'} -xslow_fit_all = {'ksone', 'kstwo', 'levy_stable', 'recipinvgauss', 'studentized_range', - 'gausshyper', 'kappa4', 'vonmises_line', 'genhyperbolic', 'ncx2', - 'powerlognorm', 'genexpon'} -xfail_fit_all = {'trapezoid', 'truncpareto'} -xslow_fit_mm = {'argus', 'beta', 'exponpow', 'exponweib', 'gengamma', +xslow_fit_mle = {'gausshyper', 'ncf', 'ncx2', 'vonmises_line'} +xfail_fit_mle = {'ksone', 'kstwo', 'trapezoid', 'truncpareto'} +skip_fit_mle = {'levy_stable', 'studentized_range'} # far too slow (>10min) +xslow_fit_mm = {'argus', 'beta', 'exponpow', 'gausshyper', 'gengamma', 'genhalflogistic', 'geninvgauss', 'gompertz', 'halfgennorm', - 'johnsonsb', 'johnsonsu', 'powernorm', 'vonmises', - 'truncnorm', 'kstwobign', 'rel_breitwigner', 'wrapcauchy', - 'johnsonsu', 'truncweibull_min', 'truncexpon', 'norminvgauss'} -xslow_fit_mle = {'ncf'} -xfail_fit_mm = {'alpha', 'betaprime', 'burr', 'burr12', 'cauchy', 'crystalball', 'f', - 'fisk', 'foldcauchy', 'genextreme', 'genpareto', 'halfcauchy', - 'invgamma', 'jf_skew_t', 'kappa3', 'levy', 'levy_l', 'loglaplace', - 'lomax', 'mielke', 'ncf', 'nct', 'pareto', 'skewcauchy', 't', - 'bradford', 'tukeylambda'} + 'johnsonsb', 'kstwobign', 'ncx2', 'norminvgauss', 'truncnorm', + 'truncweibull_min', 'wrapcauchy'} +xfail_fit_mm = {'alpha', 'betaprime', 'bradford', 'burr', 'burr12', 'cauchy', + 'crystalball', 'exponweib', 'f', 'fisk', 'foldcauchy', 'genextreme', + 'genpareto', 'halfcauchy', 'invgamma', 'jf_skew_t', 'johnsonsu', + 'kappa3', 'kappa4', 'levy', 'levy_l', 'loglaplace', 'lomax', 'mielke', + 'ncf', 'nct', 'pareto', 'powerlognorm', 'powernorm', 'rel_breitwigner', + 'skewcauchy', 't', 'trapezoid', '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 @@ -199,31 +200,43 @@ def test_cont_basic(distname, arg, sn): def cases_test_cont_basic_fit(): - message = "Test fails and may be slow" - fail_mark = pytest.mark.skip(reason=message) + 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: - if distname in xfail_fit_all: - yield pytest.param(distname, arg, None, None, marks=fail_mark) - continue - if distname in xslow_fit_all: - yield pytest.param(distname, arg, None, None, marks=pytest.mark.xslow) - continue - for method in ["MLE", "MM"]: - if method == 'MM' and distname in xfail_fit_mm: - yield pytest.param(distname, arg, method, None, marks=fail_mark) - continue - if method == 'MM' and distname in xslow_fit_mm: - yield pytest.param(distname, arg, method, None, marks=pytest.mark.xslow) - continue - if method == 'MLE' and distname in xslow_fit_mle: - yield pytest.param(distname, arg, method, None, marks=pytest.mark.xslow) - continue - for fix_args in [True, False]: + 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 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', cases_test_cont_basic_fit()) @pytest.mark.parametrize('n_fit_samples', [200]) From ed1e4ebbdaa00e293d886c60393bfa50595415d2 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 26 Apr 2024 02:30:43 -0700 Subject: [PATCH 051/500] MAINT: optimize.linprog: fix bug when integrality is a list of all zeros (#20586) --- scipy/optimize/_linprog.py | 2 ++ scipy/optimize/tests/test_linprog.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/scipy/optimize/_linprog.py b/scipy/optimize/_linprog.py index 5deb51bd4558..181218217196 100644 --- a/scipy/optimize/_linprog.py +++ b/scipy/optimize/_linprog.py @@ -625,6 +625,8 @@ def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, warn(warning_message, OptimizeWarning, stacklevel=2) elif np.any(integrality): integrality = np.broadcast_to(integrality, np.shape(c)) + else: + integrality = None lp = _LPProblem(c, A_ub, b_ub, A_eq, b_eq, bounds, x0, integrality) lp, solver_options = _parse_linprog(lp, options, meth) diff --git a/scipy/optimize/tests/test_linprog.py b/scipy/optimize/tests/test_linprog.py index f219fcb07f88..63ceaa84049b 100644 --- a/scipy/optimize/tests/test_linprog.py +++ b/scipy/optimize/tests/test_linprog.py @@ -1703,6 +1703,21 @@ def test_bug_10466(self): method=self.method, options=o) assert_allclose(res.fun, -8589934560) + def test_bug_20584(self): + """ + Test that when integrality is a list of all zeros, linprog gives the + same result as when it is an array of all zeros / integrality=None + """ + c = [1, 1] + A_ub = [[-1, 0]] + b_ub = [-2.5] + res1 = linprog(c, A_ub=A_ub, b_ub=b_ub, integrality=[0, 0]) + res2 = linprog(c, A_ub=A_ub, b_ub=b_ub, integrality=np.asarray([0, 0])) + res3 = linprog(c, A_ub=A_ub, b_ub=b_ub, integrality=None) + assert_equal(res1.x, res2.x) + assert_equal(res1.x, res3.x) + + ######################### # Method-specific Tests # ######################### From 76f2d3c04852a5aeb155f7a233ee1538760e033e Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:10:00 +0100 Subject: [PATCH 052/500] DEP: integrate: switch simpson to kwarg-only, remove even kwarg (#20554) --- scipy/integrate/_quadrature.py | 93 +----------------------- scipy/integrate/tests/test_quadrature.py | 35 ++------- 2 files changed, 12 insertions(+), 116 deletions(-) diff --git a/scipy/integrate/_quadrature.py b/scipy/integrate/_quadrature.py index d23d13ee918a..045704204480 100644 --- a/scipy/integrate/_quadrature.py +++ b/scipy/integrate/_quadrature.py @@ -9,8 +9,7 @@ from scipy.special import roots_legendre from scipy.special import gammaln, logsumexp from scipy._lib._util import _rng_spawn -from scipy._lib.deprecation import (_NoValue, _deprecate_positional_args, - _deprecated) +from scipy._lib.deprecation import _deprecated __all__ = ['fixed_quad', 'quadrature', 'romberg', 'romb', @@ -544,8 +543,7 @@ def _basic_simpson(y, start, stop, x, dx, axis): return result -@_deprecate_positional_args(version="1.14") -def simpson(y, *, x=None, dx=1.0, axis=-1, even=_NoValue): +def simpson(y, *, x=None, dx=1.0, axis=-1): """ Integrate y(x) using samples along the given axis and the composite Simpson's rule. If x is None, spacing of dx is assumed. @@ -565,37 +563,6 @@ def simpson(y, *, x=None, dx=1.0, axis=-1, even=_NoValue): `x` is None. Default is 1. axis : int, optional Axis along which to integrate. Default is the last axis. - even : {None, 'simpson', 'avg', 'first', 'last'}, optional - 'avg' : Average two results: - 1) use the first N-2 intervals with - a trapezoidal rule on the last interval and - 2) use the last - N-2 intervals with a trapezoidal rule on the first interval. - - 'first' : Use Simpson's rule for the first N-2 intervals with - a trapezoidal rule on the last interval. - - 'last' : Use Simpson's rule for the last N-2 intervals with a - trapezoidal rule on the first interval. - - None : equivalent to 'simpson' (default) - - 'simpson' : Use Simpson's rule for the first N-2 intervals with the - addition of a 3-point parabolic segment for the last - interval using equations outlined by Cartwright [1]_. - If the axis to be integrated over only has two points then - the integration falls back to a trapezoidal integration. - - .. versionadded:: 1.11.0 - - .. versionchanged:: 1.11.0 - The newly added 'simpson' option is now the default as it is more - accurate in most situations. - - .. deprecated:: 1.11.0 - Parameter `even` is deprecated and will be removed in SciPy - 1.14.0. After this time the behaviour for an even number of - points will follow that of `even='simpson'`. Returns ------- @@ -605,16 +572,12 @@ def simpson(y, *, x=None, dx=1.0, axis=-1, even=_NoValue): See Also -------- quad : adaptive quadrature using QUADPACK - romberg : adaptive Romberg quadrature - quadrature : adaptive Gaussian quadrature fixed_quad : fixed-order Gaussian quadrature dblquad : double integrals tplquad : triple integrals romb : integrators for sampled data cumulative_trapezoid : cumulative integration for sampled data cumulative_simpson : cumulative integration using Simpson's 1/3 rule - ode : ODE integrators - odeint : ODE integrators Notes ----- @@ -645,15 +608,11 @@ def simpson(y, *, x=None, dx=1.0, axis=-1, even=_NoValue): >>> integrate.quad(lambda x: x**3, 0, 9)[0] 1640.25 - >>> integrate.simpson(y, x=x, even='first') - 1644.5 - """ y = np.asarray(y) nd = len(y.shape) N = y.shape[axis] last_dx = dx - first_dx = dx returnshape = 0 if x is not None: x = np.asarray(x) @@ -670,28 +629,11 @@ def simpson(y, *, x=None, dx=1.0, axis=-1, even=_NoValue): raise ValueError("If given, length of x along axis must be the " "same as y.") - # even keyword parameter is deprecated - if even is not _NoValue: - warnings.warn( - "The 'even' keyword is deprecated as of SciPy 1.11.0 and will be " - "removed in SciPy 1.14.0", - DeprecationWarning, stacklevel=2 - ) - if N % 2 == 0: val = 0.0 result = 0.0 slice_all = (slice(None),) * nd - # default is 'simpson' - even = even if even not in (_NoValue, None) else "simpson" - - if even not in ['avg', 'last', 'first', 'simpson']: - raise ValueError( - "Parameter 'even' must be 'simpson', " - "'avg', 'last', or 'first'." - ) - if N == 2: # need at least 3 points in integration axis to form parabolic # segment. If there are two points then any of 'avg', 'first', @@ -701,12 +643,7 @@ def simpson(y, *, x=None, dx=1.0, axis=-1, even=_NoValue): if x is not None: last_dx = x[slice1] - x[slice2] val += 0.5 * last_dx * (y[slice1] + y[slice2]) - - # calculation is finished. Set `even` to None to skip other - # scenarios - even = None - - if even == 'simpson': + else: # use Simpson's rule on first intervals result = _basic_simpson(y, 0, N-3, x, dx, axis) @@ -763,29 +700,7 @@ def simpson(y, *, x=None, dx=1.0, axis=-1, even=_NoValue): result += alpha*y[slice1] + beta*y[slice2] - eta*y[slice3] - # The following code (down to result=result+val) can be removed - # once the 'even' keyword is removed. - - # Compute using Simpson's rule on first intervals - if even in ['avg', 'first']: - slice1 = tupleset(slice_all, axis, -1) - slice2 = tupleset(slice_all, axis, -2) - if x is not None: - last_dx = x[slice1] - x[slice2] - val += 0.5*last_dx*(y[slice1]+y[slice2]) - result = _basic_simpson(y, 0, N-3, x, dx, axis) - # Compute using Simpson's rule on last set of intervals - if even in ['avg', 'last']: - slice1 = tupleset(slice_all, axis, 0) - slice2 = tupleset(slice_all, axis, 1) - if x is not None: - first_dx = x[tuple(slice2)] - x[tuple(slice1)] - val += 0.5*first_dx*(y[slice2]+y[slice1]) - result += _basic_simpson(y, 1, N-2, x, dx, axis) - if even == 'avg': - val /= 2.0 - result /= 2.0 - result = result + val + result += val else: result = _basic_simpson(y, 0, N-2, x, dx, axis) if returnshape: diff --git a/scipy/integrate/tests/test_quadrature.py b/scipy/integrate/tests/test_quadrature.py index 00fc2f6d45a1..87f8d248243a 100644 --- a/scipy/integrate/tests/test_quadrature.py +++ b/scipy/integrate/tests/test_quadrature.py @@ -148,40 +148,29 @@ def test_newton_cotes2(self): numeric_integral = np.dot(wts, y) assert_almost_equal(numeric_integral, exact_integral) - # ignore the DeprecationWarning emitted by the even kwd - @pytest.mark.filterwarnings('ignore::DeprecationWarning') def test_simpson(self): y = np.arange(17) assert_equal(simpson(y), 128) assert_equal(simpson(y, dx=0.5), 64) assert_equal(simpson(y, x=np.linspace(0, 4, 17)), 32) - y = np.arange(4) - x = 2**y - assert_equal(simpson(y, x=x, even='avg'), 13.875) - assert_equal(simpson(y, x=x, even='first'), 13.75) - assert_equal(simpson(y, x=x, even='last'), 14) - - # `even='simpson'` # integral should be exactly 21 x = np.linspace(1, 4, 4) def f(x): return x**2 - assert_allclose(simpson(f(x), x=x, even='simpson'), 21.0) - assert_allclose(simpson(f(x), x=x, even='avg'), 21 + 1/6) + assert_allclose(simpson(f(x), x=x), 21.0) # integral should be exactly 114 x = np.linspace(1, 7, 4) - assert_allclose(simpson(f(x), dx=2.0, even='simpson'), 114) - assert_allclose(simpson(f(x), dx=2.0, even='avg'), 115 + 1/3) + assert_allclose(simpson(f(x), dx=2.0), 114) - # `even='simpson'`, test multi-axis behaviour + # test multi-axis behaviour a = np.arange(16).reshape(4, 4) x = np.arange(64.).reshape(4, 4, 4) y = f(x) for i in range(3): - r = simpson(y, x=x, even='simpson', axis=i) + r = simpson(y, x=x, axis=i) it = np.nditer(a, flags=['multi_index']) for _ in it: idx = list(it.multi_index) @@ -192,11 +181,10 @@ def f(x): # test when integration axis only has two points x = np.arange(16).reshape(8, 2) y = f(x) - for even in ['simpson', 'avg', 'first', 'last']: - r = simpson(y, x=x, even=even, axis=-1) + r = simpson(y, x=x, axis=-1) - integral = 0.5 * (y[:, 1] + y[:, 0]) * (x[:, 1] - x[:, 0]) - assert_allclose(r, integral) + integral = 0.5 * (y[:, 1] + y[:, 0]) * (x[:, 1] - x[:, 0]) + assert_allclose(r, integral) # odd points, test multi-axis behaviour a = np.arange(25).reshape(5, 5) @@ -227,7 +215,7 @@ def f(x): zero_axis = [0.0, 0.0, 0.0, 0.0] default_axis = [170 + 1/3] * 3 # 8**3 / 3 - 1/3 assert_allclose(simpson(y, x=x, axis=0), zero_axis) - # the following should be exact for even='simpson' + # the following should be exact assert_allclose(simpson(y, x=x, axis=-1), default_axis) x = np.array([[1, 2, 4, 8], [1, 2, 4, 8], [1, 8, 16, 32]]) @@ -237,13 +225,6 @@ def f(x): assert_allclose(simpson(y, x=x, axis=0), zero_axis) assert_allclose(simpson(y, x=x, axis=-1), default_axis) - def test_simpson_deprecations(self): - x = np.linspace(0, 3, 4) - y = x**2 - with pytest.deprecated_call(match="The 'even' keyword is deprecated"): - simpson(y, x=x, even='first') - with pytest.deprecated_call(match="use keyword arguments"): - simpson(y, x) @pytest.mark.parametrize('droplast', [False, True]) def test_simpson_2d_integer_no_x(self, droplast): From 38fcc9b2d34d56e8a2b5b5645cf5fdc43b79f82b Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Fri, 26 Apr 2024 10:53:07 +0100 Subject: [PATCH 053/500] DEP: special: remove legacy kwarg from special.comb and switch to kwarg-only --- scipy/special/_basic.py | 32 ++-------------- scipy/special/_special_ufuncs_docs.cpp | 12 +++--- scipy/special/tests/test_basic.py | 53 +++----------------------- 3 files changed, 15 insertions(+), 82 deletions(-) diff --git a/scipy/special/_basic.py b/scipy/special/_basic.py index 86072c5cf46f..7b3fd8106c52 100644 --- a/scipy/special/_basic.py +++ b/scipy/special/_basic.py @@ -18,7 +18,6 @@ _sph_harm_all as _sph_harm_all_gufunc) from . import _specfun from ._comb import _comb_int -from scipy._lib.deprecation import _NoValue, _deprecate_positional_args __all__ = [ @@ -2679,8 +2678,7 @@ def obl_cv_seq(m, n, c): return _specfun.segv(m, n, c, -1)[1][:maxL] -@_deprecate_positional_args(version="1.14") -def comb(N, k, *, exact=False, repetition=False, legacy=_NoValue): +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". @@ -2693,21 +2691,10 @@ def comb(N, k, *, exact=False, repetition=False, legacy=_NoValue): 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. For non-integers, if - `exact` is True, is disregarded. + used, otherwise the result is computed exactly. repetition : bool, optional If `repetition` is True, then the number of combinations with repetition is computed. - legacy : bool, optional - If `legacy` is True and `exact` is True, then non-integral arguments - are cast to ints; if `legacy` is False, the result for non-integral - arguments is unaffected by the value of `exact`. - - .. deprecated:: 1.9.0 - Using `legacy` is deprecated and will removed by - Scipy 1.14.0. If you want to keep the legacy behaviour, cast - your inputs directly, e.g. - ``comb(int(your_N), int(your_k), exact=True)``. Returns ------- @@ -2739,25 +2726,12 @@ def comb(N, k, *, exact=False, repetition=False, legacy=_NoValue): 220 """ - if legacy is not _NoValue: - warnings.warn( - "Using 'legacy' keyword is deprecated and will be removed by " - "Scipy 1.14.0. If you want to keep the legacy behaviour, cast " - "your inputs directly, e.g. " - "'comb(int(your_N), int(your_k), exact=True)'.", - DeprecationWarning, - stacklevel=2 - ) if repetition: - return comb(N + k - 1, k, exact=exact, legacy=legacy) + 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) - elif legacy: - # here at least one number is not an integer; legacy behavior uses - # lossy casts to int - return _comb_int(N, k) # otherwise, we disregard `exact=True`; it makes no sense for # non-integral arguments return comb(N, k) diff --git a/scipy/special/_special_ufuncs_docs.cpp b/scipy/special/_special_ufuncs_docs.cpp index eb388016228a..0965b35fb6ae 100644 --- a/scipy/special/_special_ufuncs_docs.cpp +++ b/scipy/special/_special_ufuncs_docs.cpp @@ -398,16 +398,16 @@ const char *binom_doc = R"( ``y`` is negative or ``x`` is less than ``y``. >>> x, y = -3, 2 - >>> (binom(x, y), comb(x, y), comb(x, y, exact=True)) - (nan, 0.0, 0) + >>> (binom(x, y), comb(x, y)) + (nan, 0.0) >>> x, y = -3.1, 2.2 - >>> (binom(x, y), comb(x, y), comb(x, y, exact=True)) - (18.714147876804432, 0.0, 0) + >>> (binom(x, y), comb(x, y)) + (18.714147876804432, 0.0) >>> x, y = 2.2, 3.1 - >>> (binom(x, y), comb(x, y), comb(x, y, exact=True)) - (0.037399983365134115, 0.0, 0) + >>> (binom(x, y), comb(x, y)) + (0.037399983365134115, 0.0) )"; const char *exp1_doc = R"( diff --git a/scipy/special/tests/test_basic.py b/scipy/special/tests/test_basic.py index 99f4bae6dcc7..6d0be6be2743 100644 --- a/scipy/special/tests/test_basic.py +++ b/scipy/special/tests/test_basic.py @@ -40,7 +40,6 @@ from scipy.special import ellipe, ellipk, ellipkm1 from scipy.special import elliprc, elliprd, elliprf, elliprg, elliprj from scipy.special import mathieu_odd_coef, mathieu_even_coef, stirling2 -from scipy._lib.deprecation import _NoValue from scipy._lib._util import np_long, np_ulong from scipy.special._basic import _FACTORIALK_LIMITS_64BITS, \ @@ -1429,8 +1428,8 @@ def test_betainc_domain_errors(self, func, args): class TestCombinatorics: def test_comb(self): - assert_array_almost_equal(special.comb([10, 10], [3, 4]), [120., 210.]) - assert_almost_equal(special.comb(10, 3), 120.) + 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) @@ -1443,39 +1442,6 @@ def test_comb(self): expected = 100891344545564193334812497256 assert special.comb(100, 50, exact=True) == expected - @pytest.mark.parametrize("repetition", [True, False]) - @pytest.mark.parametrize("legacy", [True, False, _NoValue]) - @pytest.mark.parametrize("k", [3.5, 3]) - @pytest.mark.parametrize("N", [4.5, 4]) - def test_comb_legacy(self, N, k, legacy, repetition): - # test is only relevant for exact=True - if legacy is not _NoValue: - with pytest.warns( - DeprecationWarning, - match=r"Using 'legacy' keyword is deprecated" - ): - result = special.comb(N, k, exact=True, legacy=legacy, - repetition=repetition) - else: - result = special.comb(N, k, exact=True, legacy=legacy, - repetition=repetition) - if legacy: - # for exact=True and legacy=True, cast input arguments, else don't - if repetition: - # the casting in legacy mode happens AFTER transforming N & k, - # so rounding can change (e.g. both floats, but sum to int); - # hence we need to emulate the repetition-transformation here - N, k = int(N + k - 1), int(k) - repetition = False - else: - N, k = int(N), int(k) - # expected result is the same as with exact=False - with suppress_warnings() as sup: - if legacy is not _NoValue: - sup.filter(DeprecationWarning) - expected = special.comb(N, k, legacy=legacy, repetition=repetition) - assert_equal(result, expected) - def test_comb_with_np_int64(self): n = 70 k = 30 @@ -1490,11 +1456,10 @@ def test_comb_zeros(self): 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_array_almost_equal(special.comb([2, -1, 2, 10], [3, 3, -1, 3]), - [0., 0., 0., 120.]) + assert_allclose(special.comb([2, -1, 2, 10], [3, 3, -1, 3]), [0., 0., 0., 120.]) def test_perm(self): - assert_array_almost_equal(special.perm([10, 10], [3, 4]), [720., 5040.]) + assert_allclose(special.perm([10, 10], [3, 4]), [720., 5040.]) assert_almost_equal(special.perm(10, 3), 720.) assert_equal(special.perm(10, 3, exact=True), 720) @@ -1503,14 +1468,8 @@ def test_perm_zeros(self): 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_array_almost_equal(special.perm([2, -1, 2, 10], [3, 3, -1, 3]), - [0., 0., 0., 720.]) - - def test_positional_deprecation(self): - with pytest.deprecated_call(match="use keyword arguments"): - # from test_comb - special.comb([10, 10], [3, 4], False, False) - + assert_allclose(special.perm([2, -1, 2, 10], [3, 3, -1, 3]), [0., 0., 0., 720.]) + class TestTrigonometric: def test_cbrt(self): From c46ef137b690456d0859d07855b7236b33d90d68 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Fri, 26 Apr 2024 23:01:38 +0200 Subject: [PATCH 054/500] Revert "ENH: Use `highspy` in `linprog`" --- .gitignore | 2 + .gitmodules | 10 +- LICENSES_bundled.txt | 4 +- benchmarks/benchmarks/optimize_milp.py | 7 +- doc/source/tutorial/optimize.rst | 2 +- mypy.ini | 8 +- pyproject.toml | 2 - scipy/_lib/highs | 1 + scipy/_lib/meson.build | 3 + scipy/meson.build | 4 +- scipy/optimize/_highs/_highs_wrapper.py | 237 ------ scipy/optimize/_highs/cython/__init__.py | 0 scipy/optimize/_highs/cython/src/HConfig.h | 0 scipy/optimize/_highs/cython/src/HConst.pxd | 106 +++ scipy/optimize/_highs/cython/src/Highs.pxd | 56 ++ scipy/optimize/_highs/cython/src/HighsIO.pxd | 20 + .../optimize/_highs/cython/src/HighsInfo.pxd | 22 + scipy/optimize/_highs/cython/src/HighsLp.pxd | 46 ++ .../_highs/cython/src/HighsLpUtils.pxd | 9 + .../_highs/cython/src/HighsModelUtils.pxd | 10 + .../_highs/cython/src/HighsOptions.pxd | 110 +++ .../_highs/cython/src/HighsRuntimeOptions.pxd | 9 + .../_highs/cython/src/HighsSparseMatrix.pxd | 15 + .../_highs/cython/src/HighsStatus.pxd | 12 + .../_highs/cython/src/SimplexConst.pxd | 95 +++ scipy/optimize/_highs/cython/src/__init__.py | 0 .../_highs/cython/src/_highs_constants.pyx | 117 +++ .../_highs/cython/src/_highs_wrapper.pyx | 736 ++++++++++++++++++ .../_highs/cython/src/highs_c_api.pxd | 7 + scipy/optimize/_highs/meson.build | 307 +++++++- scipy/optimize/_highs/src/HConfig.h | 0 scipy/optimize/_highs/src/libhighs_export.h | 50 ++ scipy/optimize/_linprog_highs.py | 280 +++---- scipy/optimize/_milp.py | 9 +- scipy/optimize/tests/test_linprog.py | 11 +- scipy/optimize/tests/test_milp.py | 8 +- subprojects/highs | 1 - 37 files changed, 1818 insertions(+), 498 deletions(-) create mode 160000 scipy/_lib/highs delete mode 100644 scipy/optimize/_highs/_highs_wrapper.py create mode 100644 scipy/optimize/_highs/cython/__init__.py create mode 100644 scipy/optimize/_highs/cython/src/HConfig.h create mode 100644 scipy/optimize/_highs/cython/src/HConst.pxd create mode 100644 scipy/optimize/_highs/cython/src/Highs.pxd create mode 100644 scipy/optimize/_highs/cython/src/HighsIO.pxd create mode 100644 scipy/optimize/_highs/cython/src/HighsInfo.pxd create mode 100644 scipy/optimize/_highs/cython/src/HighsLp.pxd create mode 100644 scipy/optimize/_highs/cython/src/HighsLpUtils.pxd create mode 100644 scipy/optimize/_highs/cython/src/HighsModelUtils.pxd create mode 100644 scipy/optimize/_highs/cython/src/HighsOptions.pxd create mode 100644 scipy/optimize/_highs/cython/src/HighsRuntimeOptions.pxd create mode 100644 scipy/optimize/_highs/cython/src/HighsSparseMatrix.pxd create mode 100644 scipy/optimize/_highs/cython/src/HighsStatus.pxd create mode 100644 scipy/optimize/_highs/cython/src/SimplexConst.pxd create mode 100644 scipy/optimize/_highs/cython/src/__init__.py create mode 100644 scipy/optimize/_highs/cython/src/_highs_constants.pyx create mode 100644 scipy/optimize/_highs/cython/src/_highs_wrapper.pyx create mode 100644 scipy/optimize/_highs/cython/src/highs_c_api.pxd create mode 100644 scipy/optimize/_highs/src/HConfig.h create mode 100644 scipy/optimize/_highs/src/libhighs_export.h delete mode 160000 subprojects/highs diff --git a/.gitignore b/.gitignore index cd7b3a89475d..547eff51a193 100644 --- a/.gitignore +++ b/.gitignore @@ -322,3 +322,5 @@ scipy/optimize/_group_columns.c scipy/optimize/cython_optimize/_zeros.c scipy/optimize/cython_optimize/_zeros.pyx scipy/optimize/lbfgsb/_lbfgsbmodule.c +scipy/optimize/_highs/cython/src/_highs_wrapper.cxx +scipy/optimize/_highs/cython/src/_highs_constants.cxx diff --git a/.gitmodules b/.gitmodules index 5d5e60aa5a16..b6920b8714b2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,6 +9,10 @@ path = scipy/_lib/unuran url = https://github.com/scipy/unuran.git shallow = true +[submodule "HiGHS"] + path = scipy/_lib/highs + url = https://github.com/scipy/highs + shallow = true [submodule "scipy/_lib/boost_math"] path = scipy/_lib/boost_math url = https://github.com/boostorg/math.git @@ -19,9 +23,3 @@ [submodule "scipy/_lib/pocketfft"] path = scipy/_lib/pocketfft url = https://github.com/scipy/pocketfft -# All submodules used as a Meson `subproject` are required to be under the -# subprojects/ directory - see: -# https://mesonbuild.com/Subprojects.html#why-must-all-subprojects-be-inside-a-single-directory -[submodule "subprojects/highs"] - path = subprojects/highs - url = https://github.com/scipy/highs diff --git a/LICENSES_bundled.txt b/LICENSES_bundled.txt index 0c867c19c7b2..9a3943898822 100644 --- a/LICENSES_bundled.txt +++ b/LICENSES_bundled.txt @@ -252,9 +252,9 @@ License: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Name: HiGHS -Files: subprojects/highs/* +Files: scipy/optimize/_highs/* License: MIT - For details, see subprojects/highs/LICENCE + For details, see scipy/optimize/_highs/LICENCE Name: Boost Files: scipy/_lib/boost_math/* diff --git a/benchmarks/benchmarks/optimize_milp.py b/benchmarks/benchmarks/optimize_milp.py index ff7cce24e52f..f59ebeb081e4 100644 --- a/benchmarks/benchmarks/optimize_milp.py +++ b/benchmarks/benchmarks/optimize_milp.py @@ -48,7 +48,7 @@ def setup(self, prob): self.integrality = integrality def time_milp(self, prob): - # TODO: fix this benchmark (timing out in Aug. 2023); see gh-19389 + # TODO: fix this benchmark (timing out in Aug. 2023) # res = milp(c=self.c, constraints=self.constraints, bounds=self.bounds, # integrality=self.integrality) # assert res.success @@ -57,9 +57,8 @@ def time_milp(self, prob): class MilpMagicSquare(Benchmark): - # TODO: look at 5,6 - timing out and disabled in Apr'24 (5) and Aug'23 (6) - # see gh-19389 for details - params = [[3, 4]] + # TODO: re-add 6, timing out in Aug. 2023 + params = [[3, 4, 5]] param_names = ['size'] def setup(self, n): diff --git a/doc/source/tutorial/optimize.rst b/doc/source/tutorial/optimize.rst index 23474aab5160..8d6abb5f0270 100644 --- a/doc/source/tutorial/optimize.rst +++ b/doc/source/tutorial/optimize.rst @@ -1596,7 +1596,7 @@ Finally, we can solve the transformed problem using :func:`linprog`. >>> bounds = [x0_bounds, x1_bounds, x2_bounds, x3_bounds] >>> result = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=bounds) >>> print(result.message) - The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is None) + The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is At lower/fixed bound) The result states that our problem is infeasible, meaning that there is no solution vector that satisfies all the constraints. That doesn't necessarily mean we did anything wrong; some problems truly are infeasible. diff --git a/mypy.ini b/mypy.ini index 918b9a76a033..11fe54c67475 100644 --- a/mypy.ini +++ b/mypy.ini @@ -283,15 +283,13 @@ ignore_errors = True [mypy-scipy.optimize._linprog_util] ignore_errors = True -[mypy-scipy.optimize._highs.highspy._highs] +[mypy-scipy.optimize._linprog_highs] ignore_errors = True -ignore_missing_imports = True -[mypy-scipy.optimize._highs.highspy._highs.simplex_constants] +[mypy-scipy.optimize._highs.highs_wrapper] ignore_errors = True -ignore_missing_imports = True -[mypy-scipy.optimize._milp] +[mypy-scipy.optimize._highs.constants] ignore_errors = True [mypy-scipy.optimize._trustregion] diff --git a/pyproject.toml b/pyproject.toml index eb0b1d560a7a..0a5efd36115e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,8 +119,6 @@ tracker = "https://github.com/scipy/scipy/issues" [tool.doit] dodoFile = "dev.py" -[tool.meson-python.args] -install = ['--skip-subprojects'] [tool.cibuildwheel] skip = "cp36-* cp37-* cp38-* pp* *_ppc64le *_i686 *_s390x" diff --git a/scipy/_lib/highs b/scipy/_lib/highs new file mode 160000 index 000000000000..4a122958a82e --- /dev/null +++ b/scipy/_lib/highs @@ -0,0 +1 @@ +Subproject commit 4a122958a82e67e725d08153e099efe4dad099a2 diff --git a/scipy/_lib/meson.build b/scipy/_lib/meson.build index 1edbd94aafcc..7cdab24a5eeb 100644 --- a/scipy/_lib/meson.build +++ b/scipy/_lib/meson.build @@ -2,6 +2,9 @@ fs = import('fs') if not fs.exists('boost_math/README.md') error('Missing the `boost` submodule! Run `git submodule update --init` to fix this.') endif +if not fs.exists('highs/README.md') + error('Missing the `highs` submodule! Run `git submodule update --init` to fix this.') +endif if not fs.exists('unuran/README.md') error('Missing the `unuran` submodule! Run `git submodule update --init` to fix this.') endif diff --git a/scipy/meson.build b/scipy/meson.build index 1d25a3940a90..5f7279fa8cb9 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -342,8 +342,6 @@ Wno_switch = cc.get_supported_arguments('-Wno-switch') Wno_unused_label = cc.get_supported_arguments('-Wno-unused-label') Wno_unused_result = cc.get_supported_arguments('-Wno-unused-result') Wno_unused_variable = cc.get_supported_arguments('-Wno-unused-variable') -Wno_unused_but_set_variable = cc.get_supported_arguments('-Wno-unused-but-set-variable') -Wno_incompatible_pointer_types = cc.get_supported_arguments('-Wno-incompatible-pointer-types') # C++ warning flags _cpp_Wno_cpp = cpp.get_supported_arguments('-Wno-cpp') @@ -547,7 +545,6 @@ subdir('sparse') subdir('stats') subdir('fft') subdir('io') -subdir('optimize') subdir('spatial') subdir('cluster') subdir('constants') @@ -557,5 +554,6 @@ subdir('signal') subdir('interpolate') subdir('ndimage') subdir('odr') +subdir('optimize') subdir('datasets') subdir('misc') diff --git a/scipy/optimize/_highs/_highs_wrapper.py b/scipy/optimize/_highs/_highs_wrapper.py deleted file mode 100644 index 7fe5f387c0db..000000000000 --- a/scipy/optimize/_highs/_highs_wrapper.py +++ /dev/null @@ -1,237 +0,0 @@ -from warnings import warn - -import numpy as np -import scipy.optimize._highs.highspy._highs as _h -from scipy.optimize._highs.highspy import _highs_options as hopt # type: ignore[attr-defined] -from scipy.optimize import OptimizeWarning - - -def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, options): - numcol = c.size - numrow = rhs.size - isMip = integrality is not None and np.sum(integrality) > 0 - - # default "null" return values - res = { - "x": None, - "fun": None, - } - - # Fill up a HighsLp object - lp = _h.HighsLp() - lp.num_col_ = numcol - lp.num_row_ = numrow - lp.a_matrix_.num_col_ = numcol - lp.a_matrix_.num_row_ = numrow - lp.a_matrix_.format_ = _h.MatrixFormat.kColwise - lp.col_cost_ = c - lp.col_lower_ = lb - lp.col_upper_ = ub - lp.row_lower_ = lhs - lp.row_upper_ = rhs - lp.a_matrix_.start_ = indptr - lp.a_matrix_.index_ = indices - lp.a_matrix_.value_ = data - if integrality.size > 0: - lp.integrality_ = [_h.HighsVarType(i) for i in integrality] - - # Make a Highs object and pass it everything - highs = _h.Highs() - highs_options = _h.HighsOptions() - hoptmanager = hopt.HighsOptionsManager() - for key, val in options.items(): - # handle filtering of unsupported and default options - if val is None or key in ("sense",): - continue - - # ask for the option type - opt_type = hoptmanager.get_option_type(key) - if -1 == opt_type: - warn(f"Unrecognized options detected: {dict({key: val})}", - OptimizeWarning, stacklevel = 2) - continue - else: - if key in ("presolve", "parallel"): - # handle fake bools (require bool -> str conversions) - if isinstance(val, bool): - val = "on" if val else "off" - else: - warn( - f'Option f"{key}" is "{val}", but only True or False is ' - f"allowed. Using default.", - OptimizeWarning, - stacklevel = 2, - ) - continue - opt_type = _h.HighsOptionType(opt_type) - status, msg = check_option(highs, key, val) - if opt_type == _h.HighsOptionType.kBool: - if not isinstance(val, bool): - warn( - f'Option f"{key}" is "{val}", but only True or False is ' - f"allowed. Using default.", - OptimizeWarning, - stacklevel = 2, - ) - continue - - # warn or set option - if status != 0: - warn(msg, OptimizeWarning, stacklevel = 2) - else: - setattr(highs_options, key, val) - - opt_status = highs.passOptions(highs_options) - if opt_status == _h.HighsStatus.kError: - res.update( - { - "status": highs.getModelStatus(), - "message": highs.modelStatusToString(highs.getModelStatus()), - } - ) - return res - - init_status = highs.passModel(lp) - if init_status == _h.HighsStatus.kError: - # if model fails to load, highs.getModelStatus() will be NOT_SET - err_model_status = _h.HighsModelStatus.kModelError - res.update( - { - "status": err_model_status, - "message": highs.modelStatusToString(err_model_status), - } - ) - return res - - # Solve the LP - run_status = highs.run() - if run_status == _h.HighsStatus.kError: - res.update( - { - "status": highs.getModelStatus(), - "message": highs.modelStatusToString(highs.getModelStatus()), - } - ) - return res - - # Extract what we need from the solution - model_status = highs.getModelStatus() - - # it should always be safe to get the info object - info = highs.getInfo() - - # Failure modes: - # LP: if we have anything other than an Optimal status, it - # is unsafe (and unhelpful) to read any results - # MIP: has a non-Optimal status or has timed out/reached max iterations - # 1) If not Optimal/TimedOut/MaxIter status, there is no solution - # 2) If TimedOut/MaxIter status, there may be a feasible solution. - # if the objective function value is not Infinity, then the - # current solution is feasible and can be returned. Else, there - # is no solution. - mipFailCondition = model_status not in ( - _h.HighsModelStatus.kOptimal, - _h.HighsModelStatus.kTimeLimit, - _h.HighsModelStatus.kIterationLimit, - _h.HighsModelStatus.kSolutionLimit, - ) or ( - model_status - in { - _h.HighsModelStatus.kTimeLimit, - _h.HighsModelStatus.kIterationLimit, - _h.HighsModelStatus.kSolutionLimit, - } - and (info.objective_function_value == _h.kHighsInf) - ) - lpFailCondition = model_status != _h.HighsModelStatus.kOptimal - if (isMip and mipFailCondition) or (not isMip and lpFailCondition): - res.update( - { - "status": model_status, - "message": "model_status is " - f"{highs.modelStatusToString(model_status)}; " - "primal_status is " - f"{highs.solutionStatusToString(info.primal_solution_status)}", - "simplex_nit": info.simplex_iteration_count, - "ipm_nit": info.ipm_iteration_count, - "crossover_nit": info.crossover_iteration_count, - } - ) - return res - - # Should be safe to read the solution: - solution = highs.getSolution() - basis = highs.getBasis() - - # Lagrangians for bounds based on column statuses - marg_bnds = np.zeros((2, numcol)) - for ii in range(numcol): - if basis.col_status[ii] == _h.HighsBasisStatus.kLower: - marg_bnds[0, ii] = solution.col_dual[ii] - elif basis.col_status[ii] == _h.HighsBasisStatus.kUpper: - marg_bnds[1, ii] = solution.col_dual[ii] - - res.update( - { - "status": model_status, - "message": highs.modelStatusToString(model_status), - # Primal solution - "x": np.array(solution.col_value), - # Ax + s = b => Ax = b - s - # Note: this is for all constraints (A_ub and A_eq) - "slack": rhs - solution.row_value, - # lambda are the lagrange multipliers associated with Ax=b - "lambda": np.array(solution.row_dual), - "marg_bnds": marg_bnds, - "fun": info.objective_function_value, - "simplex_nit": info.simplex_iteration_count, - "ipm_nit": info.ipm_iteration_count, - "crossover_nit": info.crossover_iteration_count, - } - ) - - if isMip: - res.update( - { - "mip_node_count": info.mip_node_count, - "mip_dual_bound": info.mip_dual_bound, - "mip_gap": info.mip_gap, - } - ) - - return res - - -def check_option(highs_inst, option, value): - status, option_type = highs_inst.getOptionType(option) - hoptmanager = hopt.HighsOptionsManager() - - if status != _h.HighsStatus.kOk: - return -1, "Invalid option name." - - valid_types = { - _h.HighsOptionType.kBool: bool, - _h.HighsOptionType.kInt: int, - _h.HighsOptionType.kDouble: float, - _h.HighsOptionType.kString: str, - } - - expected_type = valid_types.get(option_type, None) - - if expected_type is str: - if not hoptmanager.check_string_option(option, value): - return -1, "Invalid option value." - if expected_type is float: - if not hoptmanager.check_double_option(option, value): - return -1, "Invalid option value." - if expected_type is int: - if not hoptmanager.check_int_option(option, value): - return -1, "Invalid option value." - - if expected_type is None: - return 3, "Unknown option type." - - status, current_value = highs_inst.getOptionValue(option) - if status != _h.HighsStatus.kOk: - return 4, "Failed to validate option value." - return 0, "Check option succeeded." diff --git a/scipy/optimize/_highs/cython/__init__.py b/scipy/optimize/_highs/cython/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/scipy/optimize/_highs/cython/src/HConfig.h b/scipy/optimize/_highs/cython/src/HConfig.h new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/scipy/optimize/_highs/cython/src/HConst.pxd b/scipy/optimize/_highs/cython/src/HConst.pxd new file mode 100644 index 000000000000..503d9e74a263 --- /dev/null +++ b/scipy/optimize/_highs/cython/src/HConst.pxd @@ -0,0 +1,106 @@ +# cython: language_level=3 + +from libcpp cimport bool +from libcpp.string cimport string + +cdef extern from "HConst.h" nogil: + + const int HIGHS_CONST_I_INF "kHighsIInf" + const double HIGHS_CONST_INF "kHighsInf" + const double kHighsTiny + const double kHighsZero + const int kHighsThreadLimit + + cdef enum HighsDebugLevel: + HighsDebugLevel_kHighsDebugLevelNone "kHighsDebugLevelNone" = 0 + HighsDebugLevel_kHighsDebugLevelCheap "kHighsDebugLevelCheap" + HighsDebugLevel_kHighsDebugLevelCostly "kHighsDebugLevelCostly" + HighsDebugLevel_kHighsDebugLevelExpensive "kHighsDebugLevelExpensive" + HighsDebugLevel_kHighsDebugLevelMin "kHighsDebugLevelMin" = HighsDebugLevel_kHighsDebugLevelNone + HighsDebugLevel_kHighsDebugLevelMax "kHighsDebugLevelMax" = HighsDebugLevel_kHighsDebugLevelExpensive + + ctypedef enum HighsModelStatus: + HighsModelStatusNOTSET "HighsModelStatus::kNotset" = 0 + HighsModelStatusLOAD_ERROR "HighsModelStatus::kLoadError" + HighsModelStatusMODEL_ERROR "HighsModelStatus::kModelError" + HighsModelStatusPRESOLVE_ERROR "HighsModelStatus::kPresolveError" + HighsModelStatusSOLVE_ERROR "HighsModelStatus::kSolveError" + HighsModelStatusPOSTSOLVE_ERROR "HighsModelStatus::kPostsolveError" + HighsModelStatusMODEL_EMPTY "HighsModelStatus::kModelEmpty" + HighsModelStatusOPTIMAL "HighsModelStatus::kOptimal" + HighsModelStatusINFEASIBLE "HighsModelStatus::kInfeasible" + HighsModelStatus_UNBOUNDED_OR_INFEASIBLE "HighsModelStatus::kUnboundedOrInfeasible" + HighsModelStatusUNBOUNDED "HighsModelStatus::kUnbounded" + HighsModelStatusREACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND "HighsModelStatus::kObjectiveBound" + HighsModelStatusREACHED_OBJECTIVE_TARGET "HighsModelStatus::kObjectiveTarget" + HighsModelStatusREACHED_TIME_LIMIT "HighsModelStatus::kTimeLimit" + HighsModelStatusREACHED_ITERATION_LIMIT "HighsModelStatus::kIterationLimit" + HighsModelStatusUNKNOWN "HighsModelStatus::kUnknown" + HighsModelStatusHIGHS_MODEL_STATUS_MIN "HighsModelStatus::kMin" = HighsModelStatusNOTSET + HighsModelStatusHIGHS_MODEL_STATUS_MAX "HighsModelStatus::kMax" = HighsModelStatusUNKNOWN + + cdef enum HighsBasisStatus: + HighsBasisStatusLOWER "HighsBasisStatus::kLower" = 0, # (slack) variable is at its lower bound [including fixed variables] + HighsBasisStatusBASIC "HighsBasisStatus::kBasic" # (slack) variable is basic + HighsBasisStatusUPPER "HighsBasisStatus::kUpper" # (slack) variable is at its upper bound + HighsBasisStatusZERO "HighsBasisStatus::kZero" # free variable is non-basic and set to zero + HighsBasisStatusNONBASIC "HighsBasisStatus::kNonbasic" # nonbasic with no specific bound information - useful for users and postsolve + + cdef enum SolverOption: + SOLVER_OPTION_SIMPLEX "SolverOption::SOLVER_OPTION_SIMPLEX" = -1 + SOLVER_OPTION_CHOOSE "SolverOption::SOLVER_OPTION_CHOOSE" + SOLVER_OPTION_IPM "SolverOption::SOLVER_OPTION_IPM" + + cdef enum PrimalDualStatus: + PrimalDualStatusSTATUS_NOT_SET "PrimalDualStatus::STATUS_NOT_SET" = -1 + PrimalDualStatusSTATUS_MIN "PrimalDualStatus::STATUS_MIN" = PrimalDualStatusSTATUS_NOT_SET + PrimalDualStatusSTATUS_NO_SOLUTION "PrimalDualStatus::STATUS_NO_SOLUTION" + PrimalDualStatusSTATUS_UNKNOWN "PrimalDualStatus::STATUS_UNKNOWN" + PrimalDualStatusSTATUS_INFEASIBLE_POINT "PrimalDualStatus::STATUS_INFEASIBLE_POINT" + PrimalDualStatusSTATUS_FEASIBLE_POINT "PrimalDualStatus::STATUS_FEASIBLE_POINT" + PrimalDualStatusSTATUS_MAX "PrimalDualStatus::STATUS_MAX" = PrimalDualStatusSTATUS_FEASIBLE_POINT + + cdef enum HighsOptionType: + HighsOptionTypeBOOL "HighsOptionType::kBool" = 0 + HighsOptionTypeINT "HighsOptionType::kInt" + HighsOptionTypeDOUBLE "HighsOptionType::kDouble" + HighsOptionTypeSTRING "HighsOptionType::kString" + + # workaround for lack of enum class support in Cython < 3.x + # cdef enum class ObjSense(int): + # ObjSenseMINIMIZE "ObjSense::kMinimize" = 1 + # ObjSenseMAXIMIZE "ObjSense::kMaximize" = -1 + + cdef cppclass ObjSense: + pass + + cdef ObjSense ObjSenseMINIMIZE "ObjSense::kMinimize" + cdef ObjSense ObjSenseMAXIMIZE "ObjSense::kMaximize" + + # cdef enum class MatrixFormat(int): + # MatrixFormatkColwise "MatrixFormat::kColwise" = 1 + # MatrixFormatkRowwise "MatrixFormat::kRowwise" + # MatrixFormatkRowwisePartitioned "MatrixFormat::kRowwisePartitioned" + + cdef cppclass MatrixFormat: + pass + + cdef MatrixFormat MatrixFormatkColwise "MatrixFormat::kColwise" + cdef MatrixFormat MatrixFormatkRowwise "MatrixFormat::kRowwise" + cdef MatrixFormat MatrixFormatkRowwisePartitioned "MatrixFormat::kRowwisePartitioned" + + # cdef enum class HighsVarType(int): + # kContinuous "HighsVarType::kContinuous" + # kInteger "HighsVarType::kInteger" + # kSemiContinuous "HighsVarType::kSemiContinuous" + # kSemiInteger "HighsVarType::kSemiInteger" + # kImplicitInteger "HighsVarType::kImplicitInteger" + + cdef cppclass HighsVarType: + pass + + cdef HighsVarType kContinuous "HighsVarType::kContinuous" + cdef HighsVarType kInteger "HighsVarType::kInteger" + cdef HighsVarType kSemiContinuous "HighsVarType::kSemiContinuous" + cdef HighsVarType kSemiInteger "HighsVarType::kSemiInteger" + cdef HighsVarType kImplicitInteger "HighsVarType::kImplicitInteger" diff --git a/scipy/optimize/_highs/cython/src/Highs.pxd b/scipy/optimize/_highs/cython/src/Highs.pxd new file mode 100644 index 000000000000..7139908d0341 --- /dev/null +++ b/scipy/optimize/_highs/cython/src/Highs.pxd @@ -0,0 +1,56 @@ +# cython: language_level=3 + +from libc.stdio cimport FILE + +from libcpp cimport bool +from libcpp.string cimport string + +from .HighsStatus cimport HighsStatus +from .HighsOptions cimport HighsOptions +from .HighsInfo cimport HighsInfo +from .HighsLp cimport ( + HighsLp, + HighsSolution, + HighsBasis, + ObjSense, +) +from .HConst cimport HighsModelStatus + +cdef extern from "Highs.h": + # From HiGHS/src/Highs.h + cdef cppclass Highs: + HighsStatus passHighsOptions(const HighsOptions& options) + HighsStatus passModel(const HighsLp& lp) + HighsStatus run() + HighsStatus setHighsLogfile(FILE* logfile) + HighsStatus setHighsOutput(FILE* output) + HighsStatus writeHighsOptions(const string filename, const bool report_only_non_default_values = true) + + # split up for cython below + #const HighsModelStatus& getModelStatus(const bool scaled_model = False) const + const HighsModelStatus & getModelStatus() const + + const HighsInfo& getHighsInfo "getInfo" () const + string modelStatusToString(const HighsModelStatus model_status) const + #HighsStatus getHighsInfoValue(const string& info, int& value) + HighsStatus getHighsInfoValue(const string& info, double& value) const + const HighsOptions& getHighsOptions() const + + const HighsLp& getLp() const + + HighsStatus writeSolution(const string filename, const bool pretty) const + + HighsStatus setBasis() + const HighsSolution& getSolution() const + const HighsBasis& getBasis() const + + bool changeObjectiveSense(const ObjSense sense) + + HighsStatus setHighsOptionValueBool "setOptionValue" (const string & option, const bool value) + HighsStatus setHighsOptionValueInt "setOptionValue" (const string & option, const int value) + HighsStatus setHighsOptionValueStr "setOptionValue" (const string & option, const string & value) + HighsStatus setHighsOptionValueDbl "setOptionValue" (const string & option, const double value) + + string primalDualStatusToString(const int primal_dual_status) + + void resetGlobalScheduler(bool blocking) diff --git a/scipy/optimize/_highs/cython/src/HighsIO.pxd b/scipy/optimize/_highs/cython/src/HighsIO.pxd new file mode 100644 index 000000000000..82b80ae643f1 --- /dev/null +++ b/scipy/optimize/_highs/cython/src/HighsIO.pxd @@ -0,0 +1,20 @@ +# cython: language_level=3 + + +cdef extern from "HighsIO.h" nogil: + # workaround for lack of enum class support in Cython < 3.x + # cdef enum class HighsLogType(int): + # kInfo "HighsLogType::kInfo" = 1 + # kDetailed "HighsLogType::kDetailed" + # kVerbose "HighsLogType::kVerbose" + # kWarning "HighsLogType::kWarning" + # kError "HighsLogType::kError" + + cdef cppclass HighsLogType: + pass + + cdef HighsLogType kInfo "HighsLogType::kInfo" + cdef HighsLogType kDetailed "HighsLogType::kDetailed" + cdef HighsLogType kVerbose "HighsLogType::kVerbose" + cdef HighsLogType kWarning "HighsLogType::kWarning" + cdef HighsLogType kError "HighsLogType::kError" diff --git a/scipy/optimize/_highs/cython/src/HighsInfo.pxd b/scipy/optimize/_highs/cython/src/HighsInfo.pxd new file mode 100644 index 000000000000..789b51089896 --- /dev/null +++ b/scipy/optimize/_highs/cython/src/HighsInfo.pxd @@ -0,0 +1,22 @@ +# cython: language_level=3 + +cdef extern from "HighsInfo.h" nogil: + # From HiGHS/src/lp_data/HighsInfo.h + cdef cppclass HighsInfo: + # Inherited from HighsInfoStruct: + int mip_node_count + int simplex_iteration_count + int ipm_iteration_count + int crossover_iteration_count + int primal_solution_status + int dual_solution_status + int basis_validity + double objective_function_value + double mip_dual_bound + double mip_gap + int num_primal_infeasibilities + double max_primal_infeasibility + double sum_primal_infeasibilities + int num_dual_infeasibilities + double max_dual_infeasibility + double sum_dual_infeasibilities diff --git a/scipy/optimize/_highs/cython/src/HighsLp.pxd b/scipy/optimize/_highs/cython/src/HighsLp.pxd new file mode 100644 index 000000000000..0944f083743f --- /dev/null +++ b/scipy/optimize/_highs/cython/src/HighsLp.pxd @@ -0,0 +1,46 @@ +# cython: language_level=3 + +from libcpp cimport bool +from libcpp.string cimport string +from libcpp.vector cimport vector + +from .HConst cimport HighsBasisStatus, ObjSense, HighsVarType +from .HighsSparseMatrix cimport HighsSparseMatrix + + +cdef extern from "HighsLp.h" nogil: + # From HiGHS/src/lp_data/HighsLp.h + cdef cppclass HighsLp: + int num_col_ + int num_row_ + + vector[double] col_cost_ + vector[double] col_lower_ + vector[double] col_upper_ + vector[double] row_lower_ + vector[double] row_upper_ + + HighsSparseMatrix a_matrix_ + + ObjSense sense_ + double offset_ + + string model_name_ + + vector[string] row_names_ + vector[string] col_names_ + + vector[HighsVarType] integrality_ + + bool isMip() const + + cdef cppclass HighsSolution: + vector[double] col_value + vector[double] col_dual + vector[double] row_value + vector[double] row_dual + + cdef cppclass HighsBasis: + bool valid_ + vector[HighsBasisStatus] col_status + vector[HighsBasisStatus] row_status diff --git a/scipy/optimize/_highs/cython/src/HighsLpUtils.pxd b/scipy/optimize/_highs/cython/src/HighsLpUtils.pxd new file mode 100644 index 000000000000..18ede36c146a --- /dev/null +++ b/scipy/optimize/_highs/cython/src/HighsLpUtils.pxd @@ -0,0 +1,9 @@ +# cython: language_level=3 + +from .HighsStatus cimport HighsStatus +from .HighsLp cimport HighsLp +from .HighsOptions cimport HighsOptions + +cdef extern from "HighsLpUtils.h" nogil: + # From HiGHS/src/lp_data/HighsLpUtils.h + HighsStatus assessLp(HighsLp& lp, const HighsOptions& options) diff --git a/scipy/optimize/_highs/cython/src/HighsModelUtils.pxd b/scipy/optimize/_highs/cython/src/HighsModelUtils.pxd new file mode 100644 index 000000000000..4fccc2e80046 --- /dev/null +++ b/scipy/optimize/_highs/cython/src/HighsModelUtils.pxd @@ -0,0 +1,10 @@ +# cython: language_level=3 + +from libcpp.string cimport string + +from .HConst cimport HighsModelStatus + +cdef extern from "HighsModelUtils.h" nogil: + # From HiGHS/src/lp_data/HighsModelUtils.h + string utilHighsModelStatusToString(const HighsModelStatus model_status) + string utilBasisStatusToString(const int primal_dual_status) diff --git a/scipy/optimize/_highs/cython/src/HighsOptions.pxd b/scipy/optimize/_highs/cython/src/HighsOptions.pxd new file mode 100644 index 000000000000..920c10c19e30 --- /dev/null +++ b/scipy/optimize/_highs/cython/src/HighsOptions.pxd @@ -0,0 +1,110 @@ +# cython: language_level=3 + +from libc.stdio cimport FILE + +from libcpp cimport bool +from libcpp.string cimport string +from libcpp.vector cimport vector + +from .HConst cimport HighsOptionType + +cdef extern from "HighsOptions.h" nogil: + + cdef cppclass OptionRecord: + HighsOptionType type + string name + string description + bool advanced + + cdef cppclass OptionRecordBool(OptionRecord): + bool* value + bool default_value + + cdef cppclass OptionRecordInt(OptionRecord): + int* value + int lower_bound + int default_value + int upper_bound + + cdef cppclass OptionRecordDouble(OptionRecord): + double* value + double lower_bound + double default_value + double upper_bound + + cdef cppclass OptionRecordString(OptionRecord): + string* value + string default_value + + cdef cppclass HighsOptions: + # From HighsOptionsStruct: + + # Options read from the command line + string model_file + string presolve + string solver + string parallel + double time_limit + string options_file + + # Options read from the file + double infinite_cost + double infinite_bound + double small_matrix_value + double large_matrix_value + double primal_feasibility_tolerance + double dual_feasibility_tolerance + double ipm_optimality_tolerance + double dual_objective_value_upper_bound + int highs_debug_level + int simplex_strategy + int simplex_scale_strategy + int simplex_crash_strategy + int simplex_dual_edge_weight_strategy + int simplex_primal_edge_weight_strategy + int simplex_iteration_limit + int simplex_update_limit + int ipm_iteration_limit + int highs_min_threads + int highs_max_threads + int message_level + string solution_file + bool write_solution_to_file + bool write_solution_pretty + + # Advanced options + bool run_crossover + bool mps_parser_type_free + int keep_n_rows + int allowed_simplex_matrix_scale_factor + int allowed_simplex_cost_scale_factor + int simplex_dualise_strategy + int simplex_permute_strategy + int dual_simplex_cleanup_strategy + int simplex_price_strategy + int dual_chuzc_sort_strategy + bool simplex_initial_condition_check + double simplex_initial_condition_tolerance + double dual_steepest_edge_weight_log_error_threshhold + double dual_simplex_cost_perturbation_multiplier + double start_crossover_tolerance + bool less_infeasible_DSE_check + bool less_infeasible_DSE_choose_row + bool use_original_HFactor_logic + + # Options for MIP solver + int mip_max_nodes + int mip_report_level + + # Switch for MIP solver + bool mip + + # Options for HighsPrintMessage and HighsLogMessage + FILE* logfile + FILE* output + int message_level + string solution_file + bool write_solution_to_file + bool write_solution_pretty + + vector[OptionRecord*] records diff --git a/scipy/optimize/_highs/cython/src/HighsRuntimeOptions.pxd b/scipy/optimize/_highs/cython/src/HighsRuntimeOptions.pxd new file mode 100644 index 000000000000..3e227b7a44f7 --- /dev/null +++ b/scipy/optimize/_highs/cython/src/HighsRuntimeOptions.pxd @@ -0,0 +1,9 @@ +# cython: language_level=3 + +from libcpp cimport bool + +from .HighsOptions cimport HighsOptions + +cdef extern from "HighsRuntimeOptions.h" nogil: + # From HiGHS/src/lp_data/HighsRuntimeOptions.h + bool loadOptions(int argc, char** argv, HighsOptions& options) diff --git a/scipy/optimize/_highs/cython/src/HighsSparseMatrix.pxd b/scipy/optimize/_highs/cython/src/HighsSparseMatrix.pxd new file mode 100644 index 000000000000..7eaa9ef79eee --- /dev/null +++ b/scipy/optimize/_highs/cython/src/HighsSparseMatrix.pxd @@ -0,0 +1,15 @@ +# cython: language_level=3 + +from libcpp.vector cimport vector + +from .HConst cimport MatrixFormat + + +cdef extern from "HighsSparseMatrix.h" nogil: + cdef cppclass HighsSparseMatrix: + MatrixFormat format_ + int num_col_ + int num_row_ + vector[int] start_ + vector[int] index_ + vector[double] value_ diff --git a/scipy/optimize/_highs/cython/src/HighsStatus.pxd b/scipy/optimize/_highs/cython/src/HighsStatus.pxd new file mode 100644 index 000000000000..b47813b5d391 --- /dev/null +++ b/scipy/optimize/_highs/cython/src/HighsStatus.pxd @@ -0,0 +1,12 @@ +# cython: language_level=3 + +from libcpp.string cimport string + +cdef extern from "HighsStatus.h" nogil: + ctypedef enum HighsStatus: + HighsStatusError "HighsStatus::kError" = -1 + HighsStatusOK "HighsStatus::kOk" = 0 + HighsStatusWarning "HighsStatus::kWarning" = 1 + + + string highsStatusToString(HighsStatus status) diff --git a/scipy/optimize/_highs/cython/src/SimplexConst.pxd b/scipy/optimize/_highs/cython/src/SimplexConst.pxd new file mode 100644 index 000000000000..77e7b96320d6 --- /dev/null +++ b/scipy/optimize/_highs/cython/src/SimplexConst.pxd @@ -0,0 +1,95 @@ +# cython: language_level=3 + +from libcpp cimport bool + +cdef extern from "SimplexConst.h" nogil: + + cdef enum SimplexAlgorithm: + PRIMAL "SimplexAlgorithm::kPrimal" = 0 + DUAL "SimplexAlgorithm::kDual" + + cdef enum SimplexStrategy: + SIMPLEX_STRATEGY_MIN "SimplexStrategy::kSimplexStrategyMin" = 0 + SIMPLEX_STRATEGY_CHOOSE "SimplexStrategy::kSimplexStrategyChoose" = SIMPLEX_STRATEGY_MIN + SIMPLEX_STRATEGY_DUAL "SimplexStrategy::kSimplexStrategyDual" + SIMPLEX_STRATEGY_DUAL_PLAIN "SimplexStrategy::kSimplexStrategyDualPlain" = SIMPLEX_STRATEGY_DUAL + SIMPLEX_STRATEGY_DUAL_TASKS "SimplexStrategy::kSimplexStrategyDualTasks" + SIMPLEX_STRATEGY_DUAL_MULTI "SimplexStrategy::kSimplexStrategyDualMulti" + SIMPLEX_STRATEGY_PRIMAL "SimplexStrategy::kSimplexStrategyPrimal" + SIMPLEX_STRATEGY_MAX "SimplexStrategy::kSimplexStrategyMax" = SIMPLEX_STRATEGY_PRIMAL + SIMPLEX_STRATEGY_NUM "SimplexStrategy::kSimplexStrategyNum" + + cdef enum SimplexCrashStrategy: + SIMPLEX_CRASH_STRATEGY_MIN "SimplexCrashStrategy::kSimplexCrashStrategyMin" = 0 + SIMPLEX_CRASH_STRATEGY_OFF "SimplexCrashStrategy::kSimplexCrashStrategyOff" = SIMPLEX_CRASH_STRATEGY_MIN + SIMPLEX_CRASH_STRATEGY_LTSSF_K "SimplexCrashStrategy::kSimplexCrashStrategyLtssfK" + SIMPLEX_CRASH_STRATEGY_LTSSF "SimplexCrashStrategy::kSimplexCrashStrategyLtssf" = SIMPLEX_CRASH_STRATEGY_LTSSF_K + SIMPLEX_CRASH_STRATEGY_BIXBY "SimplexCrashStrategy::kSimplexCrashStrategyBixby" + SIMPLEX_CRASH_STRATEGY_LTSSF_PRI "SimplexCrashStrategy::kSimplexCrashStrategyLtssfPri" + SIMPLEX_CRASH_STRATEGY_LTSF_K "SimplexCrashStrategy::kSimplexCrashStrategyLtsfK" + SIMPLEX_CRASH_STRATEGY_LTSF_PRI "SimplexCrashStrategy::kSimplexCrashStrategyLtsfPri" + SIMPLEX_CRASH_STRATEGY_LTSF "SimplexCrashStrategy::kSimplexCrashStrategyLtsf" + SIMPLEX_CRASH_STRATEGY_BIXBY_NO_NONZERO_COL_COSTS "SimplexCrashStrategy::kSimplexCrashStrategyBixbyNoNonzeroColCosts" + SIMPLEX_CRASH_STRATEGY_BASIC "SimplexCrashStrategy::kSimplexCrashStrategyBasic" + SIMPLEX_CRASH_STRATEGY_TEST_SING "SimplexCrashStrategy::kSimplexCrashStrategyTestSing" + SIMPLEX_CRASH_STRATEGY_MAX "SimplexCrashStrategy::kSimplexCrashStrategyMax" = SIMPLEX_CRASH_STRATEGY_TEST_SING + + cdef enum SimplexEdgeWeightStrategy: + SIMPLEX_EDGE_WEIGHT_STRATEGY_MIN "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyMin" = -1 + SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyChoose" = SIMPLEX_EDGE_WEIGHT_STRATEGY_MIN + SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyDantzig" + SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyDevex" + SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategySteepestEdge" + SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_UNIT_INITIAL "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategySteepestEdgeUnitInitial" + SIMPLEX_EDGE_WEIGHT_STRATEGY_MAX "SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyMax" = SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_UNIT_INITIAL + + cdef enum SimplexPriceStrategy: + SIMPLEX_PRICE_STRATEGY_MIN = 0 + SIMPLEX_PRICE_STRATEGY_COL = SIMPLEX_PRICE_STRATEGY_MIN + SIMPLEX_PRICE_STRATEGY_ROW + SIMPLEX_PRICE_STRATEGY_ROW_SWITCH + SIMPLEX_PRICE_STRATEGY_ROW_SWITCH_COL_SWITCH + SIMPLEX_PRICE_STRATEGY_MAX = SIMPLEX_PRICE_STRATEGY_ROW_SWITCH_COL_SWITCH + + cdef enum SimplexDualChuzcStrategy: + SIMPLEX_DUAL_CHUZC_STRATEGY_MIN = 0 + SIMPLEX_DUAL_CHUZC_STRATEGY_CHOOSE = SIMPLEX_DUAL_CHUZC_STRATEGY_MIN + SIMPLEX_DUAL_CHUZC_STRATEGY_QUAD + SIMPLEX_DUAL_CHUZC_STRATEGY_HEAP + SIMPLEX_DUAL_CHUZC_STRATEGY_BOTH + SIMPLEX_DUAL_CHUZC_STRATEGY_MAX = SIMPLEX_DUAL_CHUZC_STRATEGY_BOTH + + cdef enum InvertHint: + INVERT_HINT_NO = 0 + INVERT_HINT_UPDATE_LIMIT_REACHED + INVERT_HINT_SYNTHETIC_CLOCK_SAYS_INVERT + INVERT_HINT_POSSIBLY_OPTIMAL + INVERT_HINT_POSSIBLY_PRIMAL_UNBOUNDED + INVERT_HINT_POSSIBLY_DUAL_UNBOUNDED + INVERT_HINT_POSSIBLY_SINGULAR_BASIS + INVERT_HINT_PRIMAL_INFEASIBLE_IN_PRIMAL_SIMPLEX + INVERT_HINT_CHOOSE_COLUMN_FAIL + INVERT_HINT_Count + + cdef enum DualEdgeWeightMode: + DANTZIG "DualEdgeWeightMode::DANTZIG" = 0 + DEVEX "DualEdgeWeightMode::DEVEX" + STEEPEST_EDGE "DualEdgeWeightMode::STEEPEST_EDGE" + Count "DualEdgeWeightMode::Count" + + cdef enum PriceMode: + ROW "PriceMode::ROW" = 0 + COL "PriceMode::COL" + + const int PARALLEL_THREADS_DEFAULT + const int DUAL_TASKS_MIN_THREADS + const int DUAL_MULTI_MIN_THREADS + + const bool invert_if_row_out_negative + + const int NONBASIC_FLAG_TRUE + const int NONBASIC_FLAG_FALSE + + const int NONBASIC_MOVE_UP + const int NONBASIC_MOVE_DN + const int NONBASIC_MOVE_ZE diff --git a/scipy/optimize/_highs/cython/src/__init__.py b/scipy/optimize/_highs/cython/src/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/scipy/optimize/_highs/cython/src/_highs_constants.pyx b/scipy/optimize/_highs/cython/src/_highs_constants.pyx new file mode 100644 index 000000000000..815e7d79e9e1 --- /dev/null +++ b/scipy/optimize/_highs/cython/src/_highs_constants.pyx @@ -0,0 +1,117 @@ +# cython: language_level=3 + +'''Export enum values and constants from HiGHS.''' + +from .HConst cimport ( + HIGHS_CONST_I_INF, + HIGHS_CONST_INF, + + HighsDebugLevel_kHighsDebugLevelNone, + HighsDebugLevel_kHighsDebugLevelCheap, + + HighsModelStatusNOTSET, + HighsModelStatusLOAD_ERROR, + HighsModelStatusMODEL_ERROR, + HighsModelStatusMODEL_EMPTY, + HighsModelStatusPRESOLVE_ERROR, + HighsModelStatusSOLVE_ERROR, + HighsModelStatusPOSTSOLVE_ERROR, + HighsModelStatusINFEASIBLE, + HighsModelStatus_UNBOUNDED_OR_INFEASIBLE, + HighsModelStatusUNBOUNDED, + HighsModelStatusOPTIMAL, + HighsModelStatusREACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND, + HighsModelStatusREACHED_OBJECTIVE_TARGET, + HighsModelStatusREACHED_TIME_LIMIT, + HighsModelStatusREACHED_ITERATION_LIMIT, + + ObjSenseMINIMIZE, + kContinuous, + kInteger, + kSemiContinuous, + kSemiInteger, + kImplicitInteger, +) +from .HighsIO cimport ( + kInfo, + kDetailed, + kVerbose, + kWarning, + kError, +) +from .SimplexConst cimport ( + # Simplex strategy + SIMPLEX_STRATEGY_CHOOSE, + SIMPLEX_STRATEGY_DUAL, + SIMPLEX_STRATEGY_PRIMAL, + + # Crash strategy + SIMPLEX_CRASH_STRATEGY_OFF, + SIMPLEX_CRASH_STRATEGY_BIXBY, + SIMPLEX_CRASH_STRATEGY_LTSF, + + # Edge weight strategy + SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE, + SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG, + SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX, + SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE, +) + +# HConst +CONST_I_INF = HIGHS_CONST_I_INF +CONST_INF = HIGHS_CONST_INF + +# Debug level +MESSAGE_LEVEL_NONE = HighsDebugLevel_kHighsDebugLevelNone +MESSAGE_LEVEL_MINIMAL = HighsDebugLevel_kHighsDebugLevelCheap + +# HighsIO +LOG_TYPE_INFO = kInfo +LOG_TYPE_DETAILED = kDetailed +LOG_TYPE_VERBOSE = kVerbose +LOG_TYPE_WARNING = kWarning +LOG_TYPE_ERROR = kError + +# HighsLp +MODEL_STATUS_NOTSET = HighsModelStatusNOTSET +MODEL_STATUS_LOAD_ERROR = HighsModelStatusLOAD_ERROR +MODEL_STATUS_MODEL_ERROR = HighsModelStatusMODEL_ERROR +MODEL_STATUS_PRESOLVE_ERROR = HighsModelStatusPRESOLVE_ERROR +MODEL_STATUS_SOLVE_ERROR = HighsModelStatusSOLVE_ERROR +MODEL_STATUS_POSTSOLVE_ERROR = HighsModelStatusPOSTSOLVE_ERROR +MODEL_STATUS_MODEL_EMPTY = HighsModelStatusMODEL_EMPTY +MODEL_STATUS_INFEASIBLE = HighsModelStatusINFEASIBLE +MODEL_STATUS_UNBOUNDED_OR_INFEASIBLE = HighsModelStatus_UNBOUNDED_OR_INFEASIBLE +MODEL_STATUS_UNBOUNDED = HighsModelStatusUNBOUNDED +MODEL_STATUS_OPTIMAL = HighsModelStatusOPTIMAL +MODEL_STATUS_REACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND = HighsModelStatusREACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND +MODEL_STATUS_REACHED_OBJECTIVE_TARGET = HighsModelStatusREACHED_OBJECTIVE_TARGET +MODEL_STATUS_REACHED_TIME_LIMIT = HighsModelStatusREACHED_TIME_LIMIT +MODEL_STATUS_REACHED_ITERATION_LIMIT = HighsModelStatusREACHED_ITERATION_LIMIT + +# Simplex strategy +HIGHS_SIMPLEX_STRATEGY_CHOOSE = SIMPLEX_STRATEGY_CHOOSE +HIGHS_SIMPLEX_STRATEGY_DUAL = SIMPLEX_STRATEGY_DUAL +HIGHS_SIMPLEX_STRATEGY_PRIMAL = SIMPLEX_STRATEGY_PRIMAL + +# Crash strategy +HIGHS_SIMPLEX_CRASH_STRATEGY_OFF = SIMPLEX_CRASH_STRATEGY_OFF +HIGHS_SIMPLEX_CRASH_STRATEGY_BIXBY = SIMPLEX_CRASH_STRATEGY_BIXBY +HIGHS_SIMPLEX_CRASH_STRATEGY_LTSF = SIMPLEX_CRASH_STRATEGY_LTSF + +# Edge weight strategy +HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE = SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE +HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG = SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG +HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX = SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX +HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE = SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE +# HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_UNIT_INITIAL = SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_UNIT_INITIAL + +# Objective sense +HIGHS_OBJECTIVE_SENSE_MINIMIZE = ObjSenseMINIMIZE + +# Variable types +HIGHS_VAR_TYPE_CONTINUOUS = kContinuous +HIGHS_VAR_TYPE_INTEGER = kInteger +HIGHS_VAR_TYPE_SEMI_CONTINUOUS = kSemiContinuous +HIGHS_VAR_TYPE_SEMI_INTEGER = kSemiInteger +HIGHS_VAR_TYPE_IMPLICIT_INTEGER = kImplicitInteger diff --git a/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx b/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx new file mode 100644 index 000000000000..d5da4e4fea25 --- /dev/null +++ b/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx @@ -0,0 +1,736 @@ +# cython: language_level=3 + +import numpy as np +cimport numpy as np +from scipy.optimize import OptimizeWarning +from warnings import warn +import numbers + +from libcpp.string cimport string +from libcpp.map cimport map as cppmap +from libcpp.cast cimport reinterpret_cast + +from .HConst cimport ( + HIGHS_CONST_INF, + + HighsModelStatus, + HighsModelStatusNOTSET, + HighsModelStatusMODEL_ERROR, + HighsModelStatusOPTIMAL, + HighsModelStatusREACHED_TIME_LIMIT, + HighsModelStatusREACHED_ITERATION_LIMIT, + + HighsOptionTypeBOOL, + HighsOptionTypeINT, + HighsOptionTypeDOUBLE, + HighsOptionTypeSTRING, + + HighsBasisStatus, + HighsBasisStatusLOWER, + HighsBasisStatusUPPER, + + MatrixFormatkColwise, + HighsVarType, +) +from .Highs cimport Highs +from .HighsStatus cimport ( + HighsStatus, + highsStatusToString, + HighsStatusError, + HighsStatusWarning, + HighsStatusOK, +) +from .HighsLp cimport ( + HighsLp, + HighsSolution, + HighsBasis, +) +from .HighsInfo cimport HighsInfo +from .HighsOptions cimport ( + HighsOptions, + OptionRecord, + OptionRecordBool, + OptionRecordInt, + OptionRecordDouble, + OptionRecordString, +) +from .HighsModelUtils cimport utilBasisStatusToString + +np.import_array() + +# options to reference for default values and bounds; +# make a map to quickly lookup +cdef HighsOptions _ref_opts +cdef cppmap[string, OptionRecord*] _ref_opt_lookup +cdef OptionRecord * _r = NULL +for _r in _ref_opts.records: + _ref_opt_lookup[_r.name] = _r + + +cdef str _opt_warning(string name, val, valid_set=None) noexcept: + cdef OptionRecord * r = _ref_opt_lookup[name] + + # BOOL + if r.type == HighsOptionTypeBOOL: + default_value = ( r).default_value + return ('Option "%s" is "%s", but only True or False is allowed. ' + 'Using default: %s.' % (name.decode(), str(val), default_value)) + + # INT + if r.type == HighsOptionTypeINT: + lower_bound = int(( r).lower_bound) + upper_bound = int(( r).upper_bound) + default_value = int(( r).default_value) + if upper_bound - lower_bound < 10: + int_range = str(set(range(lower_bound, upper_bound + 1))) + else: + int_range = '[%d, %d]' % (lower_bound, upper_bound) + return ('Option "%s" is "%s", but only values in %s are allowed. ' + 'Using default: %d.' % (name.decode(), str(val), int_range, default_value)) + + # DOUBLE + if r.type == HighsOptionTypeDOUBLE: + lower_bound = ( r).lower_bound + upper_bound = ( r).upper_bound + default_value = ( r).default_value + return ('Option "%s" is "%s", but only values in (%g, %g) are allowed. ' + 'Using default: %g.' % (name.decode(), str(val), lower_bound, upper_bound, default_value)) + + # STRING + if r.type == HighsOptionTypeSTRING: + if valid_set is not None: + descr = 'but only values in %s are allowed. ' % str(set(valid_set)) + else: + descr = 'but this is an invalid value. %s. ' % r.description.decode() + default_value = ( r).default_value.decode() + return ('Option "%s" is "%s", ' + '%s' + 'Using default: %s.' % (name.decode(), str(val), descr, default_value)) + + # We don't know what type (should be unreachable)? + return('Option "%s" is "%s", but this is not a valid value. ' + 'See documentation for valid options. ' + 'Using default.' % (name.decode(), str(val))) + +cdef apply_options(dict options, Highs & highs) noexcept: + '''Take options from dictionary and apply to HiGHS object.''' + + # Initialize for error checking + cdef HighsStatus opt_status = HighsStatusOK + + # Do all the ints + for opt in set([ + 'allowed_simplex_cost_scale_factor', + 'allowed_simplex_matrix_scale_factor', + 'dual_simplex_cleanup_strategy', + 'ipm_iteration_limit', + 'keep_n_rows', + 'threads', + 'mip_max_nodes', + 'highs_debug_level', + 'simplex_crash_strategy', + 'simplex_dual_edge_weight_strategy', + 'simplex_dualise_strategy', + 'simplex_iteration_limit', + 'simplex_permute_strategy', + 'simplex_price_strategy', + 'simplex_primal_edge_weight_strategy', + 'simplex_scale_strategy', + 'simplex_strategy', + 'simplex_update_limit', + 'small_matrix_value', + ]): + val = options.get(opt, None) + if val is not None: + if not isinstance(val, int): + warn(_opt_warning(opt.encode(), val), OptimizeWarning) + else: + opt_status = highs.setHighsOptionValueInt(opt.encode(), val) + if opt_status != HighsStatusOK: + warn(_opt_warning(opt.encode(), val), OptimizeWarning) + else: + if opt == "threads": + highs.resetGlobalScheduler(blocking=True) + + # Do all the doubles + for opt in set([ + 'dual_feasibility_tolerance', + 'dual_objective_value_upper_bound', + 'dual_simplex_cost_perturbation_multiplier', + 'dual_steepest_edge_weight_log_error_threshhold', + 'infinite_bound', + 'infinite_cost', + 'ipm_optimality_tolerance', + 'large_matrix_value', + 'primal_feasibility_tolerance', + 'simplex_initial_condition_tolerance', + 'small_matrix_value', + 'start_crossover_tolerance', + 'time_limit', + 'mip_rel_gap' + ]): + val = options.get(opt, None) + if val is not None: + if not isinstance(val, numbers.Number): + warn(_opt_warning(opt.encode(), val), OptimizeWarning) + else: + opt_status = highs.setHighsOptionValueDbl(opt.encode(), val) + if opt_status != HighsStatusOK: + warn(_opt_warning(opt.encode(), val), OptimizeWarning) + + + # Do all the strings + for opt in set(['solver']): + val = options.get(opt, None) + if val is not None: + if not isinstance(val, str): + warn(_opt_warning(opt.encode(), val), OptimizeWarning) + else: + opt_status = highs.setHighsOptionValueStr(opt.encode(), val.encode()) + if opt_status != HighsStatusOK: + warn(_opt_warning(opt.encode(), val), OptimizeWarning) + + + # Do all the bool to strings + for opt in set([ + 'parallel', + 'presolve', + ]): + val = options.get(opt, None) + if val is not None: + if isinstance(val, bool): + if val: + val0 = b'on' + else: + val0 = b'off' + opt_status = highs.setHighsOptionValueStr(opt.encode(), val0) + if opt_status != HighsStatusOK: + warn(_opt_warning(opt.encode(), val, valid_set=[True, False]), OptimizeWarning) + else: + warn(_opt_warning(opt.encode(), val, valid_set=[True, False]), OptimizeWarning) + + + # Do the actual bools + for opt in set([ + 'less_infeasible_DSE_check', + 'less_infeasible_DSE_choose_row', + 'log_to_console', + 'mps_parser_type_free', + 'output_flag', + 'run_as_hsol', + 'run_crossover', + 'simplex_initial_condition_check', + 'use_original_HFactor_logic', + ]): + val = options.get(opt, None) + if val is not None: + if val in [True, False]: + opt_status = highs.setHighsOptionValueBool(opt.encode(), val) + if opt_status != HighsStatusOK: + warn(_opt_warning(opt.encode(), val), OptimizeWarning) + else: + warn(_opt_warning(opt.encode(), val), OptimizeWarning) + + +ctypedef HighsVarType* HighsVarType_ptr + + +def _highs_wrapper( + double[::1] c, + int[::1] astart, + int[::1] aindex, + double[::1] avalue, + double[::1] lhs, + double[::1] rhs, + double[::1] lb, + double[::1] ub, + np.uint8_t[::1] integrality, + dict options): + '''Solve linear programs using HiGHS [1]_. + + Assume problems of the form: + + MIN c.T @ x + s.t. lhs <= A @ x <= rhs + lb <= x <= ub + + Parameters + ---------- + c : 1-D array, (n,) + Array of objective value coefficients. + astart : 1-D array + CSC format index array. + aindex : 1-D array + CSC format index array. + avalue : 1-D array + Data array of the matrix. + lhs : 1-D array (or None), (m,) + Array of left hand side values of the inequality constraints. + If ``lhs=None``, then an array of ``-inf`` is assumed. + rhs : 1-D array, (m,) + Array of right hand side values of the inequality constraints. + lb : 1-D array (or None), (n,) + Lower bounds on solution variables x. If ``lb=None``, then an + array of all `0` is assumed. + ub : 1-D array (or None), (n,) + Upper bounds on solution variables x. If ``ub=None``, then an + array of ``inf`` is assumed. + options : dict + A dictionary of solver options with the following fields: + + - allowed_simplex_cost_scale_factor : int + Undocumented advanced option. + + - allowed_simplex_matrix_scale_factor : int + Undocumented advanced option. + + - dual_feasibility_tolerance : double + Dual feasibility tolerance for simplex. + ``min(dual_feasibility_tolerance, + primal_feasibility_tolerance)`` will be used for + ipm feasibility tolerance. + + - dual_objective_value_upper_bound : double + Upper bound on objective value for dual simplex: + algorithm terminates if reached + + - dual_simplex_cleanup_strategy : int + Undocumented advanced option. + + - dual_simplex_cost_perturbation_multiplier : double + Undocumented advanced option. + + - dual_steepest_edge_weight_log_error_threshhold : double + Undocumented advanced option. + + - infinite_bound : double + Limit on abs(constraint bound): values larger than + this will be treated as infinite + + - infinite_cost : double + Limit on cost coefficient: values larger than this + will be treated as infinite. + + - ipm_iteration_limit : int + Iteration limit for interior-point solver. + + - ipm_optimality_tolerance : double + Optimality tolerance for IPM. + + - keep_n_rows : int {-1, 0, 1} + Undocumented advanced option. + + - ``-1``: ``KEEP_N_ROWS_DELETE_ROWS`` + - ``0``: ``KEEP_N_ROWS_DELETE_ENTRIES`` + - ``1``: ``KEEP_N_ROWS_KEEP_ROWS`` + + - large_matrix_value : double + Upper limit on abs(matrix entries): values larger than + this will be treated as infinite + + - less_infeasible_DSE_check : bool + Undocumented advanced option. + + - less_infeasible_DSE_choose_row : bool + Undocumented advanced option. + + - threads : int + Maximum number of threads in parallel execution. + + - message_level : int {0, 1, 2, 4, 7} + Verbosity level, corresponds to: + + - ``0``: ``ML_NONE`` + All messaging to stdout is suppressed. + + - ``1``: ``ML_VERBOSE`` + Includes a once-per-iteration report on simplex/ipm + progress and information about each nonzero row and + column. + + - ``2``: ``ML_DETAILED`` + Includes technical information about progress and + events in applying the simplex method. + + - ``4``: ``ML_MINIMAL`` + Once-per-solve information about progress as well as a + once-per-basis-matrix-reinversion report on progress in + simplex or a once-per-iteration report on progress in IPX. + + ``message_level`` behaves like a bitmask, i.e., any + combination of levels is possible using the bit-or + operator. + + - mps_parser_type_free : bool + Use free format MPS parsing. + + - parallel : bool + Run the solver in serial (False) or parallel (True). + + - presolve : bool + Run the presolve or not (or if ``None``, then choose). + + - primal_feasibility_tolerance : double + Primal feasibility tolerance. + ``min(dual_feasibility_tolerance, + primal_feasibility_tolerance)`` will be used for + ipm feasibility tolerance. + + - run_as_hsol : bool + Undocumented advanced option. + + - run_crossover : bool + Advanced option. Toggles running the crossover routine + for IPX. + + - sense : int {1, -1} + ``sense=1`` corresponds to the MIN problem, ``sense=-1`` + corresponds to the MAX problem. TODO: NOT IMPLEMENTED + + - simplex_crash_strategy : int {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + Strategy for simplex crash: off / LTSSF / Bixby (0/1/2). + Default is ``0``. Corresponds to the following: + + - ``0``: ``SIMPLEX_CRASH_STRATEGY_OFF`` + - ``1``: ``SIMPLEX_CRASH_STRATEGY_LTSSF_K`` + - ``2``: ``SIMPLEX_CRASH_STRATEGY_BIXBY`` + - ``3``: ``SIMPLEX_CRASH_STRATEGY_LTSSF_PRI`` + - ``4``: ``SIMPLEX_CRASH_STRATEGY_LTSF_K`` + - ``5``: ``SIMPLEX_CRASH_STRATEGY_LTSF_PRI`` + - ``6``: ``SIMPLEX_CRASH_STRATEGY_LTSF`` + - ``7``: ``SIMPLEX_CRASH_STRATEGY_BIXBY_NO_NONZERO_COL_COSTS`` + - ``8``: ``SIMPLEX_CRASH_STRATEGY_BASIC`` + - ``9``: ``SIMPLE_CRASH_STRATEGY_TEST_SING`` + + - simplex_dualise_strategy : int + Undocumented advanced option. + + - simplex_dual_edge_weight_strategy : int {0, 1, 2, 3, 4} + Strategy for simplex dual edge weights: + Dantzig / Devex / Steepest Edge. Corresponds + to the following: + + - ``0``: ``SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_DANTZIG`` + - ``1``: ``SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_DEVEX`` + - ``2``: ``SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_TO_DEVEX_SWITCH`` + - ``3``: ``SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE`` + - ``4``: ``SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_UNIT_INITIAL`` + + - simplex_initial_condition_check : bool + Undocumented advanced option. + + - simplex_initial_condition_tolerance : double + Undocumented advanced option. + + - simplex_iteration_limit : int + Iteration limit for simplex solver. + + - simplex_permute_strategy : int + Undocumented advanced option. + + - simplex_price_strategy : int + Undocumented advanced option. + + - simplex_primal_edge_weight_strategy : int {0, 1} + Strategy for simplex primal edge weights: + Dantzig / Devex. Corresponds to the following: + + - ``0``: ``SIMPLEX_PRIMAL_EDGE_WEIGHT_STRATEGY_DANTZIG`` + - ``1``: ``SIMPLEX_PRIMAL_EDGE_WEIGHT_STRATEGY_DEVEX`` + + - simplex_scale_strategy : int {0, 1, 2, 3, 4, 5} + Strategy for scaling before simplex solver: + off / on (0/1) + + - ``0``: ``SIMPLEX_SCALE_STRATEGY_OFF`` + - ``1``: ``SIMPLEX_SCALE_STRATEGY_HIGHS`` + - ``2``: ``SIMPLEX_SCALE_STRATEGY_HIGHS_FORCED`` + - ``3``: ``SIMPLEX_SCALE_STRATEGY_HIGHS_015`` + - ``4``: ``SIMPLEX_SCALE_STRATEGY_HIGHS_0157`` + - ``5``: ``SIMPLEX_SCALE_STRATEGY_HSOL`` + + - simplex_strategy : int {0, 1, 2, 3, 4} + Strategy for simplex solver. Default: 1. Corresponds + to the following: + + - ``0``: ``SIMPLEX_STRATEGY_MIN`` + - ``1``: ``SIMPLEX_STRATEGY_DUAL`` + - ``2``: ``SIMPLEX_STRATEGY_DUAL_TASKS`` + - ``3``: ``SIMPLEX_STRATEGY_DUAL_MULTI`` + - ``4``: ``SIMPLEX_STRATEGY_PRIMAL`` + + - simplex_update_limit : int + Limit on the number of simplex UPDATE operations. + + - small_matrix_value : double + Lower limit on abs(matrix entries): values smaller + than this will be treated as zero. + + - solution_file : str + Solution file + + - solver : str {'simplex', 'ipm'} + Choose which solver to use. If ``solver='simplex'`` + and ``parallel=True`` then PAMI will be used. + + - start_crossover_tolerance : double + Tolerance to be satisfied before IPM crossover will + start. + + - time_limit : double + Max number of seconds to run the solver for. + + - use_original_HFactor_logic : bool + Undocumented advanced option. + + - write_solution_to_file : bool + Write the primal and dual solution to a file + + - write_solution_pretty : bool + Write the primal and dual solution in a pretty + (human-readable) format + + See [2]_ for a list of all non-advanced options. + + Returns + ------- + res : dict + + If model_status is one of OPTIMAL, + REACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND, REACHED_TIME_LIMIT, + REACHED_ITERATION_LIMIT: + + - ``status`` : int + Model status code. + + - ``message`` : str + Message corresponding to model status code. + + - ``x`` : list + Solution variables. + + - ``slack`` : list + Slack variables. + + - ``lambda`` : list + Lagrange multipliers associated with the constraints + Ax = b. + + - ``s`` : list + Lagrange multipliers associated with the constraints + x >= 0. + + - ``fun`` + Final objective value. + + - ``simplex_nit`` : int + Number of iterations accomplished by the simplex + solver. + + - ``ipm_nit`` : int + Number of iterations accomplished by the interior- + point solver. + + If model_status is not one of the above: + + - ``status`` : int + Model status code. + + - ``message`` : str + Message corresponding to model status code. + + Notes + ----- + If ``options['write_solution_to_file']`` is ``True`` but + ``options['solution_file']`` is unset or ``''``, then the solution + will be printed to ``stdout``. + + If any iteration limit is reached, no solution will be + available. + + ``OptimizeWarning`` will be raised if any option value set by + the user is found to be incorrect. + + References + ---------- + .. [1] https://highs.dev/ + .. [2] https://www.maths.ed.ac.uk/hall/HiGHS/HighsOptions.html + ''' + + cdef int numcol = c.size + cdef int numrow = rhs.size + cdef int numnz = avalue.size + cdef int numintegrality = integrality.size + + # Fill up a HighsLp object + cdef HighsLp lp + lp.num_col_ = numcol + lp.num_row_ = numrow + lp.a_matrix_.num_col_ = numcol + lp.a_matrix_.num_row_ = numrow + lp.a_matrix_.format_ = MatrixFormatkColwise + + lp.col_cost_.resize(numcol) + lp.col_lower_.resize(numcol) + lp.col_upper_.resize(numcol) + + lp.row_lower_.resize(numrow) + lp.row_upper_.resize(numrow) + lp.a_matrix_.start_.resize(numcol + 1) + lp.a_matrix_.index_.resize(numnz) + lp.a_matrix_.value_.resize(numnz) + + # only need to set integrality if it's not's empty + cdef HighsVarType * integrality_ptr = NULL + if numintegrality > 0: + lp.integrality_.resize(numintegrality) + integrality_ptr = reinterpret_cast[HighsVarType_ptr](&integrality[0]) + lp.integrality_.assign(integrality_ptr, integrality_ptr + numcol) + + # Explicitly create pointers to pass to HiGHS C++ API; + # do checking to make sure null memory-views are not + # accessed (e.g., &lhs[0] raises exception when lhs is + # empty!) + cdef: + double * colcost_ptr = NULL + double * collower_ptr = NULL + double * colupper_ptr = NULL + double * rowlower_ptr = NULL + double * rowupper_ptr = NULL + int * astart_ptr = NULL + int * aindex_ptr = NULL + double * avalue_ptr = NULL + if numrow > 0: + rowlower_ptr = &lhs[0] + rowupper_ptr = &rhs[0] + lp.row_lower_.assign(rowlower_ptr, rowlower_ptr + numrow) + lp.row_upper_.assign(rowupper_ptr, rowupper_ptr + numrow) + else: + lp.row_lower_.empty() + lp.row_upper_.empty() + if numcol > 0: + colcost_ptr = &c[0] + collower_ptr = &lb[0] + colupper_ptr = &ub[0] + lp.col_cost_.assign(colcost_ptr, colcost_ptr + numcol) + lp.col_lower_.assign(collower_ptr, collower_ptr + numcol) + lp.col_upper_.assign(colupper_ptr, colupper_ptr + numcol) + else: + lp.col_cost_.empty() + lp.col_lower_.empty() + lp.col_upper_.empty() + lp.integrality_.empty() + if numnz > 0: + astart_ptr = &astart[0] + aindex_ptr = &aindex[0] + avalue_ptr = &avalue[0] + lp.a_matrix_.start_.assign(astart_ptr, astart_ptr + numcol + 1) + lp.a_matrix_.index_.assign(aindex_ptr, aindex_ptr + numnz) + lp.a_matrix_.value_.assign(avalue_ptr, avalue_ptr + numnz) + else: + lp.a_matrix_.start_.empty() + lp.a_matrix_.index_.empty() + lp.a_matrix_.value_.empty() + + # Create the options + cdef Highs highs + apply_options(options, highs) + + # Make a Highs object and pass it everything + cdef HighsModelStatus err_model_status = HighsModelStatusNOTSET + cdef HighsStatus init_status = highs.passModel(lp) + if init_status != HighsStatusOK: + if init_status != HighsStatusWarning: + err_model_status = HighsModelStatusMODEL_ERROR + return { + 'status': err_model_status, + 'message': highs.modelStatusToString(err_model_status).decode(), + } + + # Solve the LP + cdef HighsStatus run_status = highs.run() + if run_status == HighsStatusError: + return { + 'status': highs.getModelStatus(), + 'message': highsStatusToString(run_status).decode(), + } + + # Extract what we need from the solution + cdef HighsModelStatus model_status = highs.getModelStatus() + + # We might need an info object if we can look up the solution and a place to put solution + cdef HighsInfo info = highs.getHighsInfo() # it should always be safe to get the info object + cdef HighsSolution solution + cdef HighsBasis basis + cdef double[:, ::1] marg_bnds = np.zeros((2, numcol)) # marg_bnds[0, :]: lower + + # Failure modes: + # LP: if we have anything other than an Optimal status, it + # is unsafe (and unhelpful) to read any results + # MIP: has a non-Optimal status or has timed out/reached max iterations + # 1) If not Optimal/TimedOut/MaxIter status, there is no solution + # 2) If TimedOut/MaxIter status, there may be a feasible solution. + # if the objective function value is not Infinity, then the + # current solution is feasible and can be returned. Else, there + # is no solution. + mipFailCondition = model_status not in { + HighsModelStatusOPTIMAL, + HighsModelStatusREACHED_TIME_LIMIT, + HighsModelStatusREACHED_ITERATION_LIMIT, + } or (model_status in { + HighsModelStatusREACHED_TIME_LIMIT, + HighsModelStatusREACHED_ITERATION_LIMIT, + } and (info.objective_function_value == HIGHS_CONST_INF)) + lpFailCondition = model_status != HighsModelStatusOPTIMAL + if (highs.getLp().isMip() and mipFailCondition) or (not highs.getLp().isMip() and lpFailCondition): + return { + 'status': model_status, + 'message': f'model_status is {highs.modelStatusToString(model_status).decode()}; ' + f'primal_status is {utilBasisStatusToString( info.primal_solution_status).decode()}', + 'simplex_nit': info.simplex_iteration_count, + 'ipm_nit': info.ipm_iteration_count, + 'fun': None, + 'crossover_nit': info.crossover_iteration_count, + } + # If the model status is such that the solution can be read + else: + # Should be safe to read the solution: + solution = highs.getSolution() + basis = highs.getBasis() + + # lagrangians for bounds based on column statuses + for ii in range(numcol): + if HighsBasisStatusLOWER == basis.col_status[ii]: + marg_bnds[0, ii] = solution.col_dual[ii] + elif HighsBasisStatusUPPER == basis.col_status[ii]: + marg_bnds[1, ii] = solution.col_dual[ii] + + res = { + 'status': model_status, + 'message': highs.modelStatusToString(model_status).decode(), + + # Primal solution + 'x': [solution.col_value[ii] for ii in range(numcol)], + + # Ax + s = b => Ax = b - s + # Note: this is for all constraints (A_ub and A_eq) + 'slack': [rhs[ii] - solution.row_value[ii] for ii in range(numrow)], + + # lambda are the lagrange multipliers associated with Ax=b + 'lambda': [solution.row_dual[ii] for ii in range(numrow)], + 'marg_bnds': marg_bnds, + + 'fun': info.objective_function_value, + 'simplex_nit': info.simplex_iteration_count, + 'ipm_nit': info.ipm_iteration_count, + 'crossover_nit': info.crossover_iteration_count, + } + + if highs.getLp().isMip(): + res.update({ + 'mip_node_count': info.mip_node_count, + 'mip_dual_bound': info.mip_dual_bound, + 'mip_gap': info.mip_gap, + }) + + return res diff --git a/scipy/optimize/_highs/cython/src/highs_c_api.pxd b/scipy/optimize/_highs/cython/src/highs_c_api.pxd new file mode 100644 index 000000000000..b7097caf30bc --- /dev/null +++ b/scipy/optimize/_highs/cython/src/highs_c_api.pxd @@ -0,0 +1,7 @@ +# cython: language_level=3 + +cdef extern from "highs_c_api.h" nogil: + int Highs_passLp(void* highs, int numcol, int numrow, int numnz, + double* colcost, double* collower, double* colupper, + double* rowlower, double* rowupper, + int* astart, int* aindex, double* avalue) diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index 9fdb5ec33251..8d701e5e3f67 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -1,49 +1,284 @@ -# Setup the highs library -fs = import('fs') -if not fs.exists('../../../subprojects/highs/README.md') - error('Missing the `highs` submodule! Run `git submodule update --init` to fix this.') -endif -highs_proj = subproject('highs', - default_options : ['default_library=static', - 'use_zlib=disabled']) -highs_dep = highs_proj.get_variable('highs_dep') -highspy_cpp = highs_proj.get_variable('highspy_cpp') -highsoptions_cpp = highs_proj.get_variable('highsoptions_cpp') - -scipy_highspy_dep = [ - py3_dep, - pybind11_dep, - highs_dep, - thread_dep, - atomic_dep, +highs_define_macros = [ + '-DCMAKE_BUILD_TYPE="RELEASE"', + '-DFAST_BUILD=ON', + '-DHIGHS_GITHASH="n/a"', + '-DHIGHS_COMPILATION_DATE="2021-07-09"', # cannot generate dynamically + '-DHIGHS_VERSION_MAJOR=1', # don't care about this, look at CMakelists.txt + '-DHIGHS_VERSION_MINOR=2', + '-DHIGHS_VERSION_PATCH=0', + '-DHIGHS_DIR=' + meson.current_source_dir() / '..' / '..' / '_lib' / 'highs', + '-UOPENMP', + '-UEXT_PRESOLVE', + '-USCIP_DEV', + '-UHiGHSDEV', + '-UOSI_FOUND', + '-DNDEBUG' ] -py3.extension_module( - '_highs', - sources : highspy_cpp, - dependencies: scipy_highspy_dep, - c_args: [Wno_unused_variable, Wno_unused_but_set_variable], - cpp_args: [_cpp_Wno_unused_variable, _cpp_Wno_unused_but_set_variable], +basiclu_lib = static_library('basiclu', + [ + '../../_lib/highs/src/ipm/basiclu/src/basiclu_factorize.c', + '../../_lib/highs/src/ipm/basiclu/src/basiclu_get_factors.c', + '../../_lib/highs/src/ipm/basiclu/src/basiclu_initialize.c', + '../../_lib/highs/src/ipm/basiclu/src/basiclu_object.c', + '../../_lib/highs/src/ipm/basiclu/src/basiclu_solve_dense.c', + '../../_lib/highs/src/ipm/basiclu/src/basiclu_solve_for_update.c', + '../../_lib/highs/src/ipm/basiclu/src/basiclu_solve_sparse.c', + '../../_lib/highs/src/ipm/basiclu/src/basiclu_update.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_build_factors.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_condest.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_dfs.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_factorize_bump.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_file.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_garbage_perm.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_initialize.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_internal.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_markowitz.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_matrix_norm.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_pivot.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_residual_test.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_setup_bump.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_singletons.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_solve_dense.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_solve_for_update.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_solve_sparse.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_solve_symbolic.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_solve_triangular.c', + '../../_lib/highs/src/ipm/basiclu/src/lu_update.c' + ], + include_directories: [ + 'src', + '../../_lib/highs/src', + '../../_lib/highs/src/ipm/basiclu/include' + ], + c_args: [Wno_unused_variable, highs_define_macros] +) + +highs_flags = [ + _cpp_Wno_class_memaccess, + _cpp_Wno_format_truncation, + _cpp_Wno_non_virtual_dtor, + _cpp_Wno_sign_compare, + _cpp_Wno_switch, + _cpp_Wno_unused_but_set_variable, + _cpp_Wno_unused_variable, +] + +ipx_lib = static_library('ipx', + [ + '../../_lib/highs/src/ipm/ipx/src/basiclu_kernel.cc', + '../../_lib/highs/src/ipm/ipx/src/basiclu_wrapper.cc', + '../../_lib/highs/src/ipm/ipx/src/basis.cc', + '../../_lib/highs/src/ipm/ipx/src/conjugate_residuals.cc', + '../../_lib/highs/src/ipm/ipx/src/control.cc', + '../../_lib/highs/src/ipm/ipx/src/crossover.cc', + '../../_lib/highs/src/ipm/ipx/src/diagonal_precond.cc', + '../../_lib/highs/src/ipm/ipx/src/forrest_tomlin.cc', + '../../_lib/highs/src/ipm/ipx/src/guess_basis.cc', + '../../_lib/highs/src/ipm/ipx/src/indexed_vector.cc', + '../../_lib/highs/src/ipm/ipx/src/info.cc', + '../../_lib/highs/src/ipm/ipx/src/ipm.cc', + '../../_lib/highs/src/ipm/ipx/src/ipx_c.cc', + '../../_lib/highs/src/ipm/ipx/src/iterate.cc', + '../../_lib/highs/src/ipm/ipx/src/kkt_solver.cc', + '../../_lib/highs/src/ipm/ipx/src/kkt_solver_basis.cc', + '../../_lib/highs/src/ipm/ipx/src/kkt_solver_diag.cc', + '../../_lib/highs/src/ipm/ipx/src/linear_operator.cc', + '../../_lib/highs/src/ipm/ipx/src/lp_solver.cc', + '../../_lib/highs/src/ipm/ipx/src/lu_factorization.cc', + '../../_lib/highs/src/ipm/ipx/src/lu_update.cc', + '../../_lib/highs/src/ipm/ipx/src/maxvolume.cc', + '../../_lib/highs/src/ipm/ipx/src/model.cc', + '../../_lib/highs/src/ipm/ipx/src/normal_matrix.cc', + '../../_lib/highs/src/ipm/ipx/src/sparse_matrix.cc', + '../../_lib/highs/src/ipm/ipx/src/sparse_utils.cc', + '../../_lib/highs/src/ipm/ipx/src/splitted_normal_matrix.cc', + '../../_lib/highs/src/ipm/ipx/src/starting_basis.cc', + '../../_lib/highs/src/ipm/ipx/src/symbolic_invert.cc', + '../../_lib/highs/src/ipm/ipx/src/timer.cc', + '../../_lib/highs/src/ipm/ipx/src/utils.cc' + ], + include_directories: [ + '../../_lib/highs/src/ipm/ipx/include/', + '../../_lib/highs/src/ipm/basiclu/include/', + '../../_lib/highs/src/', + '../../_lib/highs/extern/', + 'cython/src/' + ], + dependencies: thread_dep, + cpp_args: [highs_flags, highs_define_macros] +) + +highs_lib = static_library('highs', + [ + '../../_lib/highs/extern/filereaderlp/reader.cpp', + '../../_lib/highs/src/io/Filereader.cpp', + '../../_lib/highs/src/io/FilereaderLp.cpp', + '../../_lib/highs/src/io/FilereaderEms.cpp', + '../../_lib/highs/src/io/FilereaderMps.cpp', + '../../_lib/highs/src/io/HighsIO.cpp', + '../../_lib/highs/src/io/HMPSIO.cpp', + '../../_lib/highs/src/io/HMpsFF.cpp', + '../../_lib/highs/src/io/LoadOptions.cpp', + '../../_lib/highs/src/ipm/IpxWrapper.cpp', + '../../_lib/highs/src/lp_data/Highs.cpp', + '../../_lib/highs/src/lp_data/HighsDebug.cpp', + '../../_lib/highs/src/lp_data/HighsInfo.cpp', + '../../_lib/highs/src/lp_data/HighsInfoDebug.cpp', + '../../_lib/highs/src/lp_data/HighsDeprecated.cpp', + '../../_lib/highs/src/lp_data/HighsInterface.cpp', + '../../_lib/highs/src/lp_data/HighsLp.cpp', + '../../_lib/highs/src/lp_data/HighsLpUtils.cpp', + '../../_lib/highs/src/lp_data/HighsModelUtils.cpp', + '../../_lib/highs/src/lp_data/HighsRanging.cpp', + '../../_lib/highs/src/lp_data/HighsSolution.cpp', + '../../_lib/highs/src/lp_data/HighsSolutionDebug.cpp', + '../../_lib/highs/src/lp_data/HighsSolve.cpp', + '../../_lib/highs/src/lp_data/HighsStatus.cpp', + '../../_lib/highs/src/lp_data/HighsOptions.cpp', + '../../_lib/highs/src/mip/HighsMipSolver.cpp', + '../../_lib/highs/src/mip/HighsMipSolverData.cpp', + '../../_lib/highs/src/mip/HighsDomain.cpp', + '../../_lib/highs/src/mip/HighsDynamicRowMatrix.cpp', + '../../_lib/highs/src/mip/HighsLpRelaxation.cpp', + '../../_lib/highs/src/mip/HighsSeparation.cpp', + '../../_lib/highs/src/mip/HighsSeparator.cpp', + '../../_lib/highs/src/mip/HighsTableauSeparator.cpp', + '../../_lib/highs/src/mip/HighsModkSeparator.cpp', + '../../_lib/highs/src/mip/HighsPathSeparator.cpp', + '../../_lib/highs/src/mip/HighsCutGeneration.cpp', + '../../_lib/highs/src/mip/HighsSearch.cpp', + '../../_lib/highs/src/mip/HighsConflictPool.cpp', + '../../_lib/highs/src/mip/HighsCutPool.cpp', + '../../_lib/highs/src/mip/HighsCliqueTable.cpp', + '../../_lib/highs/src/mip/HighsGFkSolve.cpp', + '../../_lib/highs/src/mip/HighsTransformedLp.cpp', + '../../_lib/highs/src/mip/HighsLpAggregator.cpp', + '../../_lib/highs/src/mip/HighsDebugSol.cpp', + '../../_lib/highs/src/mip/HighsImplications.cpp', + '../../_lib/highs/src/mip/HighsPrimalHeuristics.cpp', + '../../_lib/highs/src/mip/HighsPseudocost.cpp', + '../../_lib/highs/src/mip/HighsRedcostFixing.cpp', + '../../_lib/highs/src/mip/HighsNodeQueue.cpp', + '../../_lib/highs/src/mip/HighsObjectiveFunction.cpp', + '../../_lib/highs/src/model/HighsHessian.cpp', + '../../_lib/highs/src/model/HighsHessianUtils.cpp', + '../../_lib/highs/src/model/HighsModel.cpp', + '../../_lib/highs/src/parallel/HighsTaskExecutor.cpp', + '../../_lib/highs/src/presolve/ICrash.cpp', + '../../_lib/highs/src/presolve/ICrashUtil.cpp', + '../../_lib/highs/src/presolve/ICrashX.cpp', + '../../_lib/highs/src/presolve/HighsPostsolveStack.cpp', + '../../_lib/highs/src/presolve/HighsSymmetry.cpp', + '../../_lib/highs/src/presolve/HPresolve.cpp', + '../../_lib/highs/src/presolve/PresolveComponent.cpp', + '../../_lib/highs/src/qpsolver/basis.cpp', + '../../_lib/highs/src/qpsolver/quass.cpp', + '../../_lib/highs/src/qpsolver/ratiotest.cpp', + '../../_lib/highs/src/qpsolver/scaling.cpp', + '../../_lib/highs/src/qpsolver/perturbation.cpp', + '../../_lib/highs/src/simplex/HEkk.cpp', + '../../_lib/highs/src/simplex/HEkkControl.cpp', + '../../_lib/highs/src/simplex/HEkkDebug.cpp', + '../../_lib/highs/src/simplex/HEkkPrimal.cpp', + '../../_lib/highs/src/simplex/HEkkDual.cpp', + '../../_lib/highs/src/simplex/HEkkDualRHS.cpp', + '../../_lib/highs/src/simplex/HEkkDualRow.cpp', + '../../_lib/highs/src/simplex/HEkkDualMulti.cpp', + '../../_lib/highs/src/simplex/HEkkInterface.cpp', + '../../_lib/highs/src/simplex/HighsSimplexAnalysis.cpp', + '../../_lib/highs/src/simplex/HSimplex.cpp', + '../../_lib/highs/src/simplex/HSimplexDebug.cpp', + '../../_lib/highs/src/simplex/HSimplexNla.cpp', + '../../_lib/highs/src/simplex/HSimplexNlaDebug.cpp', + '../../_lib/highs/src/simplex/HSimplexNlaFreeze.cpp', + '../../_lib/highs/src/simplex/HSimplexNlaProductForm.cpp', + '../../_lib/highs/src/simplex/HSimplexReport.cpp', + '../../_lib/highs/src/test/DevKkt.cpp', + '../../_lib/highs/src/test/KktCh2.cpp', + '../../_lib/highs/src/util/HFactor.cpp', + '../../_lib/highs/src/util/HFactorDebug.cpp', + '../../_lib/highs/src/util/HFactorExtend.cpp', + '../../_lib/highs/src/util/HFactorRefactor.cpp', + '../../_lib/highs/src/util/HFactorUtils.cpp', + '../../_lib/highs/src/util/HighsHash.cpp', + '../../_lib/highs/src/util/HighsLinearSumBounds.cpp', + '../../_lib/highs/src/util/HighsMatrixPic.cpp', + '../../_lib/highs/src/util/HighsMatrixUtils.cpp', + '../../_lib/highs/src/util/HighsSort.cpp', + '../../_lib/highs/src/util/HighsSparseMatrix.cpp', + '../../_lib/highs/src/util/HighsUtils.cpp', + '../../_lib/highs/src/util/HSet.cpp', + '../../_lib/highs/src/util/HVectorBase.cpp', + '../../_lib/highs/src/util/stringutil.cpp', + '../../_lib/highs/src/interfaces/highs_c_api.cpp' + ], + include_directories: [ + 'src/', + '../../_lib/highs/extern/', + '../../_lib/highs/src/', + '../../_lib/highs/src/io/', + '../../_lib/highs/src/ipm/ipx/include/', + '../../_lib/highs/src/lp_data/', + '../../_lib/highs/src/util/', + ], + dependencies: thread_dep, + cpp_args: [highs_flags, highs_define_macros] +) + +_highs_wrapper = py3.extension_module('_highs_wrapper', + cython_gen_cpp.process('cython/src/_highs_wrapper.pyx'), + include_directories: [ + 'cython/src/', + 'src/', + '../../_lib/highs/src/', + '../../_lib/highs/src/io/', + '../../_lib/highs/src/lp_data/', + '../../_lib/highs/src/util/' + ], + dependencies: [np_dep, thread_dep, atomic_dep], link_args: version_link_args, - subdir: 'scipy/optimize/_highs/highspy', + link_with: [highs_lib, ipx_lib, basiclu_lib], + cpp_args: [highs_flags, highs_define_macros, cython_c_args], install: true, + subdir: 'scipy/optimize/_highs' ) - -py3.extension_module( - '_highs_options', - sources : highsoptions_cpp, - dependencies: scipy_highspy_dep, - c_args: [Wno_unused_variable, Wno_unused_but_set_variable], - cpp_args: [_cpp_Wno_unused_variable, _cpp_Wno_unused_but_set_variable], +_highs_constants = py3.extension_module('_highs_constants', + cython_gen_cpp.process('cython/src/_highs_constants.pyx'), + c_args: cython_c_args, + include_directories: [ + 'cython/src/', + 'src', + '../../_lib/highs/src/', + '../../_lib/highs/src/io/', + '../../_lib/highs/src/lp_data/', + '../../_lib/highs/src/simplex/' + ], + dependencies: thread_dep, link_args: version_link_args, - subdir: 'scipy/optimize/_highs/highspy', install: true, + subdir: 'scipy/optimize/_highs' ) py3.install_sources([ - '__init__.py', - '_highs_wrapper.py', -], + 'cython/src/HConst.pxd', + 'cython/src/Highs.pxd', + 'cython/src/HighsIO.pxd', + 'cython/src/HighsInfo.pxd', + 'cython/src/HighsLp.pxd', + 'cython/src/HighsLpUtils.pxd', + 'cython/src/HighsModelUtils.pxd', + 'cython/src/HighsOptions.pxd', + 'cython/src/HighsRuntimeOptions.pxd', + 'cython/src/HighsStatus.pxd', + 'cython/src/SimplexConst.pxd', + 'cython/src/highs_c_api.pxd' + ], + subdir: 'scipy/optimize/_highs/src/cython' +) + +py3.install_sources( + ['__init__.py'], subdir: 'scipy/optimize/_highs' ) diff --git a/scipy/optimize/_highs/src/HConfig.h b/scipy/optimize/_highs/src/HConfig.h new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/scipy/optimize/_highs/src/libhighs_export.h b/scipy/optimize/_highs/src/libhighs_export.h new file mode 100644 index 000000000000..095eab54c196 --- /dev/null +++ b/scipy/optimize/_highs/src/libhighs_export.h @@ -0,0 +1,50 @@ + +#ifndef LIBHIGHS_EXPORT_H +#define LIBHIGHS_EXPORT_H + +#ifdef LIBHIGHS_STATIC_DEFINE +# define LIBHIGHS_EXPORT +# define LIBHIGHS_NO_EXPORT +#else +# ifndef LIBHIGHS_EXPORT +# ifdef libhighs_EXPORTS + /* We are building this library */ +# if defined(_MSC_VER) +# define LIBHIGHS_EXPORT __declspec(dllexport) +# else +# define LIBHIGHS_EXPORT __attribute__((visibility("default"))) +# endif +# else + /* We are using this library */ +# if defined(_MSC_VER) +# define LIBHIGHS_EXPORT __declspec(dllexport) +# else +# define LIBHIGHS_EXPORT __attribute__((visibility("default"))) +# endif +# endif +# endif + +# ifndef LIBHIGHS_NO_EXPORT +# define LIBHIGHS_NO_EXPORT __attribute__((visibility("hidden"))) +# endif +#endif + +#ifndef LIBHIGHS_DEPRECATED +# define LIBHIGHS_DEPRECATED __attribute__ ((__deprecated__)) +#endif + +#ifndef LIBHIGHS_DEPRECATED_EXPORT +# define LIBHIGHS_DEPRECATED_EXPORT LIBHIGHS_EXPORT LIBHIGHS_DEPRECATED +#endif + +#ifndef LIBHIGHS_DEPRECATED_NO_EXPORT +# define LIBHIGHS_DEPRECATED_NO_EXPORT LIBHIGHS_NO_EXPORT LIBHIGHS_DEPRECATED +#endif + +#if 0 /* DEFINE_NO_DEPRECATED */ +# ifndef LIBHIGHS_NO_DEPRECATED +# define LIBHIGHS_NO_DEPRECATED +# endif +#endif + +#endif /* LIBHIGHS_EXPORT_H */ diff --git a/scipy/optimize/_linprog_highs.py b/scipy/optimize/_linprog_highs.py index e703a32c41d9..eb07443bb255 100644 --- a/scipy/optimize/_linprog_highs.py +++ b/scipy/optimize/_linprog_highs.py @@ -13,184 +13,97 @@ """ -from enum import Enum +import inspect import numpy as np from ._optimize import OptimizeWarning, OptimizeResult from warnings import warn from ._highs._highs_wrapper import _highs_wrapper +from ._highs._highs_constants import ( + CONST_INF, + MESSAGE_LEVEL_NONE, + HIGHS_OBJECTIVE_SENSE_MINIMIZE, + + MODEL_STATUS_NOTSET, + MODEL_STATUS_LOAD_ERROR, + MODEL_STATUS_MODEL_ERROR, + MODEL_STATUS_PRESOLVE_ERROR, + MODEL_STATUS_SOLVE_ERROR, + MODEL_STATUS_POSTSOLVE_ERROR, + MODEL_STATUS_MODEL_EMPTY, + MODEL_STATUS_OPTIMAL, + MODEL_STATUS_INFEASIBLE, + MODEL_STATUS_UNBOUNDED_OR_INFEASIBLE, + MODEL_STATUS_UNBOUNDED, + MODEL_STATUS_REACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND + as MODEL_STATUS_RDOVUB, + MODEL_STATUS_REACHED_OBJECTIVE_TARGET, + MODEL_STATUS_REACHED_TIME_LIMIT, + MODEL_STATUS_REACHED_ITERATION_LIMIT, + + HIGHS_SIMPLEX_STRATEGY_DUAL, + + HIGHS_SIMPLEX_CRASH_STRATEGY_OFF, + + HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE, + HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG, + HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX, + HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE, +) from scipy.sparse import csc_matrix, vstack, issparse -import scipy.optimize._highs.highspy._highs as _h -import scipy.optimize._highs.highspy._highs.simplex_constants as simpc - - -class HighsStatusMapping: - """Class to map HiGHS statuses to SciPy-like Return Codes and Messages""" - - class SciPyRC(Enum): - """Return codes like SciPy's for solvers""" - - OPTIMAL = 0 - ITERATION_LIMIT = 1 - INFEASIBLE = 2 - UNBOUNDED = 3 - NUMERICAL = 4 - - def __init__(self): - self.highs_to_scipy = { - _h.HighsModelStatus.kNotset: ( - self.SciPyRC.NUMERICAL, - "Not set", - "Serious numerical difficulties encountered.", - ), - _h.HighsModelStatus.kLoadError: ( - self.SciPyRC.NUMERICAL, - "Load Error", - "Serious numerical difficulties encountered.", - ), - _h.HighsModelStatus.kModelError: ( - self.SciPyRC.INFEASIBLE, - "Model Error", - "The problem is infeasible.", - ), - _h.HighsModelStatus.kPresolveError: ( - self.SciPyRC.NUMERICAL, - "Presolve Error", - "Serious numerical difficulties encountered.", - ), - _h.HighsModelStatus.kSolveError: ( - self.SciPyRC.NUMERICAL, - "Solve Error", - "Serious numerical difficulties encountered.", - ), - _h.HighsModelStatus.kPostsolveError: ( - self.SciPyRC.NUMERICAL, - "Postsolve Error", - "Serious numerical difficulties encountered.", - ), - _h.HighsModelStatus.kModelEmpty: ( - self.SciPyRC.NUMERICAL, - "Model Empty", - "Serious numerical difficulties encountered.", - ), - _h.HighsModelStatus.kOptimal: ( - self.SciPyRC.OPTIMAL, - "Optimal", - "Optimization terminated successfully.", - ), - _h.HighsModelStatus.kInfeasible: ( - self.SciPyRC.INFEASIBLE, - "Infeasible", - "The problem is infeasible.", - ), - _h.HighsModelStatus.kUnboundedOrInfeasible: ( - self.SciPyRC.NUMERICAL, - "Unbounded or Infeasible", - "Serious numerical difficulties encountered.", - ), - _h.HighsModelStatus.kUnbounded: ( - self.SciPyRC.UNBOUNDED, - "Unbounded", - "The problem is unbounded.", - ), - _h.HighsModelStatus.kObjectiveBound: ( - self.SciPyRC.NUMERICAL, - "Objective Bound", - "Serious numerical difficulties encountered.", - ), - _h.HighsModelStatus.kObjectiveTarget: ( - self.SciPyRC.NUMERICAL, - "Objective Target", - "Serious numerical difficulties encountered.", - ), - _h.HighsModelStatus.kTimeLimit: ( - self.SciPyRC.ITERATION_LIMIT, - "Time Limit", - "Time limit reached.", - ), - _h.HighsModelStatus.kIterationLimit: ( - self.SciPyRC.ITERATION_LIMIT, - "Iteration Limit", - "Iteration limit reached.", - ), - _h.HighsModelStatus.kUnknown: ( - self.SciPyRC.NUMERICAL, - "Unknown", - "Serious numerical difficulties encountered.", - ), - _h.HighsModelStatus.kSolutionLimit: ( - self.SciPyRC.NUMERICAL, - "Solution Limit", - "Serious numerical difficulties encountered.", - ), - } - - def get_scipy_status(self, highs_status, highs_message): - """Converts HiGHS status and message to SciPy-like status and messages""" - if highs_status is None or highs_message is None: - print(f"Highs Status: {highs_status}, Message: {highs_message}") - return ( - self.SciPyRC.NUMERICAL.value, - "HiGHS did not provide a status code. (HiGHS Status None: None)", - ) - - scipy_status_enum, message_prefix, scipy_message = self.highs_to_scipy.get( - _h.HighsModelStatus(highs_status), - ( - self.SciPyRC.NUMERICAL, - "Unknown HiGHS Status", - "The HiGHS status code was not recognized.", - ), - ) - full_scipy_msg = ( - f"{scipy_message} (HiGHS Status {int(highs_status)}: {highs_message})" - ) - return scipy_status_enum.value, full_scipy_msg + +def _highs_to_scipy_status_message(highs_status, highs_message): + """Converts HiGHS status number/message to SciPy status number/message""" + + scipy_statuses_messages = { + None: (4, "HiGHS did not provide a status code. "), + MODEL_STATUS_NOTSET: (4, ""), + MODEL_STATUS_LOAD_ERROR: (4, ""), + MODEL_STATUS_MODEL_ERROR: (2, ""), + MODEL_STATUS_PRESOLVE_ERROR: (4, ""), + MODEL_STATUS_SOLVE_ERROR: (4, ""), + MODEL_STATUS_POSTSOLVE_ERROR: (4, ""), + MODEL_STATUS_MODEL_EMPTY: (4, ""), + MODEL_STATUS_RDOVUB: (4, ""), + MODEL_STATUS_REACHED_OBJECTIVE_TARGET: (4, ""), + MODEL_STATUS_OPTIMAL: (0, "Optimization terminated successfully. "), + MODEL_STATUS_REACHED_TIME_LIMIT: (1, "Time limit reached. "), + MODEL_STATUS_REACHED_ITERATION_LIMIT: (1, "Iteration limit reached. "), + MODEL_STATUS_INFEASIBLE: (2, "The problem is infeasible. "), + MODEL_STATUS_UNBOUNDED: (3, "The problem is unbounded. "), + MODEL_STATUS_UNBOUNDED_OR_INFEASIBLE: (4, "The problem is unbounded " + "or infeasible. ")} + unrecognized = (4, "The HiGHS status code was not recognized. ") + scipy_status, scipy_message = ( + scipy_statuses_messages.get(highs_status, unrecognized)) + scipy_message = (f"{scipy_message}" + f"(HiGHS Status {highs_status}: {highs_message})") + return scipy_status, scipy_message def _replace_inf(x): - # Replace `np.inf` with kHighsInf + # Replace `np.inf` with CONST_INF infs = np.isinf(x) with np.errstate(invalid="ignore"): - x[infs] = np.sign(x[infs]) * _h.kHighsInf + x[infs] = np.sign(x[infs])*CONST_INF return x -class SimplexStrategy(Enum): - DANTZIG = "dantzig" - DEVEX = "devex" - STEEPEST_DEVEX = "steepest-devex" # highs min, choose - STEEPEST = "steepest" # highs max - - def to_highs_enum(self): - mapping = { - SimplexStrategy.DANTZIG: - simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDantzig.value, - SimplexStrategy.DEVEX: - simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDevex.value, - SimplexStrategy.STEEPEST_DEVEX: - simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyChoose.value, - SimplexStrategy.STEEPEST: - simpc.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategySteepestEdge.value, - } - return mapping.get(self) - - -def convert_to_highs_enum(option, option_str, choices_enum, default_value): - if option is None: - return choices_enum[default_value.upper()].to_highs_enum() +def _convert_to_highs_enum(option, option_str, choices): + # If option is in the choices we can look it up, if not use + # the default value taken from function signature and warn: try: - enum_value = choices_enum[option.upper()] + return choices[option.lower()] + except AttributeError: + return choices[option] except KeyError: - warn( - f"Option {option_str} is {option}, but only values in " - f"{[e.value for e in choices_enum]} are allowed. Using default: " - f"{default_value}.", - OptimizeWarning, - stacklevel=3, - ) - enum_value = choices_enum[default_value.upper()] - return enum_value.to_highs_enum() + sig = inspect.signature(_linprog_highs) + default_str = sig.parameters[option_str].default + warn(f"Option {option_str} is {option}, but only values in " + f"{set(choices.keys())} are allowed. Using default: " + f"{default_str}.", + OptimizeWarning, stacklevel=3) + return choices[default_str] def _linprog_highs(lp, solver, time_limit=None, presolve=True, @@ -396,12 +309,15 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, warn(message, OptimizeWarning, stacklevel=3) # Map options to HiGHS enum values - simplex_dual_edge_weight_strategy_enum = convert_to_highs_enum( + simplex_dual_edge_weight_strategy_enum = _convert_to_highs_enum( simplex_dual_edge_weight_strategy, - "simplex_dual_edge_weight_strategy", - choices_enum=SimplexStrategy, - default_value="dantzig", - ) + 'simplex_dual_edge_weight_strategy', + choices={'dantzig': HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DANTZIG, + 'devex': HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_DEVEX, + 'steepest-devex': HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_CHOOSE, + 'steepest': + HIGHS_SIMPLEX_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE, + None: None}) c, A_ub, b_ub, A_eq, b_eq, bounds, x0, integrality = lp @@ -423,19 +339,20 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, options = { 'presolve': presolve, - 'sense': _h.ObjSense.kMinimize, + 'sense': HIGHS_OBJECTIVE_SENSE_MINIMIZE, 'solver': solver, 'time_limit': time_limit, - # 'highs_debug_level': _h.kHighs, # TODO + 'highs_debug_level': MESSAGE_LEVEL_NONE, 'dual_feasibility_tolerance': dual_feasibility_tolerance, 'ipm_optimality_tolerance': ipm_optimality_tolerance, 'log_to_console': disp, 'mip_max_nodes': mip_max_nodes, 'output_flag': disp, 'primal_feasibility_tolerance': primal_feasibility_tolerance, - 'simplex_dual_edge_weight_strategy': simplex_dual_edge_weight_strategy_enum, - 'simplex_strategy': simpc.kSimplexStrategyDual.value, - # 'simplex_crash_strategy': simpc.SimplexCrashStrategy.kSimplexCrashStrategyOff, + 'simplex_dual_edge_weight_strategy': + simplex_dual_edge_weight_strategy_enum, + 'simplex_strategy': HIGHS_SIMPLEX_STRATEGY_DUAL, + 'simplex_crash_strategy': HIGHS_SIMPLEX_CRASH_STRATEGY_OFF, 'ipm_iteration_limit': maxiter, 'simplex_iteration_limit': maxiter, 'mip_rel_gap': mip_rel_gap, @@ -480,19 +397,12 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, # this needs to be updated if we start choosing the solver intelligently # Convert to scipy-style status and message - highs_mapper = HighsStatusMapping() - highs_status = res.get("status", None) - highs_message = res.get("message", None) - status, message = highs_mapper.get_scipy_status(highs_status, highs_message) - - def is_valid_x(val): - if isinstance(val, np.ndarray): - if val.dtype == object and None in val: - return False - return val is not None - - x = np.array(res["x"]) if "x" in res and is_valid_x(res["x"]) else None + highs_status = res.get('status', None) + highs_message = res.get('message', None) + status, message = _highs_to_scipy_status_message(highs_status, + highs_message) + x = np.array(res['x']) if 'x' in res else None sol = {'x': x, 'slack': slack, 'con': con, @@ -514,7 +424,7 @@ def is_valid_x(val): }), 'fun': res.get('fun'), 'status': status, - 'success': res['status'] == _h.HighsModelStatus.kOptimal, + 'success': res['status'] == MODEL_STATUS_OPTIMAL, 'message': message, 'nit': res.get('simplex_nit', 0) or res.get('ipm_nit', 0), 'crossover_nit': res.get('crossover_nit'), diff --git a/scipy/optimize/_milp.py b/scipy/optimize/_milp.py index f70e3297af4a..fd9ecf52083f 100644 --- a/scipy/optimize/_milp.py +++ b/scipy/optimize/_milp.py @@ -2,10 +2,10 @@ import numpy as np from scipy.sparse import csc_array, vstack, issparse from scipy._lib._util import VisibleDeprecationWarning -from ._highs._highs_wrapper import _highs_wrapper +from ._highs._highs_wrapper import _highs_wrapper # type: ignore[import] from ._constraints import LinearConstraint, Bounds from ._optimize import OptimizeResult -from ._linprog_highs import HighsStatusMapping +from ._linprog_highs import _highs_to_scipy_status_message def _constraints_to_components(constraints): @@ -377,9 +377,8 @@ def milp(c, *, integrality=None, bounds=None, constraints=None, options=None): # Convert to scipy-style status and message highs_status = highs_res.get('status', None) highs_message = highs_res.get('message', None) - hstat = HighsStatusMapping() - status, message = hstat.get_scipy_status(highs_status, - highs_message) + status, message = _highs_to_scipy_status_message(highs_status, + highs_message) res['status'] = status res['message'] = message res['success'] = (status == 0) diff --git a/scipy/optimize/tests/test_linprog.py b/scipy/optimize/tests/test_linprog.py index 63ceaa84049b..3a3f88ce41ea 100644 --- a/scipy/optimize/tests/test_linprog.py +++ b/scipy/optimize/tests/test_linprog.py @@ -315,7 +315,7 @@ def test_highs_status_message(): options=options, integrality=integrality) msg = "Time limit reached. (HiGHS Status 13:" assert res.status == 1 - assert msg in res.message + assert res.message.startswith(msg) options = {"maxiter": 10} res = linprog(c=c, A_eq=A, b_eq=b, bounds=bounds, method='highs-ds', @@ -334,14 +334,13 @@ def test_highs_status_message(): assert res.status == 3 assert res.message.startswith(msg) - from scipy.optimize._linprog_highs import HighsStatusMapping - highs_mapper = HighsStatusMapping() - status, message = highs_mapper.get_scipy_status(58, "Hello!") - assert status == 4 + from scipy.optimize._linprog_highs import _highs_to_scipy_status_message + status, message = _highs_to_scipy_status_message(58, "Hello!") msg = "The HiGHS status code was not recognized. (HiGHS Status 58:" + assert status == 4 assert message.startswith(msg) - status, message = highs_mapper.get_scipy_status(None, None) + status, message = _highs_to_scipy_status_message(None, None) msg = "HiGHS did not provide a status code. (HiGHS Status None: None)" assert status == 4 assert message.startswith(msg) diff --git a/scipy/optimize/tests/test_milp.py b/scipy/optimize/tests/test_milp.py index 8bf2d8e6b60e..0970a15a8bcc 100644 --- a/scipy/optimize/tests/test_milp.py +++ b/scipy/optimize/tests/test_milp.py @@ -112,7 +112,6 @@ def test_result(): assert not res.success msg = "Time limit reached. (HiGHS Status 13:" assert res.message.startswith(msg) - assert msg in res.message assert (res.fun is res.mip_dual_bound is res.mip_gap is res.mip_node_count is res.x is None) @@ -291,15 +290,14 @@ def test_infeasible_prob_16609(): _msg_time = "Time limit reached. (HiGHS Status 13:" -_msg_sol = "Serious numerical difficulties encountered. (HiGHS Status 16:" +_msg_iter = "Iteration limit reached. (HiGHS Status 14:" -# See https://github.com/scipy/scipy/pull/19255#issuecomment-1778438888 -@pytest.mark.xfail(reason="Often buggy, revisit with callbacks, gh-19255") @pytest.mark.skipif(np.intp(0).itemsize < 8, reason="Unhandled 32-bit GCC FP bug") +@pytest.mark.slow @pytest.mark.parametrize(["options", "msg"], [({"time_limit": 0.1}, _msg_time), - ({"node_limit": 1}, _msg_sol)]) + ({"node_limit": 1}, _msg_iter)]) def test_milp_timeout_16545(options, msg): # Ensure solution is not thrown away if MILP solver times out # -- see gh-16545 diff --git a/subprojects/highs b/subprojects/highs deleted file mode 160000 index a0df06fb20f2..000000000000 --- a/subprojects/highs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a0df06fb20f2c67c02cf84d8a3b72862c2ab2b27 From efbcba15d3624201aeb6387270726812996e7836 Mon Sep 17 00:00:00 2001 From: Jonas Eschle Date: Sat, 27 Apr 2024 05:06:38 -0400 Subject: [PATCH 055/500] ENH: Add more initialization methods for HessianUpdateStrategy (#13534) * Remove init_scale restriction to scalar * Remove init_scale restriction to scalar * ENH: add arbitrary shaped initial scale for HessianUpdateStrategy Remove the float conversion that implies a scalar. This adds a lot of flexibility to e.g. intialize with an array or even a matrix. If a matrix is given, it will be used as the initial matrix. * ENH: add arbitrary shaped initial scale for HessianUpdateStrategy Remove the float conversion that implies a scalar. This adds a lot of flexibility to e.g. intialize with an array or even a matrix. If a matrix is given, it will be used as the initial matrix. * ENH: add arbitrary shaped initial scale for HessianUpdateStrategy Remove the float conversion that implies a scalar. This adds a lot of flexibility to e.g. intialize with an array or even a matrix. If a matrix is given, it will be used as the initial matrix. * ENH: add arbitrary shaped initial scale for HessianUpdateStrategy Remove the float conversion that implies a scalar. This adds a lot of flexibility to e.g. intialize with an array or even a matrix. If a matrix is given, it will be used as the initial matrix. * ENH: add arbitrary shaped initial scale for HessianUpdateStrategy Remove the float conversion that implies a scalar. This adds a lot of flexibility to e.g. intialize with an array or even a matrix. If a matrix is given, it will be used as the initial matrix. * ENH: add arbitrary shaped initial scale for HessianUpdateStrategy Remove the float conversion that implies a scalar. This adds a lot of flexibility to e.g. intialize with an array or even a matrix. If a matrix is given, it will be used as the initial matrix. * ENH: add arbitrary shaped initial scale for HessianUpdateStrategy Remove the float conversion that implies a scalar. This adds a lot of flexibility to e.g. intialize with an array or even a matrix. If a matrix is given, it will be used as the initial matrix. * docs: improve HessianUpdateStrategy parameter init_scale * ENH: make init_scale in HessianUpdateStrategy a real array This will fail loudly if it can't be cast to the exact dtype, e.g. if it is complex. Added an extra check to ensure that this will do. Also extended the tests to check for both approximation types, hess and inv_hess * ENH: add legacy cast in HessianUpdateStrategys init_scale Added the float cast again to ensure full legacy behavior * FIX: Check if init_scale is str in HessianUpdateStrategy Gives FutureWarning otherwise as a string comparison with an array * TEST: Check if init_scale is str in HessianUpdateStrategy test Gives FutureWarning otherwise as a string comparison with an array * REFACTOR: Make elif for if that would have been elif anyway * chore: remove whitespaces * chore: remove whitespaces * chore: remove unused import * enh: add input checks * tests: add match check in message * tests: use pytests raises * tests: typo * fix: init multiply * fix: init multiply * style: fix * tests: escape regex * tests: escape regex 2 * tests: escape regex 2 * tests: escape regex 3 * tests: escape regex 4 * tests: escape regex 4 * tests: fix test with correct error * enh: add more descriptive errors * enh: add more descriptive errors * enh: be explicit about copy * chore: reformat * tests: fix exceptions * chore: fix linting * chore: fix linting 2 --- scipy/optimize/_hessian_update_strategy.py | 79 +++++++++++---- .../tests/test_hessian_update_strategy.py | 98 +++++++++++++++++-- 2 files changed, 153 insertions(+), 24 deletions(-) diff --git a/scipy/optimize/_hessian_update_strategy.py b/scipy/optimize/_hessian_update_strategy.py index b8529e51e83b..c72d1159314e 100644 --- a/scipy/optimize/_hessian_update_strategy.py +++ b/scipy/optimize/_hessian_update_strategy.py @@ -1,7 +1,7 @@ """Hessian update strategies for quasi-Newton optimization methods.""" import numpy as np from numpy.linalg import norm -from scipy.linalg import get_blas_funcs +from scipy.linalg import get_blas_funcs, issymmetric from warnings import warn @@ -188,15 +188,56 @@ def update(self, delta_x, delta_grad): return if self.first_iteration: # Get user specific scale - if self.init_scale == "auto": + if isinstance(self.init_scale, str) and self.init_scale == "auto": scale = self._auto_scale(delta_x, delta_grad) else: - scale = float(self.init_scale) - # Scale initial matrix with ``scale * np.eye(n)`` + scale = self.init_scale + + # Check for complex: numpy will silently cast a complex array to + # a real one but not so for scalar as it raises a TypeError. + # Checking here brings a consistent behavior. + replace = False + if np.size(scale) == 1: + # to account for the legacy behavior having the exact same cast + scale = float(scale) + elif np.iscomplexobj(scale): + raise TypeError("init_scale contains complex elements, " + "must be real.") + else: # test explicitly for allowed shapes and values + replace = True + if self.approx_type == 'hess': + shape = np.shape(self.B) + dtype = self.B.dtype + else: + shape = np.shape(self.H) + dtype = self.H.dtype + # copy, will replace the original + scale = np.array(scale, dtype=dtype, copy=True) + + # it has to match the shape of the matrix for the multiplication, + # no implicit broadcasting is allowed + if shape != (init_shape := np.shape(scale)): + raise ValueError("If init_scale is an array, it must have the " + f"dimensions of the hess/inv_hess: {shape}." + f" Got {init_shape}.") + if not issymmetric(scale): + raise ValueError("If init_scale is an array, it must be" + " symmetric (passing scipy.linalg.issymmetric)" + " to be an approximation of a hess/inv_hess.") + + # Scale initial matrix with ``scale * np.eye(n)`` or replace + # This is not ideal, we could assign the scale directly in + # initialize, but we would need to if self.approx_type == 'hess': - self.B *= scale + if replace: + self.B = scale + else: + self.B *= scale else: - self.H *= scale + if replace: + self.H = scale + else: + self.H *= scale self.first_iteration = False self._update_implementation(delta_x, delta_grad) @@ -254,13 +295,15 @@ class BFGS(FullHessianUpdateStrategy): unaffected by the exception strategy. By default is equal to 1e-8 when ``exception_strategy = 'skip_update'`` and equal to 0.2 when ``exception_strategy = 'damp_update'``. - init_scale : {float, 'auto'} - Matrix scale at first iteration. At the first - iteration the Hessian matrix or its inverse will be initialized - with ``init_scale*np.eye(n)``, where ``n`` is the problem dimension. + init_scale : {float, np.array, 'auto'} + This parameter can be used to initialize the Hessian or its + inverse. When a float is given, the relevant array is initialized + to ``np.eye(n) * init_scale``, where ``n`` is the problem dimension. + Alternatively, if a precisely ``(n, n)`` shaped, symmetric array is given, + this array will be used. Otherwise an error is generated. Set it to 'auto' in order to use an automatic heuristic for choosing the initial scale. The heuristic is described in [1]_, p.143. - By default uses 'auto'. + The default is 'auto'. Notes ----- @@ -309,7 +352,7 @@ def _update_inverse_hessian(self, ys, Hy, yHy, s): Second Edition (2006). """ self.H = self._syr2(-1.0 / ys, s, Hy, a=self.H) - self.H = self._syr((ys+yHy)/ys**2, s, a=self.H) + self.H = self._syr((ys + yHy) / ys ** 2, s, a=self.H) def _update_hessian(self, ys, Bs, sBs, y): """Update the Hessian matrix. @@ -385,13 +428,15 @@ class SR1(FullHessianUpdateStrategy): defines the minimum denominator magnitude allowed in the update. When the condition is violated we skip the update. By default uses ``1e-8``. - init_scale : {float, 'auto'}, optional - Matrix scale at first iteration. At the first - iteration the Hessian matrix or its inverse will be initialized - with ``init_scale*np.eye(n)``, where ``n`` is the problem dimension. + init_scale : {float, np.array, 'auto'}, optional + This parameter can be used to initialize the Hessian or its + inverse. When a float is given, the relevant array is initialized + to ``np.eye(n) * init_scale``, where ``n`` is the problem dimension. + Alternatively, if a precisely ``(n, n)`` shaped, symmetric array is given, + this array will be used. Otherwise an error is generated. Set it to 'auto' in order to use an automatic heuristic for choosing the initial scale. The heuristic is described in [1]_, p.143. - By default uses 'auto'. + The default is 'auto'. Notes ----- diff --git a/scipy/optimize/tests/test_hessian_update_strategy.py b/scipy/optimize/tests/test_hessian_update_strategy.py index e6872997881c..fe9d7a059b47 100644 --- a/scipy/optimize/tests/test_hessian_update_strategy.py +++ b/scipy/optimize/tests/test_hessian_update_strategy.py @@ -1,5 +1,8 @@ -import numpy as np +import re from copy import deepcopy + +import numpy as np +import pytest from numpy.linalg import norm from numpy.testing import (TestCase, assert_array_almost_equal, assert_array_equal, assert_array_less) @@ -49,19 +52,100 @@ def hess(self, x): class TestHessianUpdateStrategy(TestCase): + def test_hessian_initialization(self): - quasi_newton = (BFGS(), SR1()) - for qn in quasi_newton: - qn.initialize(5, 'hess') - B = qn.get_matrix() + ndims = 5 + symmetric_matrix = np.array([[43, 24, 33, 34, 49], + [24, 36, 44, 15, 44], + [33, 44, 37, 1, 30], + [34, 15, 1, 5, 46], + [49, 44, 30, 46, 22]]) + init_scales = ( + ('auto', np.eye(ndims)), + (2, np.eye(ndims) * 2), + (np.arange(1, ndims + 1) * np.eye(ndims), + np.arange(1, ndims + 1) * np.eye(ndims)), + (symmetric_matrix, symmetric_matrix),) + for approx_type in ['hess', 'inv_hess']: + for init_scale, true_matrix in init_scales: + # large min_{denominator,curvatur} makes them skip an update, + # so we can have our initial matrix + quasi_newton = (BFGS(init_scale=init_scale, + min_curvature=1e50, + exception_strategy='skip_update'), + SR1(init_scale=init_scale, + min_denominator=1e50)) - assert_array_equal(B, np.eye(5)) + for qn in quasi_newton: + qn.initialize(ndims, approx_type) + B = qn.get_matrix() + + assert_array_equal(B, np.eye(ndims)) + # don't test the auto init scale + if isinstance(init_scale, str) and init_scale == 'auto': + continue + + qn.update(np.ones(ndims) * 1e-5, np.arange(ndims) + 0.2) + B = qn.get_matrix() + assert_array_equal(B, true_matrix) # For this list of points, it is known # that no exception occur during the # Hessian update. Hence no update is - # skipped or damped. + # skiped or damped. + + + def test_initialize_catch_illegal(self): + ndims = 3 + # no complex allowed + inits_msg_errtype = ((complex(3.14), + re.escape("float() argument must be a " + "string or a real number, " + "not 'complex'"), + TypeError), + + (np.array([3.2, 2.3, 1.2]).astype(np.complex128), + "init_scale contains complex elements, " + "must be real.", + TypeError), + + (np.array([[43, 24, 33], + [24, 36, 44, ], + [33, 44, 37, ]]).astype(np.complex128), + "init_scale contains complex elements, " + "must be real.", + TypeError), + + # not square + (np.array([[43, 55, 66]]), + re.escape( + "If init_scale is an array, it must have the " + "dimensions of the hess/inv_hess: (3, 3)." + " Got (1, 3)."), + ValueError), + + # not symmetric + (np.array([[43, 24, 33], + [24.1, 36, 44, ], + [33, 44, 37, ]]), + re.escape("If init_scale is an array, it must be" + " symmetric (passing scipy.linalg.issymmetric)" + " to be an approximation of a hess/inv_hess."), + ValueError), + ) + for approx_type in ['hess', 'inv_hess']: + for init_scale, message, errortype in inits_msg_errtype: + # large min_{denominator,curvatur} makes it skip an update, + # so we can retrieve our initial matrix + quasi_newton = (BFGS(init_scale=init_scale), + SR1(init_scale=init_scale)) + + for qn in quasi_newton: + qn.initialize(ndims, approx_type) + with pytest.raises(errortype, match=message): + qn.update(np.ones(ndims), np.arange(ndims)) + def test_rosenbrock_with_no_exception(self): # Define auxiliary problem prob = Rosenbrock(n=5) From 701d8da42825f7c5124dcdaba470a841598091b3 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Sun, 28 Apr 2024 08:00:45 -0600 Subject: [PATCH 056/500] BUG: Fix error with 180 degree rotation in Rotation.align_vectors() with an infinite weight (#20573) * Fix exact rotations at 180 deg, improve near 180 deg Comments * Tests for exact near 180 deg rotations * Fix tests * Code review updates --------- Co-authored-by: Scott Shambaugh --- scipy/spatial/transform/_rotation.pyx | 25 +++++++++++++++--- .../spatial/transform/tests/test_rotation.py | 26 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/scipy/spatial/transform/_rotation.pyx b/scipy/spatial/transform/_rotation.pyx index f18a24270277..2c4a3b70df41 100644 --- a/scipy/spatial/transform/_rotation.pyx +++ b/scipy/spatial/transform/_rotation.pyx @@ -3483,13 +3483,32 @@ cdef class Rotation: # We first find the minimum angle rotation between the primary # vectors. cross = np.cross(b_pri[0], a_pri[0]) - theta = atan2(_norm3(cross), np.dot(a_pri[0], b_pri[0])) - if theta < 1e-3: # small angle Taylor series approximation + cross_norm = _norm3(cross) + theta = atan2(cross_norm, _dot3(a_pri[0], b_pri[0])) + tolerance = 1e-3 # tolerance for small angle approximation (rad) + R_flip = cls.identity() + if (np.pi - theta) < tolerance: + # Near pi radians, the Taylor series appoximation of x/sin(x) + # diverges, so for numerical stability we flip pi and then + # rotate back by the small angle pi - theta + if cross_norm == 0: + # For antiparallel vectors, cross = [0, 0, 0] so we need to + # manually set an arbitrary orthogonal axis of rotation + i = np.argmin(np.abs(a_pri[0])) + r = np.zeros(3) + r[i - 1], r[i - 2] = a_pri[0][i - 2], -a_pri[0][i - 1] + else: + r = cross # Shortest angle orthogonal axis of rotation + R_flip = Rotation.from_rotvec(r / np.linalg.norm(r) * np.pi) + theta = np.pi - theta + cross = -cross + if abs(theta) < tolerance: + # Small angle Taylor series approximation for numerical stability theta2 = theta * theta r = cross * (1 + theta2 / 6 + theta2 * theta2 * 7 / 360) else: r = cross * theta / np.sin(theta) - R_pri = cls.from_rotvec(r) + R_pri = cls.from_rotvec(r) * R_flip if N == 1: # No secondary vectors, so we are done diff --git a/scipy/spatial/transform/tests/test_rotation.py b/scipy/spatial/transform/tests/test_rotation.py index bf208f34437b..381ce7ca06f2 100644 --- a/scipy/spatial/transform/tests/test_rotation.py +++ b/scipy/spatial/transform/tests/test_rotation.py @@ -1467,6 +1467,32 @@ def test_align_vectors_parallel(): assert_allclose(R.apply(b[0]), a[0], atol=atol) +def test_align_vectors_antiparallel(): + # Test exact 180 deg rotation + atol = 1e-12 + as_to_test = np.array([[[1, 0, 0], [0, 1, 0]], + [[0, 1, 0], [1, 0, 0]], + [[0, 0, 1], [0, 1, 0]]]) + bs_to_test = [[-a[0], a[1]] for a in as_to_test] + for a, b in zip(as_to_test, bs_to_test): + R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) + assert_allclose(R.magnitude(), np.pi, atol=atol) + assert_allclose(R.apply(b[0]), a[0], atol=atol) + + # Test exact rotations near 180 deg + Rs = Rotation.random(100, random_state=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([dR.apply(a[0]), a[1]]) + for a in as_to_test: + R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) + R2, _ = Rotation.align_vectors(a, b, weights=[1e10, 1]) + assert_allclose(R.as_matrix(), R2.as_matrix(), atol=atol) + + def test_align_vectors_primary_only(): atol = 1e-12 mats_a = Rotation.random(100, random_state=0).as_matrix() From d575726949d7ac5bc2b75d0f17d0dc13006db22f Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sun, 28 Apr 2024 20:11:38 +0200 Subject: [PATCH 057/500] TYP: update supported Mypy version from 1.0.0 to 1.10.0 (#20600) --- .github/workflows/linux.yml | 2 +- pyproject.toml | 2 +- requirements/dev.txt | 2 +- scipy/_lib/_testutils.py | 4 ++-- scipy/conftest.py | 4 ++-- scipy/optimize/_milp.py | 2 +- scipy/spatial/_ckdtree.pyi | 16 ++++++++-------- scipy/special/_orthogonal.pyi | 2 +- scipy/special/_support_alternative_backends.py | 2 +- scipy/stats/_qmc.py | 6 +++--- scipy/stats/_unuran/unuran_wrapper.pyi | 12 ++++++------ 11 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 80150126c180..1096e7f06e4f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -118,7 +118,7 @@ jobs: if: matrix.python-version == '3.10' run: | # Packages that are only needed for their annotations - python -m pip install mypy==1.0.0 types-psutil typing_extensions + python -m pip install mypy==1.10.0 types-psutil typing_extensions python -m pip install pybind11 sphinx python -u dev.py mypy diff --git a/pyproject.toml b/pyproject.toml index 0a5efd36115e..826a4976c65f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ doc = [ "jupyterlite-pyodide-kernel", ] dev = [ - "mypy", + "mypy==1.10.0", "typing_extensions", "types-psutil", "pycodestyle", diff --git a/requirements/dev.txt b/requirements/dev.txt index 46086ccdc0fb..47aea16dfb5e 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,6 +1,6 @@ # Generated via tools/generate_requirements.py. # Do not edit this file; modify `pyproject.toml` instead and run `python tools/generate_requirements.py`. -mypy +mypy==1.10.0 typing_extensions types-psutil pycodestyle diff --git a/scipy/_lib/_testutils.py b/scipy/_lib/_testutils.py index e8a6d49d65cd..e71c665e51c7 100644 --- a/scipy/_lib/_testutils.py +++ b/scipy/_lib/_testutils.py @@ -17,8 +17,8 @@ try: # Need type: ignore[import-untyped] for mypy >= 1.6 - import cython # type: ignore[import] - from Cython.Compiler.Version import ( # type: ignore[import] + import cython # type: ignore[import-untyped] + from Cython.Compiler.Version import ( # type: ignore[import-untyped] version as cython_version, ) except ImportError: diff --git a/scipy/conftest.py b/scipy/conftest.py index 62fd6d745dcf..90713b01376e 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -116,7 +116,7 @@ def check_fpu_mode(request): pass try: - import torch # type: ignore[import] + import torch # type: ignore[import-not-found] xp_available_backends.update({'pytorch': torch}) # can use `mps` or `cpu` torch.set_default_device(SCIPY_DEVICE) @@ -124,7 +124,7 @@ def check_fpu_mode(request): pass try: - import cupy # type: ignore[import] + import cupy # type: ignore[import-not-found] xp_available_backends.update({'cupy': cupy}) except ImportError: pass diff --git a/scipy/optimize/_milp.py b/scipy/optimize/_milp.py index fd9ecf52083f..c1d74499d5b5 100644 --- a/scipy/optimize/_milp.py +++ b/scipy/optimize/_milp.py @@ -2,7 +2,7 @@ import numpy as np from scipy.sparse import csc_array, vstack, issparse from scipy._lib._util import VisibleDeprecationWarning -from ._highs._highs_wrapper import _highs_wrapper # type: ignore[import] +from ._highs._highs_wrapper import _highs_wrapper # type: ignore[import-not-found] from ._constraints import LinearConstraint, Bounds from ._optimize import OptimizeResult from ._linprog_highs import _highs_to_scipy_status_message diff --git a/scipy/spatial/_ckdtree.pyi b/scipy/spatial/_ckdtree.pyi index 7b16a0f73614..c6624b79a6a6 100644 --- a/scipy/spatial/_ckdtree.pyi +++ b/scipy/spatial/_ckdtree.pyi @@ -73,7 +73,7 @@ class cKDTree(Generic[_BoxType]): # The latter gives us more flexibility in setting the generic parameter # though. @overload - def __new__( # type: ignore[misc] + def __new__( # type: ignore[overload-overlap] cls, data: npt.ArrayLike, leafsize: int = ..., @@ -127,7 +127,7 @@ class cKDTree(Generic[_BoxType]): ) -> list[list[int]]: ... @overload - def query_pairs( # type: ignore[misc] + def query_pairs( # type: ignore[overload-overlap] self, r: float, p: float = ..., @@ -144,7 +144,7 @@ class cKDTree(Generic[_BoxType]): ) -> npt.NDArray[np.intp]: ... @overload - def count_neighbors( # type: ignore[misc] + def count_neighbors( # type: ignore[overload-overlap] self, other: cKDTree, r: _ArrayLike0D, @@ -153,7 +153,7 @@ class cKDTree(Generic[_BoxType]): cumulative: bool = ..., ) -> int: ... @overload - def count_neighbors( # type: ignore[misc] + def count_neighbors( # type: ignore[overload-overlap] self, other: cKDTree, r: _ArrayLike0D, @@ -162,7 +162,7 @@ class cKDTree(Generic[_BoxType]): cumulative: bool = ..., ) -> np.float64: ... @overload - def count_neighbors( # type: ignore[misc] + def count_neighbors( # type: ignore[overload-overlap] self, other: cKDTree, r: npt.ArrayLike, @@ -181,7 +181,7 @@ class cKDTree(Generic[_BoxType]): ) -> npt.NDArray[np.float64]: ... @overload - def sparse_distance_matrix( # type: ignore[misc] + def sparse_distance_matrix( # type: ignore[overload-overlap] self, other: cKDTree, max_distance: float, @@ -189,7 +189,7 @@ class cKDTree(Generic[_BoxType]): output_type: Literal["dok_matrix"] = ..., ) -> dok_matrix: ... @overload - def sparse_distance_matrix( # type: ignore[misc] + def sparse_distance_matrix( # type: ignore[overload-overlap] self, other: cKDTree, max_distance: float, @@ -197,7 +197,7 @@ class cKDTree(Generic[_BoxType]): output_type: Literal["coo_matrix"] = ..., ) -> coo_matrix: ... @overload - def sparse_distance_matrix( # type: ignore[misc] + def sparse_distance_matrix( # type: ignore[overload-overlap] self, other: cKDTree, max_distance: float, diff --git a/scipy/special/_orthogonal.pyi b/scipy/special/_orthogonal.pyi index 84da71ee3473..9388513e8735 100644 --- a/scipy/special/_orthogonal.pyi +++ b/scipy/special/_orthogonal.pyi @@ -288,7 +288,7 @@ class orthopoly1d(np.poly1d): @overload def __call__(self, x: _ArrayLike0D) -> Any: ... @overload - def __call__(self, x: np.poly1d) -> np.poly1d: ... # type: ignore[misc] + def __call__(self, x: np.poly1d) -> np.poly1d: ... # type: ignore[overload-overlap] @overload def __call__(self, x: np.typing.ArrayLike) -> np.ndarray: ... diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index f1b8ba28c2fe..1aa54969063e 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -21,7 +21,7 @@ def get_array_special_func(f_name, xp, n_array_args): elif is_torch(xp): f = getattr(xp.special, f_name, None) elif is_cupy(xp): - import cupyx # type: ignore[import] + import cupyx # type: ignore[import-not-found] f = getattr(cupyx.scipy.special, f_name, None) elif xp.__name__ == f"{array_api_compat_prefix}.jax": f = getattr(xp.scipy.special, f_name, None) diff --git a/scipy/stats/_qmc.py b/scipy/stats/_qmc.py index 484cc1367c03..1aea6a694c8f 100644 --- a/scipy/stats/_qmc.py +++ b/scipy/stats/_qmc.py @@ -668,7 +668,7 @@ def n_primes(n: IntNumber) -> list[int]: 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] # type: ignore[misc] + 953, 967, 971, 977, 983, 991, 997][:n] if len(primes) < n: big_number = 2000 @@ -1508,7 +1508,7 @@ def _random_oa_lhs(self, n: IntNumber = 4) -> np.ndarray: oa_lhs_sample /= p - return oa_lhs_sample[:, :self.d] # type: ignore + return oa_lhs_sample[:, :self.d] class Sobol(QMCEngine): @@ -1767,7 +1767,7 @@ def _random( ) sample = np.concatenate( [self._first_point, sample] - )[:n] # type: ignore[misc] + )[:n] else: _draw( n=n, num_gen=self.num_generated - 1, dim=self.d, diff --git a/scipy/stats/_unuran/unuran_wrapper.pyi b/scipy/stats/_unuran/unuran_wrapper.pyi index 96b6d4e65edf..46387c5891c6 100644 --- a/scipy/stats/_unuran/unuran_wrapper.pyi +++ b/scipy/stats/_unuran/unuran_wrapper.pyi @@ -18,7 +18,7 @@ class UNURANError(RuntimeError): class Method: @overload - def rvs(self, size: None = ...) -> float | int: ... # type: ignore[misc] + 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: ... @@ -50,7 +50,7 @@ class TransformedDensityRejection(Method): @property def squeeze_area(self) -> float: ... @overload - def ppf_hat(self, u: ArrayLike0D) -> float: ... # type: ignore[misc] + def ppf_hat(self, u: ArrayLike0D) -> float: ... # type: ignore[overload-overlap] @overload def ppf_hat(self, u: npt.ArrayLike) -> np.ndarray: ... @@ -99,11 +99,11 @@ class NumericalInversePolynomial(Method): @property def intervals(self) -> int: ... @overload - def ppf(self, u: ArrayLike0D) -> float: ... # type: ignore[misc] + 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[misc] + 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: ... @@ -135,7 +135,7 @@ class NumericalInverseHermite(Method): @property def intervals(self) -> int: ... @overload - def ppf(self, u: ArrayLike0D) -> float: ... # type: ignore[misc] + def ppf(self, u: ArrayLike0D) -> float: ... # type: ignore[overload-overlap] @overload def ppf(self, u: npt.ArrayLike) -> np.ndarray: ... def qrvs(self, @@ -174,6 +174,6 @@ class DiscreteGuideTable(Method): guide_factor: float = ..., random_state: SeedType = ...) -> None: ... @overload - def ppf(self, u: ArrayLike0D) -> float: ... # type: ignore[misc] + def ppf(self, u: ArrayLike0D) -> float: ... # type: ignore[overload-overlap] @overload def ppf(self, u: npt.ArrayLike) -> np.ndarray: ... From b9668a0f5571425620f92d7f0f3556af6f8cdfce Mon Sep 17 00:00:00 2001 From: lucascolley Date: Sun, 28 Apr 2024 23:32:53 +0100 Subject: [PATCH 058/500] MAINT: lint: temporarily disable UP031 [lint only] --- tools/lint.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/lint.toml b/tools/lint.toml index 69f7e9e2e812..4e7e66a09967 100644 --- a/tools/lint.toml +++ b/tools/lint.toml @@ -12,7 +12,8 @@ target-version = "py39" # `B028` added in gh-19623. # `ICN001` added in gh-20382 to enforce common conventions for imports select = ["E", "F", "PGH004", "UP", "B028", "ICN001"] -ignore = ["E741"] +# UP031 should be enabled once someone fixes the errors. +ignore = ["E741", "UP031"] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" From 4ee0de31b758f81be85435a0ad72d315a840441d Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:08:45 +0100 Subject: [PATCH 059/500] ENH: constants: add array api support (#20593) Co-authored-by: Ralf Gommers --- .github/workflows/array_api.yml | 1 + doc/source/dev/api-dev/array_api.rst | 1 + scipy/_lib/_array_api.py | 13 ++- scipy/constants/_constants.py | 32 ++++--- scipy/constants/tests/test_constants.py | 116 +++++++++++++++++------- 5 files changed, 116 insertions(+), 47 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 25550618857a..d046777ac3ca 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -91,6 +91,7 @@ jobs: export OMP_NUM_THREADS=2 # expand as new modules are added python dev.py --no-build test -b all -s cluster -- --durations 3 --timeout=60 + python dev.py --no-build test -b all -s constants -- --durations 3 --timeout=60 python dev.py --no-build test -b all -s fft -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.special.tests.test_support_alternative_backends -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy._lib.tests.test_array_api diff --git a/doc/source/dev/api-dev/array_api.rst b/doc/source/dev/api-dev/array_api.rst index 660871bcd5cb..0a437c5f1a10 100644 --- a/doc/source/dev/api-dev/array_api.rst +++ b/doc/source/dev/api-dev/array_api.rst @@ -95,6 +95,7 @@ variable is set: - `scipy.cluster.hierarchy` - `scipy.cluster.vq` +- `scipy.constants` - `scipy.fft` Support is provided in `scipy.special` for the following functions: diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index e32d81c70cf3..98d7f50bb6ee 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -120,9 +120,11 @@ def array_namespace(*arrays): def _asarray( - array, dtype=None, order=None, copy=None, *, xp=None, check_finite=False + array, dtype=None, order=None, copy=None, *, xp=None, check_finite=False, + subok=False ): - """SciPy-specific replacement for `np.asarray` with `order` and `check_finite`. + """SciPy-specific replacement for `np.asarray` with `order`, `check_finite`, and + `subok`. Memory layout parameter `order` is not exposed in the Array API standard. `order` is only enforced if the input array implementation @@ -131,13 +133,18 @@ def _asarray( `check_finite` is also not a keyword in the array API standard; included here for convenience rather than that having to be a separate function call inside SciPy functions. + + `subok` is included to allow this function to preserve the behaviour of + `np.asanyarray` for NumPy based inputs. """ if xp is None: xp = array_namespace(array) if xp.__name__ in {"numpy", "scipy._lib.array_api_compat.numpy"}: # Use NumPy API to support order if copy is True: - array = np.array(array, order=order, dtype=dtype) + array = np.array(array, order=order, dtype=dtype, subok=subok) + elif subok: + array = np.asanyarray(array, order=order, dtype=dtype) else: array = np.asarray(array, order=order, dtype=dtype) diff --git a/scipy/constants/_constants.py b/scipy/constants/_constants.py index 666690d47a83..7163518c0c15 100644 --- a/scipy/constants/_constants.py +++ b/scipy/constants/_constants.py @@ -13,11 +13,13 @@ from typing import TYPE_CHECKING, Any from ._codata import value as _cd -import numpy as np if TYPE_CHECKING: import numpy.typing as npt +from scipy._lib._array_api import array_namespace, _asarray + + """ BasSw 2006 physical constants: imported from CODATA @@ -269,19 +271,21 @@ def convert_temperature( array([ 233.15, 313.15]) """ + xp = array_namespace(val) + _val = _asarray(val, xp=xp, subok=True) # Convert from `old_scale` to Kelvin if old_scale.lower() in ['celsius', 'c']: - tempo = np.asanyarray(val) + zero_Celsius + tempo = _val + zero_Celsius elif old_scale.lower() in ['kelvin', 'k']: - tempo = np.asanyarray(val) + tempo = _val elif old_scale.lower() in ['fahrenheit', 'f']: - tempo = (np.asanyarray(val) - 32) * 5 / 9 + zero_Celsius + tempo = (_val - 32) * 5 / 9 + zero_Celsius elif old_scale.lower() in ['rankine', 'r']: - tempo = np.asanyarray(val) * 5 / 9 + tempo = _val * 5 / 9 else: - raise NotImplementedError("%s scale is unsupported: supported scales " - "are Celsius, Kelvin, Fahrenheit, and " - "Rankine" % old_scale) + raise NotImplementedError(f"{old_scale=} is unsupported: supported scales " + "are Celsius, Kelvin, Fahrenheit, and " + "Rankine") # and from Kelvin to `new_scale`. if new_scale.lower() in ['celsius', 'c']: res = tempo - zero_Celsius @@ -292,9 +296,9 @@ def convert_temperature( elif new_scale.lower() in ['rankine', 'r']: res = tempo * 9 / 5 else: - raise NotImplementedError("'%s' scale is unsupported: supported " - "scales are 'Celsius', 'Kelvin', " - "'Fahrenheit', and 'Rankine'" % new_scale) + raise NotImplementedError(f"{new_scale=} is unsupported: supported " + "scales are 'Celsius', 'Kelvin', " + "'Fahrenheit', and 'Rankine'") return res @@ -329,7 +333,8 @@ def lambda2nu(lambda_: npt.ArrayLike) -> Any: array([ 2.99792458e+08, 1.00000000e+00]) """ - return c / np.asanyarray(lambda_) + xp = array_namespace(lambda_) + return c / _asarray(lambda_, xp=xp, subok=True) def nu2lambda(nu: npt.ArrayLike) -> Any: @@ -359,4 +364,5 @@ def nu2lambda(nu: npt.ArrayLike) -> Any: array([ 2.99792458e+08, 1.00000000e+00]) """ - return c / np.asanyarray(nu) + xp = array_namespace(nu) + return c / _asarray(nu, xp=xp, subok=True) diff --git a/scipy/constants/tests/test_constants.py b/scipy/constants/tests/test_constants.py index 8d7461d978fa..9f5c241b13f7 100644 --- a/scipy/constants/tests/test_constants.py +++ b/scipy/constants/tests/test_constants.py @@ -1,35 +1,89 @@ -from numpy.testing import assert_equal, assert_allclose +import pytest + import scipy.constants as sc +from scipy.conftest import array_api_compatible +from scipy._lib._array_api import xp_assert_equal, xp_assert_close + + +pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends")] +skip_xp_backends = pytest.mark.skip_xp_backends + + +class TestConvertTemperature: + def test_convert_temperature(self, xp): + xp_assert_equal(sc.convert_temperature(xp.asarray(32.), 'f', 'Celsius'), + xp.asarray(0.0)) + xp_assert_equal(sc.convert_temperature(xp.asarray([0., 0.]), + 'celsius', 'Kelvin'), + xp.asarray([273.15, 273.15])) + xp_assert_equal(sc.convert_temperature(xp.asarray([0., 0.]), 'kelvin', 'c'), + xp.asarray([-273.15, -273.15])) + xp_assert_equal(sc.convert_temperature(xp.asarray([32., 32.]), 'f', 'k'), + xp.asarray([273.15, 273.15])) + xp_assert_equal(sc.convert_temperature(xp.asarray([273.15, 273.15]), + 'kelvin', 'F'), + xp.asarray([32., 32.])) + xp_assert_equal(sc.convert_temperature(xp.asarray([0., 0.]), 'C', 'fahrenheit'), + xp.asarray([32., 32.])) + xp_assert_close(sc.convert_temperature(xp.asarray([0., 0.], dtype=xp.float64), + 'c', 'r'), + xp.asarray([491.67, 491.67], dtype=xp.float64), + rtol=0., atol=1e-13) + xp_assert_close(sc.convert_temperature(xp.asarray([491.67, 491.67], + dtype=xp.float64), + 'Rankine', 'C'), + xp.asarray([0., 0.], dtype=xp.float64), rtol=0., atol=1e-13) + xp_assert_close(sc.convert_temperature(xp.asarray([491.67, 491.67], + dtype=xp.float64), + 'r', 'F'), + xp.asarray([32., 32.], dtype=xp.float64), rtol=0., atol=1e-13) + xp_assert_close(sc.convert_temperature(xp.asarray([32., 32.], dtype=xp.float64), + 'fahrenheit', 'R'), + xp.asarray([491.67, 491.67], dtype=xp.float64), + rtol=0., atol=1e-13) + xp_assert_close(sc.convert_temperature(xp.asarray([273.15, 273.15], + dtype=xp.float64), + 'K', 'R'), + xp.asarray([491.67, 491.67], dtype=xp.float64), + rtol=0., atol=1e-13) + xp_assert_close(sc.convert_temperature(xp.asarray([491.67, 0.], + dtype=xp.float64), + 'rankine', 'kelvin'), + xp.asarray([273.15, 0.], dtype=xp.float64), rtol=0., atol=1e-13) + + @skip_xp_backends(np_only=True, reasons=['Python list input uses NumPy backend']) + def test_convert_temperature_array_like(self): + xp_assert_close(sc.convert_temperature([491.67, 0.], 'rankine', 'kelvin'), + [273.15, 0.], rtol=0., atol=1e-13) + + + @skip_xp_backends(np_only=True, reasons=['Python int input uses NumPy backend']) + def test_convert_temperature_errors(self, xp): + with pytest.raises(NotImplementedError, match="old_scale="): + sc.convert_temperature(1, old_scale="cheddar", new_scale="kelvin") + with pytest.raises(NotImplementedError, match="new_scale="): + sc.convert_temperature(1, old_scale="kelvin", new_scale="brie") + + +class TestLambdaToNu: + def test_lambda_to_nu(self, xp): + xp_assert_equal(sc.lambda2nu(xp.asarray([sc.speed_of_light, 1])), + xp.asarray([1, sc.speed_of_light])) + + + @skip_xp_backends(np_only=True, reasons=['Python list input uses NumPy backend']) + def test_lambda_to_nu_array_like(self, xp): + xp_assert_equal(sc.lambda2nu([sc.speed_of_light, 1]), + [1, sc.speed_of_light]) + +class TestNuToLambda: + def test_nu_to_lambda(self, xp): + xp_assert_equal(sc.nu2lambda(xp.asarray([sc.speed_of_light, 1])), + xp.asarray([1, sc.speed_of_light])) -def test_convert_temperature(): - assert_equal(sc.convert_temperature(32, 'f', 'Celsius'), 0) - assert_equal(sc.convert_temperature([0, 0], 'celsius', 'Kelvin'), - [273.15, 273.15]) - assert_equal(sc.convert_temperature([0, 0], 'kelvin', 'c'), - [-273.15, -273.15]) - assert_equal(sc.convert_temperature([32, 32], 'f', 'k'), [273.15, 273.15]) - assert_equal(sc.convert_temperature([273.15, 273.15], 'kelvin', 'F'), - [32, 32]) - assert_equal(sc.convert_temperature([0, 0], 'C', 'fahrenheit'), [32, 32]) - assert_allclose(sc.convert_temperature([0, 0], 'c', 'r'), [491.67, 491.67], - rtol=0., atol=1e-13) - assert_allclose(sc.convert_temperature([491.67, 491.67], 'Rankine', 'C'), - [0., 0.], rtol=0., atol=1e-13) - assert_allclose(sc.convert_temperature([491.67, 491.67], 'r', 'F'), - [32., 32.], rtol=0., atol=1e-13) - assert_allclose(sc.convert_temperature([32, 32], 'fahrenheit', 'R'), - [491.67, 491.67], rtol=0., atol=1e-13) - assert_allclose(sc.convert_temperature([273.15, 273.15], 'K', 'R'), - [491.67, 491.67], rtol=0., atol=1e-13) - assert_allclose(sc.convert_temperature([491.67, 0.], 'rankine', 'kelvin'), - [273.15, 0.], rtol=0., atol=1e-13) - - -def test_lambda_to_nu(): - assert_equal(sc.lambda2nu([sc.speed_of_light, 1]), [1, sc.speed_of_light]) - - -def test_nu_to_lambda(): - assert_equal(sc.nu2lambda([sc.speed_of_light, 1]), [1, sc.speed_of_light]) + @skip_xp_backends(np_only=True, reasons=['Python list input uses NumPy backend']) + def test_nu_to_lambda_array_like(self, xp): + xp_assert_equal(sc.nu2lambda([sc.speed_of_light, 1]), + [1, sc.speed_of_light]) From fa9852cf57667c4fe8f51c8861af1bd41a4489f5 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 29 Apr 2024 17:29:02 -0700 Subject: [PATCH 060/500] MAINT: optimize._chandrupatla: reduce xatol (#20501) * MAINT: optimize._chandrupatla: reduce default xatol * MAINT: optimize._chandrupatla: increase maximum number of iterations * MAINT: optimize._chandrupatla: scale maxiter with dtype --- scipy/optimize/_chandrupatla.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/scipy/optimize/_chandrupatla.py b/scipy/optimize/_chandrupatla.py index 26c65c7bcd87..6ce98230af68 100644 --- a/scipy/optimize/_chandrupatla.py +++ b/scipy/optimize/_chandrupatla.py @@ -1,10 +1,10 @@ import numpy as np -from ._zeros_py import _xtol, _rtol, _iter +from ._zeros_py import _rtol import scipy._lib._elementwise_iterative_method as eim from scipy._lib._util import _RichResult -def _chandrupatla(func, a, b, *, args=(), xatol=_xtol, xrtol=_rtol, - fatol=None, frtol=0, maxiter=_iter, callback=None): +def _chandrupatla(func, a, b, *, args=(), xatol=None, xrtol=_rtol, + fatol=None, frtol=0, maxiter=None, callback=None): """Find the root of an elementwise function using Chandrupatla's algorithm. For each element of the output of `func`, `chandrupatla` seeks the scalar @@ -33,6 +33,8 @@ def _chandrupatla(func, a, b, *, args=(), xatol=_xtol, xrtol=_rtol, See Notes for details. maxiter : int, optional The maximum number of iterations of the algorithm to perform. + The default is the maximum possible number of bisections within + the (normal) floating point numbers of the relevant dtype. callback : callable, optional An optional user-supplied function to be called before the first iteration and after each iteration. @@ -84,9 +86,9 @@ def _chandrupatla(func, a, b, *, args=(), xatol=_xtol, xrtol=_rtol, ``fun(xmin) <= fatol + abs(fmin0) * frtol``. This is equivalent to the termination condition described in [1]_ with ``xrtol = 4e-10``, ``xatol = 1e-5``, and ``fatol = frtol = 0``. The default values are - ``xatol = 2e-12``, ``xrtol = 4 * np.finfo(float).eps``, ``frtol = 0``, - and ``fatol`` is the smallest normal number of the ``dtype`` returned - by ``func``. + ``xatol = 4*tiny``, ``xrtol = 4*eps``, ``frtol = 0``, and ``fatol = tiny``, + where ``eps`` and ``tiny`` are the precision and smallest normal number + of the result ``dtype`` of function inputs and outputs. References ---------- @@ -128,10 +130,11 @@ def _chandrupatla(func, a, b, *, args=(), xatol=_xtol, xrtol=_rtol, f1, f2 = fs status = np.full_like(x1, eim._EINPROGRESS, dtype=int) # in progress nit, nfev = 0, 2 # two function evaluations performed above - xatol = _xtol if xatol is None else xatol + xatol = 4*np.finfo(dtype).tiny if xatol is None else xatol xrtol = _rtol if xrtol is None else xrtol fatol = np.finfo(dtype).tiny if fatol is None else fatol frtol = frtol * np.minimum(np.abs(f1), np.abs(f2)) + maxiter = 2**np.finfo(dtype).nexp if maxiter is None else maxiter work = _RichResult(x1=x1, f1=f1, x2=x2, f2=f2, x3=None, f3=None, t=0.5, xatol=xatol, xrtol=xrtol, fatol=fatol, frtol=frtol, nit=nit, nfev=nfev, status=status) @@ -245,9 +248,10 @@ def _chandrupatla_iv(func, args, xatol, xrtol, or np.any(np.isnan(tols)) or tols.shape != (4,)): raise ValueError('Tolerances must be non-negative scalars.') - maxiter_int = int(maxiter) - if maxiter != maxiter_int or maxiter < 0: - raise ValueError('`maxiter` must be a non-negative integer.') + if maxiter is not None: + maxiter_int = int(maxiter) + if maxiter != maxiter_int or maxiter < 0: + raise ValueError('`maxiter` must be a non-negative integer.') if callback is not None and not callable(callback): raise ValueError('`callback` must be callable.') @@ -344,7 +348,7 @@ def _chandrupatla_minimize(func, x1, x2, x3, *, args=(), xatol=None, or ``(f1 - 2*f2 + f3)/2 <= abs(f2)*frtol + fatol``. Note that first of these differs from the termination conditions described in [1]_. The default values of `xrtol` is the square root of the precision of the - appropriate dtype, and ``xatol=fatol = frtol`` is the smallest normal + appropriate dtype, and ``xatol = fatol = frtol`` is the smallest normal number of the appropriate dtype. References From 4222ece47be5b15654b6f9889947514f9723e198 Mon Sep 17 00:00:00 2001 From: Warren Weckesser Date: Tue, 30 Apr 2024 00:28:53 -0400 Subject: [PATCH 061/500] ENH: stats: Implement _isf for burr12 (#20615) * STY: Add a few missing blank lines. * ENH: stats: Implement _isf for burr12 to improve accuracy when p is small. --- scipy/stats/_continuous_distns.py | 3 +++ scipy/stats/tests/test_distributions.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py index d51602133b6d..5e6832de507d 100644 --- a/scipy/stats/_continuous_distns.py +++ b/scipy/stats/_continuous_distns.py @@ -1321,6 +1321,9 @@ def _ppf(self, q, c, d): # 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 diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py index e5a12be7a634..ad9fc1d37516 100644 --- a/scipy/stats/tests/test_distributions.py +++ b/scipy/stats/tests/test_distributions.py @@ -3158,6 +3158,7 @@ def test_fit_analytic_mle(self, c, loc, scale, fix_c, fix_scale): _assert_less_or_close_loglike(stats.loglaplace, data, **kwds) + class TestPowerlaw: # In the following data, `sf` was computed with mpmath. @@ -3810,6 +3811,7 @@ def test_compare_with_gamlss_r(self, gamlss_pdf_data, a, 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 @@ -4000,6 +4002,7 @@ def test_fit_gh19332(self): # 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, seed=rng) @@ -4793,6 +4796,7 @@ def test_fit_mm(self, a, loc, scale, fix_a, fix_loc, fix_scale): 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 @@ -7509,6 +7513,19 @@ def test_moments_edge(self): 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 From 6ae5a2e707fa702200a66da3c089f1533fd72180 Mon Sep 17 00:00:00 2001 From: h-vetinari Date: Tue, 30 Apr 2024 17:39:07 +1100 Subject: [PATCH 062/500] BUG: special: handle uint arrays in factorial{,2,k} (#20607) --- scipy/special/_basic.py | 3 ++- scipy/special/tests/test_basic.py | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/scipy/special/_basic.py b/scipy/special/_basic.py index 7b3fd8106c52..2ef20eb0c1d7 100644 --- a/scipy/special/_basic.py +++ b/scipy/special/_basic.py @@ -2943,7 +2943,8 @@ 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 - result[n_mod_k == r] *= corr(k, r) + # cast to int because uint types break on `-r` + result[n_mod_k == r] *= corr(k, int(r)) return result diff --git a/scipy/special/tests/test_basic.py b/scipy/special/tests/test_basic.py index 6d0be6be2743..9284110e937e 100644 --- a/scipy/special/tests/test_basic.py +++ b/scipy/special/tests/test_basic.py @@ -2130,9 +2130,13 @@ def _check(res, nucleus): _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, exact): - n = np.array(5, ndmin=dim) + 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)) From 34c8578484485559a3282f2bd53e546c047cf910 Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Tue, 30 Apr 2024 09:09:31 +0100 Subject: [PATCH 063/500] DOC: integrate: remove references to deprecated and legacy functions [docs only] --- doc/source/tutorial/integrate.rst | 25 ++++++------------------- scipy/integrate/_quadpack_py.py | 10 ---------- scipy/integrate/_quadrature.py | 17 ----------------- 3 files changed, 6 insertions(+), 46 deletions(-) diff --git a/doc/source/tutorial/integrate.rst b/doc/source/tutorial/integrate.rst index f6d5965a59e7..970b6353ddce 100644 --- a/doc/source/tutorial/integrate.rst +++ b/doc/source/tutorial/integrate.rst @@ -265,23 +265,12 @@ which is the same result as before. Gaussian quadrature ------------------- -A few functions are also provided in order to perform simple Gaussian -quadrature over a fixed interval. The first is :obj:`fixed_quad`, which -performs fixed-order Gaussian quadrature. The second function is -:obj:`quadrature`, which performs Gaussian quadrature of multiple -orders until the difference in the integral estimate is beneath some -tolerance supplied by the user. These functions both use the module -``scipy.special.orthogonal``, which can calculate the roots and quadrature -weights of a large variety of orthogonal polynomials (the polynomials -themselves are available as special functions returning instances of -the polynomial class --- e.g., :obj:`special.legendre `). - - -Romberg Integration -------------------- - -Romberg's method [WPR]_ is another method for numerically evaluating an -integral. See the help function for :func:`romberg` for further details. +:obj:`fixed_quad` performs fixed-order Gaussian quadrature over a fixed interval. This +function uses the collection of orthogonal polynomials provided by ``scipy.special``, +which can calculate the roots and quadrature weights of a large variety of orthogonal +polynomials (the polynomials themselves are available as special functions returning +instances of the polynomial class --- e.g., +:obj:`special.legendre `). Integrating using Samples @@ -787,6 +776,4 @@ Let's ensure that they have computed the same result:: References ~~~~~~~~~~ -.. [WPR] https://en.wikipedia.org/wiki/Romberg's_method - .. [MOL] https://en.wikipedia.org/wiki/Method_of_lines diff --git a/scipy/integrate/_quadpack_py.py b/scipy/integrate/_quadpack_py.py index a9d200e6ddca..599a30dffe5c 100644 --- a/scipy/integrate/_quadpack_py.py +++ b/scipy/integrate/_quadpack_py.py @@ -122,9 +122,6 @@ def quad(func, a, b, args=(), full_output=0, epsabs=1.49e-8, epsrel=1.49e-8, tplquad : triple integral nquad : n-dimensional integrals (uses `quad` recursively) fixed_quad : fixed-order Gaussian quadrature - quadrature : adaptive Gaussian quadrature - odeint : ODE integrator - ode : ODE integrator simpson : integrator for sampled data romb : integrator for sampled data scipy.special : for coefficients and roots of orthogonal polynomials @@ -727,9 +724,6 @@ def dblquad(func, a, b, gfun, hfun, args=(), epsabs=1.49e-8, epsrel=1.49e-8): tplquad : triple integral nquad : N-dimensional integrals fixed_quad : fixed-order Gaussian quadrature - quadrature : adaptive Gaussian quadrature - odeint : ODE integrator - ode : ODE integrator simpson : integrator for sampled data romb : integrator for sampled data scipy.special : for coefficients and roots of orthogonal polynomials @@ -860,14 +854,11 @@ def tplquad(func, a, b, gfun, hfun, qfun, rfun, args=(), epsabs=1.49e-8, See Also -------- quad : Adaptive quadrature using QUADPACK - quadrature : Adaptive Gaussian quadrature fixed_quad : Fixed-order Gaussian quadrature dblquad : Double integrals nquad : N-dimensional integrals romb : Integrators for sampled data simpson : Integrators for sampled data - ode : ODE integrators - odeint : ODE integrators scipy.special : For coefficients and roots of orthogonal polynomials Notes @@ -1045,7 +1036,6 @@ def nquad(func, ranges, args=None, opts=None, full_output=False): quad : 1-D numerical integration dblquad, tplquad : double and triple integrals fixed_quad : fixed-order Gaussian quadrature - quadrature : adaptive Gaussian quadrature Notes ----- diff --git a/scipy/integrate/_quadrature.py b/scipy/integrate/_quadrature.py index 045704204480..7fe4ef9424eb 100644 --- a/scipy/integrate/_quadrature.py +++ b/scipy/integrate/_quadrature.py @@ -217,13 +217,9 @@ def fixed_quad(func, a, b, args=(), n=5): quad : adaptive quadrature using QUADPACK dblquad : double integrals tplquad : triple integrals - romberg : adaptive Romberg quadrature - quadrature : adaptive Gaussian quadrature romb : integrators for sampled data simpson : integrators for sampled data cumulative_trapezoid : cumulative integration for sampled data - ode : ODE integrator - odeint : ODE integrator Examples -------- @@ -345,7 +341,6 @@ def quadrature(func, a, b, args=(), tol=1.49e-8, rtol=1.49e-8, maxiter=50, See Also -------- - romberg : adaptive Romberg quadrature fixed_quad : fixed-order Gaussian quadrature quad : adaptive quadrature using QUADPACK dblquad : double integrals @@ -353,8 +348,6 @@ def quadrature(func, a, b, args=(), tol=1.49e-8, rtol=1.49e-8, maxiter=50, romb : integrator for sampled data simpson : integrator for sampled data cumulative_trapezoid : cumulative integration for sampled data - ode : ODE integrator - odeint : ODE integrator Examples -------- @@ -437,14 +430,10 @@ def cumulative_trapezoid(y, x=None, dx=1.0, axis=-1, initial=None): numpy.cumsum, numpy.cumprod cumulative_simpson : cumulative integration using Simpson's 1/3 rule quad : adaptive quadrature using QUADPACK - romberg : adaptive Romberg quadrature - quadrature : adaptive Gaussian quadrature fixed_quad : fixed-order Gaussian quadrature dblquad : double integrals tplquad : triple integrals romb : integrators for sampled data - ode : ODE integrators - odeint : ODE integrators Examples -------- @@ -987,15 +976,11 @@ def romb(y, dx=1.0, axis=-1, show=False): See Also -------- quad : adaptive quadrature using QUADPACK - romberg : adaptive Romberg quadrature - quadrature : adaptive Gaussian quadrature fixed_quad : fixed-order Gaussian quadrature dblquad : double integrals tplquad : triple integrals simpson : integrators for sampled data cumulative_trapezoid : cumulative integration for sampled data - ode : ODE integrators - odeint : ODE integrators Examples -------- @@ -1202,8 +1187,6 @@ def romberg(function, a, b, args=(), tol=1.48e-8, rtol=1.48e-8, show=False, romb : Integrators for sampled data. simpson : Integrators for sampled data. cumulative_trapezoid : Cumulative integration for sampled data. - ode : ODE integrator. - odeint : ODE integrator. References ---------- From 713bce97dd8a6c755a9e8a59d36e6e92ea39a80e Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 30 Apr 2024 06:05:43 -0700 Subject: [PATCH 064/500] TST: adjust other very slow tests (#20487) --- scipy/_lib/tests/test_import_cycles.py | 2 + scipy/integrate/tests/test_quadpack.py | 1 + scipy/interpolate/tests/test_gil.py | 2 +- scipy/interpolate/tests/test_rgi.py | 2 +- .../tests/test__differential_evolution.py | 2 +- .../tests/test_minimize_constrained.py | 129 +++++++++--------- .../linalg/_eigen/lobpcg/tests/test_lobpcg.py | 16 +-- scipy/sparse/linalg/tests/test_propack.py | 1 + 8 files changed, 79 insertions(+), 76 deletions(-) diff --git a/scipy/_lib/tests/test_import_cycles.py b/scipy/_lib/tests/test_import_cycles.py index e61c57093f62..feaf2ff4cf64 100644 --- a/scipy/_lib/tests/test_import_cycles.py +++ b/scipy/_lib/tests/test_import_cycles.py @@ -1,3 +1,4 @@ +import pytest import sys import subprocess @@ -7,6 +8,7 @@ # Check that all modules are importable in a new Python process. # This is not necessarily true if there are import cycles present. +@pytest.mark.slow def test_public_modules_importable(): pids = [subprocess.Popen([sys.executable, '-c', f'import {module}']) for module in PUBLIC_MODULES] diff --git a/scipy/integrate/tests/test_quadpack.py b/scipy/integrate/tests/test_quadpack.py index 90bf6006cf1f..a7f6c7d195b0 100644 --- a/scipy/integrate/tests/test_quadpack.py +++ b/scipy/integrate/tests/test_quadpack.py @@ -342,6 +342,7 @@ def simpfunc(z, y, x, t): # Note order of arguments. (2.,)), 2*8/3.0 * (b**4.0 - a**4.0)) + @pytest.mark.xslow @pytest.mark.parametrize( "x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, expected", [ diff --git a/scipy/interpolate/tests/test_gil.py b/scipy/interpolate/tests/test_gil.py index 0902308fb6af..818cd7275bf3 100644 --- a/scipy/interpolate/tests/test_gil.py +++ b/scipy/interpolate/tests/test_gil.py @@ -28,7 +28,7 @@ def run(self): return WorkerThread() - @pytest.mark.slow + @pytest.mark.xslow @pytest.mark.xfail(reason='race conditions, may depend on system load') def test_rectbivariatespline(self): def generate_params(n_points): diff --git a/scipy/interpolate/tests/test_rgi.py b/scipy/interpolate/tests/test_rgi.py index 95b5e2253031..e4855d205293 100644 --- a/scipy/interpolate/tests/test_rgi.py +++ b/scipy/interpolate/tests/test_rgi.py @@ -476,7 +476,7 @@ def f(x, y): ]) def test_descending_points_nd(self, method, ndims, func): - if ndims == 5 and method in {"cubic", "quintic"}: + if ndims >= 4 and method in {"cubic", "quintic"}: pytest.skip("too slow; OOM (quintic); or nearly so (cubic)") rng = np.random.default_rng(42) diff --git a/scipy/optimize/tests/test__differential_evolution.py b/scipy/optimize/tests/test__differential_evolution.py index d5638a120aaa..8f83c1601648 100644 --- a/scipy/optimize/tests/test__differential_evolution.py +++ b/scipy/optimize/tests/test__differential_evolution.py @@ -1339,7 +1339,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) - @pytest.mark.slow + @pytest.mark.xslow @pytest.mark.xfail(platform.machine() == 'ppc64le', reason="fails on ppc64le") def test_L8(self): diff --git a/scipy/optimize/tests/test_minimize_constrained.py b/scipy/optimize/tests/test_minimize_constrained.py index 6dad4bad5a39..1f68bfafae66 100644 --- a/scipy/optimize/tests/test_minimize_constrained.py +++ b/scipy/optimize/tests/test_minimize_constrained.py @@ -2,7 +2,7 @@ import pytest from scipy.linalg import block_diag from scipy.sparse import csc_matrix -from numpy.testing import (TestCase, assert_array_almost_equal, +from numpy.testing import (assert_array_almost_equal, assert_array_less, assert_, assert_allclose, suppress_warnings) from scipy.optimize import (NonlinearConstraint, @@ -443,67 +443,70 @@ def hess(x, v): return NonlinearConstraint(fun, -np.inf, 0, jac, hess) -class TestTrustRegionConstr(TestCase): - - @pytest.mark.slow - def test_list_of_problems(self): - list_of_problems = [Maratos(), - Maratos(constr_hess='2-point'), - Maratos(constr_hess=SR1()), - Maratos(constr_jac='2-point', constr_hess=SR1()), - MaratosGradInFunc(), - HyperbolicIneq(), - HyperbolicIneq(constr_hess='3-point'), - HyperbolicIneq(constr_hess=BFGS()), - HyperbolicIneq(constr_jac='3-point', - constr_hess=BFGS()), - Rosenbrock(), - IneqRosenbrock(), - EqIneqRosenbrock(), - BoundedRosenbrock(), - Elec(n_electrons=2), - Elec(n_electrons=2, constr_hess='2-point'), - Elec(n_electrons=2, constr_hess=SR1()), - Elec(n_electrons=2, constr_jac='3-point', - constr_hess=SR1())] - - for prob in list_of_problems: - for grad in (prob.grad, '3-point', False): - for hess in (prob.hess, - '3-point', - SR1(), - BFGS(exception_strategy='damp_update'), - BFGS(exception_strategy='skip_update')): - - # Remove exceptions - if grad in ('2-point', '3-point', 'cs', False) and \ - hess in ('2-point', '3-point', 'cs'): - continue - if prob.grad is True and grad in ('3-point', False): - continue - with suppress_warnings() as sup: - sup.filter(UserWarning, "delta_grad == 0.0") - result = minimize(prob.fun, prob.x0, - method='trust-constr', - jac=grad, hess=hess, - bounds=prob.bounds, - constraints=prob.constr) - - if prob.x_opt is not None: - assert_array_almost_equal(result.x, prob.x_opt, - decimal=5) - # gtol - if result.status == 1: - assert_array_less(result.optimality, 1e-8) - # xtol - if result.status == 2: - assert_array_less(result.tr_radius, 1e-8) - - if result.method == "tr_interior_point": - assert_array_less(result.barrier_parameter, 1e-8) - # max iter - if result.status in (0, 3): - raise RuntimeError("Invalid termination condition.") +class TestTrustRegionConstr: + list_of_problems = [Maratos(), + Maratos(constr_hess='2-point'), + Maratos(constr_hess=SR1()), + Maratos(constr_jac='2-point', constr_hess=SR1()), + MaratosGradInFunc(), + HyperbolicIneq(), + HyperbolicIneq(constr_hess='3-point'), + HyperbolicIneq(constr_hess=BFGS()), + HyperbolicIneq(constr_jac='3-point', + constr_hess=BFGS()), + Rosenbrock(), + IneqRosenbrock(), + EqIneqRosenbrock(), + BoundedRosenbrock(), + Elec(n_electrons=2), + Elec(n_electrons=2, constr_hess='2-point'), + Elec(n_electrons=2, constr_hess=SR1()), + Elec(n_electrons=2, constr_jac='3-point', + constr_hess=SR1())] + + @pytest.mark.parametrize('prob', list_of_problems) + @pytest.mark.parametrize('grad', ('prob.grad', '3-point', False)) + @pytest.mark.parametrize('hess', ("prob.hess", '3-point', SR1(), + BFGS(exception_strategy='damp_update'), + BFGS(exception_strategy='skip_update'))) + def test_list_of_problems(self, prob, grad, hess): + grad = prob.grad if grad == "prob.grad" else grad + hess = prob.hess if hess == "prob.hess" else hess + # Remove exceptions + if (grad in {'2-point', '3-point', 'cs', False} and + hess in {'2-point', '3-point', 'cs'}): + pytest.skip("Numerical Hessian needs analytical gradient") + if prob.grad is True and grad in {'3-point', False}: + pytest.skip("prob.grad incompatible with grad in {'3-point', False}") + sensitive = (isinstance(prob, BoundedRosenbrock) and grad == '3-point' + and isinstance(hess, BFGS)) + if sensitive: + pytest.xfail("Seems sensitive to initial conditions w/ Accelerate") + with suppress_warnings() as sup: + sup.filter(UserWarning, "delta_grad == 0.0") + result = minimize(prob.fun, prob.x0, + method='trust-constr', + jac=grad, hess=hess, + bounds=prob.bounds, + constraints=prob.constr) + + if prob.x_opt is not None: + assert_array_almost_equal(result.x, prob.x_opt, + decimal=5) + # gtol + if result.status == 1: + assert_array_less(result.optimality, 1e-8) + # xtol + if result.status == 2: + assert_array_less(result.tr_radius, 1e-8) + + if result.method == "tr_interior_point": + assert_array_less(result.barrier_parameter, 1e-8) + + # check for max iter + message = f"Invalid termination condition: {result.status}." + assert result.status not in {0, 3}, message + def test_default_jac_and_hess(self): def fun(x): @@ -641,7 +644,7 @@ def obj(x): assert result['success'] -class TestEmptyConstraint(TestCase): +class TestEmptyConstraint: """ Here we minimize x^2+y^2 subject to x^2-y^2>1. The actual minimum is at (0, 0) which fails the constraint. diff --git a/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py b/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py index 4b060d77d1f7..3fe07ba31296 100644 --- a/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py +++ b/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py @@ -548,9 +548,7 @@ def test_diagonal_data_types(n, m): # 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'] - list_sparse_format = ['coo'] - sparse_formats = len(list_sparse_format) + list_sparse_format = ['bsr', 'coo', 'csc', 'csr', 'dia', 'dok', 'lil'] for s_f_i, s_f in enumerate(list_sparse_format): As64 = diags([vals * vals], [0], (n, n), format=s_f) @@ -629,15 +627,13 @@ def Mf32precond(x): listY = [Yf64, Yf32] tests = list(itertools.product(listA, listB, listM, listX, listY)) - # This is one of the slower tests because there are >1,000 configs - # to test here, instead of checking product of all input, output types - # test each configuration for the first sparse format, and then - # for one additional sparse format. this takes 2/7=30% as long as - # testing all configurations for all sparse formats. - if s_f_i > 0: - tests = tests[s_f_i - 1::sparse_formats-1] 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.95: + continue # too many tests eigvals, _ = lobpcg(A, X, B=B, M=M, Y=Y, tol=1e-4, maxiter=100, largest=False) assert_allclose(eigvals, diff --git a/scipy/sparse/linalg/tests/test_propack.py b/scipy/sparse/linalg/tests/test_propack.py index 64eb888fd994..2dac7133997a 100644 --- a/scipy/sparse/linalg/tests/test_propack.py +++ b/scipy/sparse/linalg/tests/test_propack.py @@ -89,6 +89,7 @@ def test_svdp(ctor, dtype, irl, which): check_svdp(n, m, ctor, dtype, k, irl, which) +@pytest.mark.xslow @pytest.mark.parametrize('dtype', _dtypes) @pytest.mark.parametrize('irl', (False, True)) @pytest.mark.timeout(120) # True, complex64 > 60 s: prerel deps cov 64bit blas From a01535d2b7d655f35542d769a9469e52be4c8125 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 30 Apr 2024 10:10:54 -0700 Subject: [PATCH 065/500] TST: add xslow or fail_slow marks as appropriate [skip cirrus] [skip circle] --- scipy/conftest.py | 2 +- scipy/linalg/tests/test_extending.py | 2 ++ .../_trustregion_constr/tests/test_report.py | 3 ++ scipy/optimize/tests/test_extending.py | 2 ++ scipy/spatial/tests/test_qhull.py | 2 ++ scipy/special/tests/test_extending.py | 2 ++ scipy/stats/tests/test_continuous_basic.py | 2 +- scipy/stats/tests/test_fit.py | 30 +++++++++++++++---- scipy/stats/tests/test_stats.py | 1 + 9 files changed, 38 insertions(+), 8 deletions(-) diff --git a/scipy/conftest.py b/scipy/conftest.py index ee171aba0922..3d44eb24db73 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -30,7 +30,7 @@ def pytest_configure(config): try: # This is a more reliable test of whether pytest_fail_slow is installed # When I uninstalled it, `import pytest_fail_slow` didn't fail! - from pytest_fail_slow import parse_duration # noqa:F401 + from pytest_fail_slow import parse_duration # type: ignore[import-not-found] # noqa:F401,E501 except Exception: config.addinivalue_line( "markers", 'fail_slow: mark a test for a non-default timeout failure') diff --git a/scipy/linalg/tests/test_extending.py b/scipy/linalg/tests/test_extending.py index b6f7b92d2a32..56b22b2a1744 100644 --- a/scipy/linalg/tests/test_extending.py +++ b/scipy/linalg/tests/test_extending.py @@ -9,6 +9,8 @@ from scipy.linalg.lapack import dgtsv # type: ignore[attr-defined] +@pytest.mark.fail_slow(30) +# 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.machine() in ["wasm32", "wasm64"], diff --git a/scipy/optimize/_trustregion_constr/tests/test_report.py b/scipy/optimize/_trustregion_constr/tests/test_report.py index e08d3e849004..95924bc0a280 100644 --- a/scipy/optimize/_trustregion_constr/tests/test_report.py +++ b/scipy/optimize/_trustregion_constr/tests/test_report.py @@ -1,3 +1,4 @@ +import pytest import numpy as np from scipy.optimize import minimize, Bounds @@ -13,6 +14,8 @@ def test_gh10880(): minimize(lambda x: x**2, x0=2., method='trust-constr', bounds=bnds, options=opts) +@pytest.mark.slow +@pytest.mark.fail_slow(10) def test_gh12922(): # checks that verbose reporting works with trust-constr for # general constraints diff --git a/scipy/optimize/tests/test_extending.py b/scipy/optimize/tests/test_extending.py index eacb3f9dccf5..80e25f28891c 100644 --- a/scipy/optimize/tests/test_extending.py +++ b/scipy/optimize/tests/test_extending.py @@ -6,6 +6,8 @@ from scipy._lib._testutils import IS_EDITABLE, _test_cython_extension, cython +@pytest.mark.fail_slow(20) +# 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.machine() in ["wasm32", "wasm64"], diff --git a/scipy/spatial/tests/test_qhull.py b/scipy/spatial/tests/test_qhull.py index dcf55e8f05b5..8a94e380482c 100644 --- a/scipy/spatial/tests/test_qhull.py +++ b/scipy/spatial/tests/test_qhull.py @@ -345,6 +345,8 @@ def test_degenerate_barycentric_transforms(self): self._check_barycentric_transforms(tri) @pytest.mark.slow + @pytest.mark.fail_slow(10) + # OK per https://github.com/scipy/scipy/pull/20487#discussion_r1572684869 def test_more_barycentric_transforms(self): # Triangulate some "nasty" grids diff --git a/scipy/special/tests/test_extending.py b/scipy/special/tests/test_extending.py index 0096b9282434..57ab39a9d489 100644 --- a/scipy/special/tests/test_extending.py +++ b/scipy/special/tests/test_extending.py @@ -7,6 +7,8 @@ from scipy.special import beta, gamma +@pytest.mark.fail_slow(20) +# 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.machine() in ["wasm32", "wasm64"], diff --git a/scipy/stats/tests/test_continuous_basic.py b/scipy/stats/tests/test_continuous_basic.py index cd114facbb51..b2be9d590d3e 100644 --- a/scipy/stats/tests/test_continuous_basic.py +++ b/scipy/stats/tests/test_continuous_basic.py @@ -51,7 +51,7 @@ xslow_test_moments = {'studentized_range', 'ksone', 'vonmises', 'vonmises_line', 'recipinvgauss', 'kstwo', 'kappa4'} -xslow_fit_mle = {'gausshyper', 'ncf', 'ncx2', 'vonmises_line'} +xslow_fit_mle = {'gausshyper', 'ncf', 'ncx2', 'recipinvgauss', 'vonmises_line'} xfail_fit_mle = {'ksone', 'kstwo', 'trapezoid', 'truncpareto'} skip_fit_mle = {'levy_stable', 'studentized_range'} # far too slow (>10min) xslow_fit_mm = {'argus', 'beta', 'exponpow', 'gausshyper', 'gengamma', diff --git a/scipy/stats/tests/test_fit.py b/scipy/stats/tests/test_fit.py index 047ed2617c31..63732dfe0fa1 100644 --- a/scipy/stats/tests/test_fit.py +++ b/scipy/stats/tests/test_fit.py @@ -38,6 +38,10 @@ 'studentized_range', ] +# these pass but are XSLOW (>1s) +mle_Xslow_fits = ['recipinvgauss', 'geninvgauss', 'vonmises_line', 'exponweib', + 'rel_breitwigner', 'betaprime', 'f', 'jf_skew_t', 'crystallball'] + # 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 = [ @@ -68,12 +72,16 @@ 'studentized_range'] # not sure if these fail, but they caused my patience to fail -mm_slow_fits = ['argus', 'exponpow', 'exponweib', 'gausshyper', 'genexpon', - 'genhalflogistic', 'halfgennorm', 'gompertz', 'johnsonsb', - 'kappa4', 'kstwobign', 'recipinvgauss', - 'truncexpon', 'vonmises', 'vonmises_line'] +mm_XXslow_fits = ['argus', 'exponpow', 'exponweib', 'gausshyper', 'genexpon', + 'genhalflogistic', 'halfgennorm', '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_slow_fits, "MLE": mle_failing_fits} +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: @@ -109,6 +117,16 @@ def test_cont_fit(distname, arg, method): " test nevertheless.]") pytest.xfail(msg) + if distname in xslow_fits[method]: + # Skip xslow fits unless SCIPY_XLSOW=1 + try: + xslow = not int(os.environ['SCIPY_XSLOW']) + except Exception: + xslow = True + if xslow: + msg = "very slow test; set environment variable SCIPY_XSLOW=1 to run." + pytest.skip(msg) + distfn = getattr(stats, distname) truearg = np.hstack([arg, [0.0, 1.0]]) @@ -292,7 +310,7 @@ def cases_test_fit_mse(): # Please keep this list in alphabetical order... xslow_basic_fit = {'argus', 'beta', 'betaprime', 'burr', 'burr12', 'dgamma', 'f', 'gengamma', 'gennorm', - 'halfgennorm', 'invgamma', 'invgauss', 'jf_skew_t' + 'halfgennorm', 'invgamma', 'invgauss', 'jf_skew_t', 'johnsonsb', 'kappa4', 'loguniform', 'mielke', 'nakagami', 'ncf', 'nchypergeom_fisher', 'nchypergeom_wallenius', 'nct', 'ncx2', diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index ce37e481e318..300e1eb61f25 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -8429,6 +8429,7 @@ def test_dist_perm(self): assert_approx_equal(stat_dist, 0.163, significant=1) assert_approx_equal(pvalue_dist, 0.001, significant=1) + @pytest.mark.fail_slow(10) # all other tests are XSLOW; we need at least one to run @pytest.mark.slow def test_pvalue_literature(self): np.random.seed(12345678) From 527ca0abff1b63c0d10b2a8046597988a04f1e12 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 30 Apr 2024 15:53:47 -0700 Subject: [PATCH 066/500] TST: refactor test_cont_fit; adjust fail-slow marks --- .github/workflows/windows.yml | 2 +- scipy/_lib/tests/test_import_cycles.py | 1 + scipy/_lib/tests/test_warnings.py | 1 + scipy/stats/tests/test_fit.py | 37 ++++++++++---------------- 4 files changed, 17 insertions(+), 24 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 4e7b2b64069a..ea6147acc6dc 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -97,7 +97,7 @@ jobs: - name: Test run: | - python dev.py test -j2 --mode full -- --durations=10 --timeout=60 --fail-slow=5 + python dev.py test -j2 --mode full -- --durations=0 --durations-min=1.0 --timeout=60 --fail-slow=5.0 ############################################################################# diff --git a/scipy/_lib/tests/test_import_cycles.py b/scipy/_lib/tests/test_import_cycles.py index feaf2ff4cf64..02177fec255e 100644 --- a/scipy/_lib/tests/test_import_cycles.py +++ b/scipy/_lib/tests/test_import_cycles.py @@ -8,6 +8,7 @@ # Check that all modules are importable in a new Python process. # This is not necessarily true if there are import cycles present. +@pytest.mark.fail_slow(20) @pytest.mark.slow def test_public_modules_importable(): pids = [subprocess.Popen([sys.executable, '-c', f'import {module}']) diff --git a/scipy/_lib/tests/test_warnings.py b/scipy/_lib/tests/test_warnings.py index bf2e8daad4df..8730d16e1ac0 100644 --- a/scipy/_lib/tests/test_warnings.py +++ b/scipy/_lib/tests/test_warnings.py @@ -95,6 +95,7 @@ def warning_calls(): return bad_filters, bad_stacklevels +@pytest.mark.fail_slow(20) @pytest.mark.slow def test_warning_calls_filters(warning_calls): bad_filters, bad_stacklevels = warning_calls diff --git a/scipy/stats/tests/test_fit.py b/scipy/stats/tests/test_fit.py index 63732dfe0fa1..b9cf08a61308 100644 --- a/scipy/stats/tests/test_fit.py +++ b/scipy/stats/tests/test_fit.py @@ -39,8 +39,8 @@ ] # these pass but are XSLOW (>1s) -mle_Xslow_fits = ['recipinvgauss', 'geninvgauss', 'vonmises_line', 'exponweib', - 'rel_breitwigner', 'betaprime', 'f', 'jf_skew_t', 'crystallball'] +mle_Xslow_fits = ['betaprime', 'crystalball', 'exponweib', 'f', 'geninvgauss', + 'jf_skew_t', '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. @@ -105,27 +105,18 @@ def cases_test_cont_fit(): @pytest.mark.parametrize('distname,arg', cases_test_cont_fit()) @pytest.mark.parametrize('method', ["MLE", "MM"]) def test_cont_fit(distname, arg, method): - if distname in failing_fits[method]: - # Skip failing fits unless overridden - try: - xfail = not int(os.environ['SCIPY_XFAIL']) - except Exception: - xfail = True - if xfail: - msg = "Fitting %s doesn't work reliably yet" % distname - msg += (" [Set environment variable SCIPY_XFAIL=1 to run this" - " test nevertheless.]") - pytest.xfail(msg) - - if distname in xslow_fits[method]: - # Skip xslow fits unless SCIPY_XLSOW=1 - try: - xslow = not int(os.environ['SCIPY_XSLOW']) - except Exception: - xslow = True - if xslow: - msg = "very slow test; set environment variable SCIPY_XSLOW=1 to run." - pytest.skip(msg) + 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) From 0c8ab873de403ed8283d1364f6db5f6e24630199 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sun, 28 Apr 2024 21:03:03 +0200 Subject: [PATCH 067/500] TYP: add type annotations to `scipy/_lib/_array_api.py` This covers everything aside from the testing functions in this file. --- scipy/_lib/_array_api.py | 49 ++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 98d7f50bb6ee..aff04f4da98d 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -11,7 +11,11 @@ import os import warnings +from types import ModuleType +from typing import Any, Literal, TYPE_CHECKING + import numpy as np +import numpy.typing as npt from scipy._lib import array_api_compat from scipy._lib.array_api_compat import ( @@ -34,7 +38,12 @@ } -def compliance_scipy(arrays): +if TYPE_CHECKING: + Array = Any # To be changed to a Protocol later (see array-api#589) + ArrayLike = Array | npt.ArrayLike + + +def compliance_scipy(arrays: list[ArrayLike]) -> list[Array]: """Raise exceptions on known-bad subclasses. The following subclasses are not supported and raise and error: @@ -72,7 +81,7 @@ def compliance_scipy(arrays): return arrays -def _check_finite(array, xp): +def _check_finite(array: Array, xp: ModuleType) -> None: """Check for NaNs or Infs.""" msg = "array must not contain infs or NaNs" try: @@ -82,7 +91,7 @@ def _check_finite(array, xp): raise ValueError(msg) -def array_namespace(*arrays): +def array_namespace(*arrays: Array) -> ModuleType: """Get the array API compatible namespace for the arrays xs. Parameters @@ -112,17 +121,23 @@ def array_namespace(*arrays): # here we could wrap the namespace if needed return np_compat - arrays = [array for array in arrays if array is not None] + _arrays = [array for array in arrays if array is not None] - arrays = compliance_scipy(arrays) + _arrays = compliance_scipy(_arrays) - return array_api_compat.array_namespace(*arrays) + return array_api_compat.array_namespace(*_arrays) def _asarray( - array, dtype=None, order=None, copy=None, *, xp=None, check_finite=False, - subok=False -): + array: ArrayLike, + dtype: Any = None, + order: Literal['K', 'A', 'C', 'F'] | None = None, + copy: bool | None = None, + *, + xp: ModuleType | None = None, + check_finite: bool = False, + subok: bool = False, + ) -> Array: """SciPy-specific replacement for `np.asarray` with `order`, `check_finite`, and `subok`. @@ -164,7 +179,7 @@ def _asarray( return array -def atleast_nd(x, *, ndim, xp=None): +def atleast_nd(x: Array, *, ndim: int, xp: ModuleType | None = None) -> Array: """Recursively expand the dimension to have at least `ndim`.""" if xp is None: xp = array_namespace(x) @@ -175,7 +190,7 @@ def atleast_nd(x, *, ndim, xp=None): return x -def copy(x, *, xp=None): +def copy(x: Array, *, xp: ModuleType | None = None) -> Array: """ Copies an array. @@ -202,15 +217,15 @@ def copy(x, *, xp=None): return _asarray(x, copy=True, xp=xp) -def is_numpy(xp): +def is_numpy(xp: ModuleType) -> bool: return xp.__name__ in ('numpy', 'scipy._lib.array_api_compat.numpy') -def is_cupy(xp): +def is_cupy(xp: ModuleType) -> bool: return xp.__name__ in ('cupy', 'scipy._lib.array_api_compat.cupy') -def is_torch(xp): +def is_torch(xp: ModuleType) -> bool: return xp.__name__ in ('torch', 'scipy._lib.array_api_compat.torch') @@ -327,7 +342,7 @@ def xp_assert_less(actual, desired, check_namespace=True, check_dtype=True, err_msg=err_msg, verbose=verbose) -def cov(x, *, xp=None): +def cov(x: Array, *, xp: ModuleType | None = None) -> Array: if xp is None: xp = array_namespace(x) @@ -355,9 +370,9 @@ def cov(x, *, xp=None): return xp.squeeze(c, axis=axes) -def xp_unsupported_param_msg(param): +def xp_unsupported_param_msg(param: Any) -> str: return f'Providing {param!r} is only supported for numpy arrays.' -def is_complex(x, xp): +def is_complex(x: Array, xp: ModuleType) -> bool: return xp.isdtype(x.dtype, 'complex floating') From 690d7cdcf787732f9f9cf5f6f773ea3d6f26f80b Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Wed, 1 May 2024 09:24:25 +0200 Subject: [PATCH 068/500] TYP: add type-ignores to `_RichResult` internals in `_lib` Mypy 1.10.0 is complaining about this locally (not in CI): ``` scipy/_lib/_util.py:874: error: Incompatible types in assignment (expression has type "Callable[[dict[_KT, _VT], _KT, _VT], None]", base class "object" defined the type as "Callable[[object, str, Any], None]") [assignment] scipy/_lib/_util.py:875: error: Incompatible types in assignment (expression has type "Callable[[dict[_KT, _VT], _KT], None]", base class "object" defined the type as "Callable[[object, str], None]") [assignment] ``` The complaint seems valid but the code works as intended, so let's explicitly ignore this. [skip cirrus] [skip circle] --- scipy/_lib/_util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py index ebb3d1b940ad..5a03de16824e 100644 --- a/scipy/_lib/_util.py +++ b/scipy/_lib/_util.py @@ -871,8 +871,8 @@ def __getattr__(self, name): except KeyError as e: raise AttributeError(name) from e - __setattr__ = dict.__setitem__ - __delattr__ = dict.__delitem__ + __setattr__ = dict.__setitem__ # type: ignore[assignment] + __delattr__ = dict.__delitem__ # type: ignore[assignment] def __repr__(self): order_keys = ['message', 'success', 'status', 'fun', 'funl', 'x', 'xl', From 9b02876abc4dd3f92b5b346704ca99cfe94745ef Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Wed, 1 May 2024 10:16:23 +0200 Subject: [PATCH 069/500] Update scipy/optimize/_trustregion_constr/tests/test_report.py [skip ci] Co-authored-by: Matt Haberland --- scipy/optimize/_trustregion_constr/tests/test_report.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scipy/optimize/_trustregion_constr/tests/test_report.py b/scipy/optimize/_trustregion_constr/tests/test_report.py index 95924bc0a280..c82796fea723 100644 --- a/scipy/optimize/_trustregion_constr/tests/test_report.py +++ b/scipy/optimize/_trustregion_constr/tests/test_report.py @@ -14,8 +14,7 @@ def test_gh10880(): minimize(lambda x: x**2, x0=2., method='trust-constr', bounds=bnds, options=opts) -@pytest.mark.slow -@pytest.mark.fail_slow(10) +@pytest.mark.xslow def test_gh12922(): # checks that verbose reporting works with trust-constr for # general constraints From 14a91b4497417768b4bfdd075cc66576d1bf052b Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Wed, 1 May 2024 10:22:55 +0200 Subject: [PATCH 070/500] TYP: change Mypy settings to not error on unused type-ignore's This setting is super fragile. It makes it impossible to use a different version than is pinned in CI. And even for the same version, it happens that Mypy complains locally but not in CI (this may depend on optional dependencies being installed, or other reasons). [skip cirrus] [skip circle] --- mypy.ini | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mypy.ini b/mypy.ini index 11fe54c67475..8aa1f57c8af3 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,6 +1,9 @@ [mypy] warn_redundant_casts = True -warn_unused_ignores = True +# This is too fragile - can be tested locally with `True` once in a while, +# and especially when upgrading to a new Mypy version. However, Mypy is +# not consistent enough for this to be a reasonable default. +warn_unused_ignores = False show_error_codes = True plugins = numpy.typing.mypy_plugin From 8f65d152509940228091834d6b2042e422237913 Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Thu, 2 May 2024 10:01:55 +0100 Subject: [PATCH 071/500] ENH: add dtype dependent default rtol to xp_assert_close Co-authored-by: Matt Haberland --- scipy/_lib/_array_api.py | 12 +++++++++++- scipy/cluster/tests/test_vq.py | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index aff04f4da98d..13d8cec74dee 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -305,13 +305,23 @@ def xp_assert_equal(actual, desired, check_namespace=True, check_dtype=True, return np.testing.assert_array_equal(actual, desired, err_msg=err_msg) -def xp_assert_close(actual, desired, rtol=1e-07, atol=0, check_namespace=True, +def xp_assert_close(actual, desired, rtol=None, atol=0, check_namespace=True, check_dtype=True, check_shape=True, err_msg='', xp=None): __tracebackhide__ = True # Hide traceback for py.test if xp is None: xp = array_namespace(actual) desired = _strict_check(actual, desired, xp, check_namespace=check_namespace, check_dtype=check_dtype, check_shape=check_shape) + + floating = xp.isdtype(actual.dtype, ('real floating', 'complex floating')) + if rtol is None and floating: + # multiplier of 4 is used as for `np.float64` this puts the default `rtol` + # roughly half way between sqrt(eps) and the default for + # `numpy.testing.assert_allclose`, 1e-7 + rtol = xp.finfo(actual.dtype).eps**0.5 * 4 + elif rtol is None: + rtol = 1e-7 + if is_cupy(xp): return xp.testing.assert_allclose(actual, desired, rtol=rtol, atol=atol, err_msg=err_msg) diff --git a/scipy/cluster/tests/test_vq.py b/scipy/cluster/tests/test_vq.py index fffe32012002..8d32933e67a3 100644 --- a/scipy/cluster/tests/test_vq.py +++ b/scipy/cluster/tests/test_vq.py @@ -413,9 +413,9 @@ def test_kmeans_and_kmeans2_random_seed(self, xp): # test for kmeans res1, _ = kmeans(data, 2, seed=seed1) res2, _ = kmeans(data, 2, seed=seed2) - xp_assert_close(res1, res2, xp=xp) # should be same results + xp_assert_close(res1, res2) # should be same results # test for kmeans2 for minit in ["random", "points", "++"]: res1, _ = kmeans2(data, 2, minit=minit, seed=seed1) res2, _ = kmeans2(data, 2, minit=minit, seed=seed2) - xp_assert_close(res1, res2, xp=xp) # should be same results + xp_assert_close(res1, res2) # should be same results From b6f1a648d757bdc4a61d6d4349ef3a1ad7182166 Mon Sep 17 00:00:00 2001 From: Julien Schueller Date: Thu, 2 May 2024 15:39:25 +0200 Subject: [PATCH 072/500] MAINT: Drop unused function_calls variable --- scipy/special/special/cephes/kolmogorov.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/scipy/special/special/cephes/kolmogorov.h b/scipy/special/special/cephes/kolmogorov.h index 8af6c88b3dbc..30dac53538ba 100644 --- a/scipy/special/special/cephes/kolmogorov.h +++ b/scipy/special/special/cephes/kolmogorov.h @@ -818,7 +818,6 @@ namespace cephes { */ double x, logpcdf; int iterations = 0; - int function_calls = 0; double a = 0, b = 1; double maxlogpcdf, psfrootn; double dx, dxold; @@ -923,7 +922,6 @@ namespace cephes { SPECFUN_ASSERT(x > 0); { ThreeProbs probs = _smirnov(n, x0); - ++function_calls; df = ((pcdf < 0.5) ? (pcdf - probs.cdf) : (probs.sf - psf)); dfdx = -probs.pdf; } From 1f17de53c0a3d9b2cf25647e5c385270a504f74a Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Thu, 2 May 2024 18:50:39 +0200 Subject: [PATCH 073/500] DEV: lint: disable UP031 Showed up as a linting error in an unrelated PR for me: ``` scipy/interpolate/_interpolate.py:918:30: UP032 [*] Use f-string instead of `format` call scipy/interpolate/_interpolate.py:1972:30: UP032 [*] Use f-string instead of `format` call ``` This should not happen; the old code is fine, so this check needs to be silenced or fixed separately. Similar to gh-20601. [skip ci] --- tools/lint.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lint.toml b/tools/lint.toml index 97aabfe66f9f..bcc9f31b9135 100644 --- a/tools/lint.toml +++ b/tools/lint.toml @@ -18,7 +18,7 @@ target-version = "py39" # `ICN001` added in gh-20382 to enforce common conventions for imports select = ["E", "F", "PGH004", "UP", "B028", "ICN001"] # UP031 should be enabled once someone fixes the errors. -ignore = ["E741", "UP031"] +ignore = ["E741", "UP031", "UP032"] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" From 80b4bed07a3264f2b582196957cbebb12495288b Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 2 May 2024 10:01:40 -0700 Subject: [PATCH 074/500] TST: stats: remove some unnecessary specification of dtype (#3) --- scipy/stats/_stats_py.py | 2 +- scipy/stats/tests/test_stats.py | 43 ++++++++++++++++----------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index cc504a75fb1a..d5202e75c80c 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -4830,7 +4830,7 @@ def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0): dtype = xp.result_type(x.dtype, y.dtype) if xp.isdtype(dtype, "integral"): - dtype = xp.float64 + dtype = xp.asarray(1.).dtype if xp.isdtype(dtype, "complex floating"): raise ValueError('This function does not support complex data') diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 300e1eb61f25..411a7f51a233 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -382,8 +382,8 @@ def test_basic(self, xp): a = xp.asarray([-1, 0, 1]) b = xp.asarray([0, 0, 3]) r, prob = stats.pearsonr(a, b) - xp_assert_close(r, xp.asarray(np.sqrt(3)/2)) - xp_assert_close(prob, xp.asarray(1/3, dtype=xp.float64)) + 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 @@ -512,13 +512,13 @@ def test_negative_correlation_pvalue_gh17795(self, xp): xp_assert_close(test_less.pvalue, xp.asarray(0.), atol=1e-20) def test_length3_r_exactly_negative_one(self, xp): - x = xp.asarray([1, 2, 3]) - y = xp.asarray([5, -4, -13]) + 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, dtype=xp.float64) + one = xp.asarray(1.0) xp_assert_close(r, -one) xp_assert_close(p, 0*one, atol=1e-7) low, high = res.confidence_interval() @@ -641,8 +641,8 @@ def test_nd_input_validation(self, xp): def test_nd_special_cases(self, xp): rng = np.random.default_rng(34989235492245) - x0 = xp.asarray(rng.random((3, 5)), dtype=xp.float64) - y0 = xp.asarray(rng.random((3, 5)), dtype=xp.float64) + x0 = xp.asarray(rng.random((3, 5))) + y0 = xp.asarray(rng.random((3, 5))) message = 'An input array is constant' with pytest.warns(stats.ConstantInputWarning, match=message): @@ -3331,11 +3331,10 @@ def test_constant_moments(self, dtype, expect, order, xp): 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 = np.arange(8).reshape(2, -1).astype(float) - a = xp.asarray(a) + a = xp.reshape(xp.arange(8.), (2, -1)) a[1, 0] = np.nan mm = stats.moment(a, 2, axis=1) - xp_assert_close(mm, xp.asarray([1.25, np.nan], dtype=xp.float64), atol=1e-15) + xp_assert_close(mm, xp.asarray([1.25, np.nan]), atol=1e-15) @array_api_compatible def test_moment_empty_order(self, xp): @@ -3395,12 +3394,12 @@ def test_skewness(self, xp): 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, dtype=xp.float64)) - xp_assert_close(y, xp.asarray(-0.29322304336607, dtype=xp.float64), atol=1e-10) - y = stats.skew(xp.asarray(self.testmathworks, dtype=xp.float64), bias=0) - xp_assert_close(y, xp.asarray(-0.437111105023940, dtype=xp.float64), atol=1e-10) - y = stats.skew(xp.asarray(self.testcase, dtype=xp.float64)) - xp_assert_close(y, xp.asarray(0.0, dtype=xp.float64), atol=1e-10) + 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 @@ -3619,10 +3618,10 @@ def test_onesample(self, xp): 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, dtype=xp.float64), 2.) + t, p = stats.ttest_1samp(xp.asarray(self.X1), 2.) - xp_assert_close(t, xp.asarray(self.T1_2, dtype=xp.float64)) - xp_assert_close(p, xp.asarray(self.P1_2, dtype=xp.float64)) + xp_assert_close(t, xp.asarray(self.T1_2)) + xp_assert_close(p, xp.asarray(self.P1_2)) def test_onesample_nan_policy(self, xp): # check nan policy @@ -3696,8 +3695,8 @@ def test_1samp_ci_iv(self, xp): 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 = data.astype(np.float64, copy=True) # ensure byte order - data = xp.asarray(data, dtype=xp.float64) + data = data.astype(copy=True) # ensure byte order + data = xp.asarray(data) res = stats.ttest_1samp(data, 0., alternative=alternative, axis=axis) l, u = res.confidence_interval(confidence_level=alpha) @@ -3707,7 +3706,7 @@ def test_pvalue_ci(self, alpha, data_axis, alternative, xp): res = stats.ttest_1samp(data, popmean, alternative=alternative, axis=axis) shape = list(data.shape) shape.pop(axis) - ref = xp.broadcast_to(xp.asarray(1-alpha, dtype=xp.float64), shape) + ref = xp.broadcast_to(xp.asarray(1-alpha), shape) xp_assert_close(res.pvalue, ref) From d61be38b0b57a78e77ccecb94fcf4065aaed2b52 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 2 May 2024 10:07:52 -0700 Subject: [PATCH 075/500] TST: integrate.tanhsinh: mark test case XSLOW (#20628) --- scipy/integrate/tests/test_tanhsinh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scipy/integrate/tests/test_tanhsinh.py b/scipy/integrate/tests/test_tanhsinh.py index 6e675968eedc..2680b89c6b02 100644 --- a/scipy/integrate/tests/test_tanhsinh.py +++ b/scipy/integrate/tests/test_tanhsinh.py @@ -1,4 +1,5 @@ # mypy: disable-error-code="attr-defined" +import os import pytest import numpy as np @@ -215,6 +216,8 @@ def test_accuracy(self, ref, case): if distname in {'dgamma', 'dweibull', 'laplace', 'kstwo'}: # should split up interval at first-derivative discontinuity pytest.skip('tanh-sinh is not great for non-smooth integrands') + if distname in {'studentized_range'} and not int(os.getenv('SCIPY_XSLOW', 0)): + pytest.skip('This case passes, but it is too slow.') dist = getattr(stats, distname)(*params) x = dist.interval(ref) res = _tanhsinh(dist.pdf, *x) From 75fb00dc876acf6250cab2bec24119c980527896 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Thu, 2 May 2024 21:19:52 +0100 Subject: [PATCH 076/500] ENH: stats.skewtest: add array-API support (#20597) * ENH: stats.skewtest: add array-API support --- scipy/_lib/_array_api.py | 1 + scipy/stats/_stats_py.py | 26 ++++++++++----- scipy/stats/tests/test_stats.py | 58 +++++++++++++++++++++++---------- 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 13d8cec74dee..89a8171b58e8 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -319,6 +319,7 @@ def xp_assert_close(actual, desired, rtol=None, atol=0, check_namespace=True, # roughly half way between sqrt(eps) and the default for # `numpy.testing.assert_allclose`, 1e-7 rtol = xp.finfo(actual.dtype).eps**0.5 * 4 + elif rtol is None: rtol = 1e-7 diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index d5202e75c80c..fb715e6010a2 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -1447,6 +1447,8 @@ def _get_pvalue(statistic, distribution, alternative, symmetric=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. @@ -1606,23 +1608,29 @@ def skewtest(a, axis=0, nan_policy='propagate', alternative='two-sided'): agree fairly closely, even for our small sample. """ + xp = array_namespace(a) + a, axis = _chk_asarray(a, axis, xp=xp) + b2 = skew(a, axis) n = a.shape[axis] if n < 8: raise ValueError( - "skewtest is not valid with less than 8 samples; %i samples" - " were given." % int(n)) + f"skewtest is not valid with less than 8 samples; {n} samples were given.") y = b2 * math.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 + math.sqrt(2 * (beta2 - 1)) delta = 1 / math.sqrt(0.5 * math.log(W2)) alpha = math.sqrt(2.0 / (W2 - 1)) - y = np.where(y == 0, 1, y) - Z = delta * np.log(y / alpha + np.sqrt((y / alpha)**2 + 1)) + y = xp.where(y == 0, xp.asarray(1, dtype=y.dtype), y) + Z = delta * xp.log(y / alpha + xp.sqrt((y / alpha)**2 + 1)) - pvalue = _get_pvalue(Z, distributions.norm, alternative) - return SkewtestResult(Z[()], pvalue[()]) + Z_np = np.asarray(Z) + pvalue = _get_pvalue(Z_np, distributions.norm, alternative) + pvalue = xp.asarray(pvalue, dtype=Z.dtype) + 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')) @@ -2508,7 +2516,7 @@ def _histogram(a, numbins=10, defaultlimits=None, weights=None, extrapoints = len([v for v in a if defaultlimits[0] > v or v > defaultlimits[1]]) if extrapoints > 0 and printextras: - warnings.warn("Points outside given histogram range = %s" % extrapoints, + warnings.warn(f"Points outside given histogram range = {extrapoints}", stacklevel=3,) return HistogramResult(hist, defaultlimits[0], binsize, extrapoints) @@ -8502,7 +8510,7 @@ def ks_1samp(x, cdf, args=(), alternative='two-sided', method='auto'): alternative = {'t': 'two-sided', 'g': 'greater', 'l': 'less'}.get( alternative.lower()[0], alternative) if alternative not in ['two-sided', 'greater', 'less']: - raise ValueError("Unexpected alternative %s" % alternative) + raise ValueError(f"Unexpected value {alternative=}") N = len(x) x = np.sort(x) @@ -9155,7 +9163,7 @@ def kstest(rvs, cdf, args=(), N=20, alternative='two-sided', method='auto'): if alternative == 'two_sided': alternative = 'two-sided' if alternative not in ['two-sided', 'greater', 'less']: - raise ValueError("Unexpected alternative %s" % alternative) + 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, diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 411a7f51a233..56e2acab88b9 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6009,26 +6009,44 @@ def test_describe_empty(self): assert_raises(ValueError, stats.describe, []) -def test_normalitytests(): +@pytest.mark.skip_xp_backends(cpu_only=True, + reasons=['Uses NumPy for pvalue']) +@pytest.mark.usefixtures("skip_xp_backends") +@array_api_compatible +def test_normalitytests(xp): assert_raises(ValueError, stats.skewtest, 4.) assert_raises(ValueError, stats.kurtosistest, 4.) assert_raises(ValueError, stats.normaltest, 4.) # numbers verified with R: dagoTest in package fBasics - st_normal, st_skew, st_kurt = (3.92371918, 1.98078826, -0.01403734) - pv_normal, pv_skew, pv_kurt = (0.14059673, 0.04761502, 0.98880019) + # 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 + st_normal, st_skew, st_kurt = (3.92371918, xp.asarray(1.98078826090875881), + -0.01403734) + pv_normal, pv_skew, pv_kurt = (0.14059673, xp.asarray(0.04761502382843208), + 0.98880019) pv_skew_less, pv_kurt_less = 1 - pv_skew / 2, pv_kurt / 2 pv_skew_greater, pv_kurt_greater = pv_skew / 2, 1 - pv_kurt / 2 - x = np.array((-2, -1, 0, 1, 2, 3)*4)**2 + x = np.array((-2, -1, 0, 1, 2, 3.)*4)**2 + x_xp = xp.asarray((-2, -1, 0, 1, 2, 3.)*4)**2 attributes = ('statistic', 'pvalue') assert_array_almost_equal(stats.normaltest(x), (st_normal, pv_normal)) check_named_results(stats.normaltest(x), attributes) - assert_array_almost_equal(stats.skewtest(x), (st_skew, pv_skew)) - assert_array_almost_equal(stats.skewtest(x, alternative='less'), - (st_skew, pv_skew_less)) - assert_array_almost_equal(stats.skewtest(x, alternative='greater'), - (st_skew, pv_skew_greater)) + res = stats.skewtest(x_xp) + xp_assert_close(res.statistic, st_skew) + xp_assert_close(res.pvalue, pv_skew) + res = stats.skewtest(x_xp, alternative='less') + xp_assert_close(res.statistic, st_skew) + xp_assert_close(res.pvalue, pv_skew_less) + res = stats.skewtest(x_xp, alternative='greater') + xp_assert_close(res.statistic, st_skew) + xp_assert_close(res.pvalue, pv_skew_greater) check_named_results(stats.skewtest(x), attributes) assert_array_almost_equal(stats.kurtosistest(x), (st_kurt, pv_kurt)) assert_array_almost_equal(stats.kurtosistest(x, alternative='less'), @@ -6041,8 +6059,9 @@ def test_normalitytests(): # see gh-13549. # skew parameter is 1 > 0 a1 = stats.skewnorm.rvs(a=1, size=10000, random_state=123) - pval = stats.skewtest(a1, alternative='greater').pvalue - assert_almost_equal(pval, 0.0, decimal=5) + 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) # excess kurtosis of laplace is 3 > 0 a2 = stats.laplace.rvs(size=10000, random_state=123) pval = stats.kurtosistest(a2, alternative='greater').pvalue @@ -6051,16 +6070,19 @@ def test_normalitytests(): # Test axis=None (equal to axis=0 for 1-D input) assert_array_almost_equal(stats.normaltest(x, axis=None), (st_normal, pv_normal)) - assert_array_almost_equal(stats.skewtest(x, axis=None), - (st_skew, pv_skew)) + res = stats.skewtest(x_xp, axis=None) + xp_assert_close(res.statistic, st_skew) + xp_assert_close(res.pvalue, pv_skew) assert_array_almost_equal(stats.kurtosistest(x, axis=None), (st_kurt, pv_kurt)) - x = np.arange(10.) - x[9] = np.nan + x = xp.arange(10.) + x[9] = xp.nan with np.errstate(invalid="ignore"): - assert_array_equal(stats.skewtest(x), (np.nan, np.nan)) + assert_array_equal(stats.skewtest(x), (xp.nan, xp.nan)) + # nan_policy only compatible with NumPy arrays + x = np.asarray(x) expected = (1.0184643553962129, 0.30845733195153502) assert_array_almost_equal(stats.skewtest(x, nan_policy='omit'), expected) @@ -6831,7 +6853,7 @@ def test_binomtest(): for p, res in zip(pp, results): assert_approx_equal(stats.binomtest(x, n, p).pvalue, res, - significant=12, err_msg='fail forp=%f' % p) + significant=12, err_msg=f'fail forp={p}') assert_approx_equal(stats.binomtest(50, 100, 0.1).pvalue, 5.8320387857343647e-024, significant=12) @@ -7393,7 +7415,7 @@ def test_nist(self): rtol = 1e-4 assert_allclose(res[0], f, rtol=rtol, - err_msg='Failing testcase: %s' % test_case) + err_msg=f'Failing testcase: {test_case}') @pytest.mark.parametrize("a, b, expected", [ (np.array([42, 42, 42]), np.array([7, 7, 7]), (np.inf, 0)), From bec9c7d253b439a51e2b1ac3132f4df5f5ff8b80 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 2 May 2024 16:58:35 -0600 Subject: [PATCH 077/500] BUG: fix Vor/Delaunay segfaults * Deals with the `spatial` part of gh-20623 (`Voronoi` was also affected beyond the originally-reported `Delaunay` segfault). * Both classes are documented to accept arrays with two dimensions only, so raise a `ValueError` in cases with other dimensions to avoid the segfault. * Other potential points of confusion here are the differences between arrays with two dimensions and two dimensional arrays that represent generators with more than two dimensions via the number of columns, but this is just how things are for most array programming languages of course. Also, the docs don't explicitly say array-like for the generators I don't think, but this patch only worked if I placed it after the coercion to array type. [skip circle] --- scipy/spatial/_qhull.pyx | 4 ++++ scipy/spatial/tests/test_qhull.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/scipy/spatial/_qhull.pyx b/scipy/spatial/_qhull.pyx index d6d7ce6361b1..e24f362c5136 100644 --- a/scipy/spatial/_qhull.pyx +++ b/scipy/spatial/_qhull.pyx @@ -1802,6 +1802,8 @@ class Delaunay(_QhullUser): if np.ma.isMaskedArray(points): raise ValueError('Input points cannot be a masked array') points = np.ascontiguousarray(points, dtype=np.double) + if points.ndim != 2: + raise ValueError("Input points array must have 2 dimensions.") if qhull_options is None: if not incremental: @@ -2592,6 +2594,8 @@ class Voronoi(_QhullUser): if np.ma.isMaskedArray(points): raise ValueError('Input points cannot be a masked array') points = np.ascontiguousarray(points, dtype=np.double) + if points.ndim != 2: + raise ValueError("Input points array must have 2 dimensions.") if qhull_options is None: if not incremental: diff --git a/scipy/spatial/tests/test_qhull.py b/scipy/spatial/tests/test_qhull.py index 8a94e380482c..7efaa5525896 100644 --- a/scipy/spatial/tests/test_qhull.py +++ b/scipy/spatial/tests/test_qhull.py @@ -1178,3 +1178,11 @@ def test_cube(self): assert set(a) == set(b) # facet orientation can differ assert_allclose(hs.dual_points, qhalf_points) + + +@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) From 287f353bb6c9194fe54edd2da3ffcf2e4423cb96 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 29 Apr 2024 05:50:44 +0200 Subject: [PATCH 078/500] CI: add job that builds for non-default Python interpreter on Ubuntu This guards against issues with trying to use the `python3` interpreter for anything that actually requires the Python interpreter we are building for. In such a build config with multiple interpreters, we need to be careful not to pick up the wrong one when invoking `custom_target` or `run_command` in meson.build files. --- .github/workflows/linux.yml | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1096e7f06e4f..3cbe99e3a8e8 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -387,3 +387,48 @@ jobs: python -m pip install numpy==1.23.5 cython pybind11 pytest pytest-timeout pytest-xdist pytest-env 'Pillow<10.0.0' mpmath pythran pooch meson hypothesis && \ LD_LIBRARY_PATH=/usr/local/lib python dev.py build && \ LD_LIBRARY_PATH=/usr/local/lib python dev.py test" + + ################################################################################# + distro_multiple_pythons: + # Purpose is to build for a non-default Python interpreter in a Linux distro + # For such a build config, `python`/`python3` executables may not have + # build dependencies like Cython or NumPy installed. + name: non-default Python interpreter, fast, py3.10/npMin, pip+pytest + needs: get_commit_message + if: > + needs.get_commit_message.outputs.message == 1 + && (github.repository == 'scipy/scipy' || github.repository == '') + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4.1.1 + with: + submodules: recursive + + - name: Setup system dependencies + run: | + sudo apt-get -y update + # `python3-dev` yields Python 3.10 on Ubuntu 22.04 + sudo apt install -y python3-dev python3.11-dev ninja-build pkg-config libatlas-base-dev liblapack-dev + + - name: Setup Python build deps + run: | + python3.11 -m pip install build pythran pybind11 cython numpy meson-python + + - name: Build wheel and install + run: | + python3.11 -m build -wnx -Csetup-args=-Dblas=blas-atlas -Csetup-args=-Dlapack=lapack-atlas -Ccompile-args=-j2 + python3.11 -m pip install dist/*.whl + + - name: Install test dependencies + run: | + python3.11 -m pip install pytest hypothesis + python3.10 -m pip install meson # ensure compile test work with this + + - name: Run tests + run: | + # Just a small subset of tests; this will be fine if the build + # succeeds (that's the real purpose of this job) + pushd $RUNNER_TEMP + python3.11 -m pytest --pyargs scipy.cluster + python3.11 -m pytest --pyargs scipy.linalg + popd From d85ba6b910ea9040b6a72bdc4ea87d151118f41d Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 29 Apr 2024 06:57:55 +0200 Subject: [PATCH 079/500] BLD: use a generator for f2py This removes all remaining invocations of `py3` in meson.build files. Not invoking the Python interpreter we're building for at all helps with cross-compilation, since that interpreter typically isn't able to run without the use of an emulator like QEMU. --- mypy.ini | 2 +- scipy/_build_utils/_generate_blas_wrapper.py | 1 + scipy/_build_utils/echo.py | 12 +++++++ scipy/_build_utils/meson.build | 4 +-- scipy/integrate/meson.build | 32 +++---------------- .../{banded5x5.pyf => test_odeint_banded.pyf} | 0 scipy/interpolate/_fitpack2.py | 2 +- scipy/interpolate/_fitpack_impl.py | 2 +- scipy/interpolate/_interpolate.py | 2 +- scipy/interpolate/meson.build | 10 ++---- .../src/{fitpack.pyf => dfitpack.pyf} | 2 +- scipy/interpolate/tests/test_bsplines.py | 2 +- scipy/interpolate/tests/test_fitpack.py | 2 +- scipy/io/meson.build | 8 +---- .../{_test_fortran.pyf => test_fortran.pyf} | 0 scipy/linalg/_generate_pyx.py | 1 + scipy/linalg/meson.build | 15 +++------ scipy/meson.build | 12 ++++++- scipy/optimize/meson.build | 32 +++---------------- scipy/sparse/_generate_sparsetools.py | 1 + scipy/sparse/linalg/_propack/meson.build | 8 +---- scipy/sparse/sparsetools/meson.build | 3 +- scipy/special/_generate_pyx.py | 1 + scipy/special/meson.build | 3 +- scipy/stats/meson.build | 9 ++---- tools/generate_f2pymod.py | 5 ++- tools/version_utils.py | 1 + 27 files changed, 62 insertions(+), 110 deletions(-) create mode 100644 scipy/_build_utils/echo.py rename scipy/integrate/tests/{banded5x5.pyf => test_odeint_banded.pyf} (100%) rename scipy/interpolate/src/{fitpack.pyf => dfitpack.pyf} (99%) rename scipy/io/{_test_fortran.pyf => test_fortran.pyf} (100%) diff --git a/mypy.ini b/mypy.ini index 8aa1f57c8af3..943daa4d4742 100644 --- a/mypy.ini +++ b/mypy.ini @@ -119,7 +119,7 @@ ignore_missing_imports = True [mypy-scipy.optimize._slsqp] ignore_missing_imports = True -[mypy-scipy.interpolate.dfitpack] +[mypy-scipy.interpolate._dfitpack] ignore_missing_imports = True [mypy-scipy.interpolate.interpnd] diff --git a/scipy/_build_utils/_generate_blas_wrapper.py b/scipy/_build_utils/_generate_blas_wrapper.py index 3ce717508d00..b36092291325 100644 --- a/scipy/_build_utils/_generate_blas_wrapper.py +++ b/scipy/_build_utils/_generate_blas_wrapper.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """ Generate wrappers to dispatch BLAS/LAPACK calls to the properly prefixed/ suffixed symbols. diff --git a/scipy/_build_utils/echo.py b/scipy/_build_utils/echo.py new file mode 100644 index 000000000000..27efdbe0f686 --- /dev/null +++ b/scipy/_build_utils/echo.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +""" +A dummy script that only echos its input arguments. + +This is useful in case a platform-independent way to run a no-op command +on a target in a meson.build file is needed (e.g., to establish a +dependency between targets). +""" +import logging +import sys + +logging.debug(f"Passed args to `scipy/_build_utils/echo.py`: {sys.argv[1:]}") diff --git a/scipy/_build_utils/meson.build b/scipy/_build_utils/meson.build index 27c38b5125dd..c9de2910900a 100644 --- a/scipy/_build_utils/meson.build +++ b/scipy/_build_utils/meson.build @@ -1,8 +1,8 @@ if generate_blas_wrappers - blas_wrapper_gen = files('_generate_blas_wrapper.py') + blas_wrapper_gen = find_program('_generate_blas_wrapper.py') blas_lapack_wrappers = custom_target('blas_lapack_wrappers', output: ['blas_lapack_wrappers.c'], - command: [py3, blas_wrapper_gen, '-o', '@OUTDIR@', accelerate_flag], + command: [blas_wrapper_gen, '-o', '@OUTDIR@', accelerate_flag], depend_files: [ '../linalg/cython_blas_signatures.txt', '../linalg/cython_lapack_signatures.txt', diff --git a/scipy/integrate/meson.build b/scipy/integrate/meson.build index 6ecdc5f19e72..1a0edfc724d3 100644 --- a/scipy/integrate/meson.build +++ b/scipy/integrate/meson.build @@ -128,14 +128,8 @@ py3.extension_module('_odepack', subdir: 'scipy/integrate' ) -vode_module = custom_target('vode_module', - output: ['_vode-f2pywrappers.f', '_vodemodule.c'], - input: 'vode.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - py3.extension_module('_vode', - vode_module, + f2py_gen.process('vode.pyf'), link_with: [vode_lib], c_args: [Wno_unused_variable], link_args: version_link_args, @@ -145,14 +139,8 @@ py3.extension_module('_vode', subdir: 'scipy/integrate' ) -lsoda_module = custom_target('lsoda_module', - output: ['_lsoda-f2pywrappers.f', '_lsodamodule.c'], - input: 'lsoda.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - py3.extension_module('_lsoda', - lsoda_module, + f2py_gen.process('lsoda.pyf'), link_with: [lsoda_lib, mach_lib], c_args: [Wno_unused_variable], dependencies: [lapack_dep, fortranobject_dep], @@ -162,14 +150,8 @@ py3.extension_module('_lsoda', subdir: 'scipy/integrate' ) -_dop_module = custom_target('_dop_module', - output: ['_dop-f2pywrappers.f', '_dopmodule.c'], - input: 'dop.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - py3.extension_module('_dop', - _dop_module, + f2py_gen.process('dop.pyf'), link_with: [dop_lib], c_args: [Wno_unused_variable], dependencies: [fortranobject_dep], @@ -186,14 +168,8 @@ py3.extension_module('_test_multivariate', subdir: 'scipy/integrate' ) -_test_odeint_banded_module = custom_target('_test_odeint_banded_module', - output: ['_test_odeint_bandedmodule.c', '_test_odeint_banded-f2pywrappers.f'], - input: 'tests/banded5x5.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - py3.extension_module('_test_odeint_banded', - ['tests/banded5x5.f', _test_odeint_banded_module], + ['tests/banded5x5.f', f2py_gen.process('tests/test_odeint_banded.pyf')], link_with: [lsoda_lib, mach_lib], fortran_args: _fflag_Wno_unused_dummy_argument, link_args: version_link_args, diff --git a/scipy/integrate/tests/banded5x5.pyf b/scipy/integrate/tests/test_odeint_banded.pyf similarity index 100% rename from scipy/integrate/tests/banded5x5.pyf rename to scipy/integrate/tests/test_odeint_banded.pyf diff --git a/scipy/interpolate/_fitpack2.py b/scipy/interpolate/_fitpack2.py index 88e916287c98..b6820b4fa4a4 100644 --- a/scipy/interpolate/_fitpack2.py +++ b/scipy/interpolate/_fitpack2.py @@ -25,7 +25,7 @@ import numpy as np from . import _fitpack_impl -from . import dfitpack +from . import _dfitpack as dfitpack dfitpack_int = dfitpack.types.intvar.dtype diff --git a/scipy/interpolate/_fitpack_impl.py b/scipy/interpolate/_fitpack_impl.py index 99c660e2b183..307758b46b8e 100644 --- a/scipy/interpolate/_fitpack_impl.py +++ b/scipy/interpolate/_fitpack_impl.py @@ -32,7 +32,7 @@ # Try to replace _fitpack interface with # f2py-generated version -from . import dfitpack +from . import _dfitpack as dfitpack dfitpack_int = dfitpack.types.intvar.dtype diff --git a/scipy/interpolate/_interpolate.py b/scipy/interpolate/_interpolate.py index 9d9d460f52e7..fa37db0e78ae 100644 --- a/scipy/interpolate/_interpolate.py +++ b/scipy/interpolate/_interpolate.py @@ -12,7 +12,7 @@ from scipy.special import comb from . import _fitpack_py -from . import dfitpack +from . import _dfitpack as dfitpack from ._polyint import _Interpolator1D from . import _ppoly from .interpnd import _ndim_coords_from_arrays diff --git a/scipy/interpolate/meson.build b/scipy/interpolate/meson.build index 43cc02102244..61b55bcfaa87 100644 --- a/scipy/interpolate/meson.build +++ b/scipy/interpolate/meson.build @@ -143,15 +143,9 @@ py3.extension_module('_fitpack', subdir: 'scipy/interpolate' ) -dfitpack_module = custom_target('dfitpack_module', - output: ['dfitpack-f2pywrappers.f', 'dfitpackmodule.c'], - input: 'src/fitpack.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - # TODO: Add flags for 64 bit ints -py3.extension_module('dfitpack', - dfitpack_module, +py3.extension_module('_dfitpack', + f2py_gen.process('src/dfitpack.pyf'), c_args: [Wno_unused_variable], link_args: version_link_args, dependencies: [lapack_dep, fortranobject_dep], diff --git a/scipy/interpolate/src/fitpack.pyf b/scipy/interpolate/src/dfitpack.pyf similarity index 99% rename from scipy/interpolate/src/fitpack.pyf rename to scipy/interpolate/src/dfitpack.pyf index a9535a83f479..80ba286b6124 100644 --- a/scipy/interpolate/src/fitpack.pyf +++ b/scipy/interpolate/src/dfitpack.pyf @@ -9,7 +9,7 @@ ! This limit can be overridden with -fmax-stack-var-size, or -frecursive can ! be used to force all local arrays to be allocated on the stack. ! -python module dfitpack ! in +python module _dfitpack ! in usercode ''' diff --git a/scipy/interpolate/tests/test_bsplines.py b/scipy/interpolate/tests/test_bsplines.py index 3a75f4d30743..4f16a89b394f 100644 --- a/scipy/interpolate/tests/test_bsplines.py +++ b/scipy/interpolate/tests/test_bsplines.py @@ -24,7 +24,7 @@ # XXX: move to the interpolate namespace from scipy.interpolate._ndbspline import make_ndbspl -from scipy.interpolate import dfitpack +from scipy.interpolate import _dfitpack as dfitpack from scipy.interpolate import _bsplines as _b diff --git a/scipy/interpolate/tests/test_fitpack.py b/scipy/interpolate/tests/test_fitpack.py index c76178681de0..46f8c781f695 100644 --- a/scipy/interpolate/tests/test_fitpack.py +++ b/scipy/interpolate/tests/test_fitpack.py @@ -12,7 +12,7 @@ from scipy.interpolate._fitpack_py import (splrep, splev, bisplrep, bisplev, sproot, splprep, splint, spalde, splder, splantider, insert, dblint) -from scipy.interpolate.dfitpack import regrid_smth +from scipy.interpolate._dfitpack import regrid_smth from scipy.interpolate._fitpack2 import dfitpack_int diff --git a/scipy/io/meson.build b/scipy/io/meson.build index 019c98506647..94ad2d289222 100644 --- a/scipy/io/meson.build +++ b/scipy/io/meson.build @@ -1,12 +1,6 @@ -_test_fortran_module = custom_target('_test_fortran_module', - output: ['_test_fortranmodule.c'], - input: '_test_fortran.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - py3.extension_module('_test_fortran', [ - _test_fortran_module, + f2py_gen.process('test_fortran.pyf'), '_test_fortran.f' ], c_args: [Wno_unused_variable], diff --git a/scipy/io/_test_fortran.pyf b/scipy/io/test_fortran.pyf similarity index 100% rename from scipy/io/_test_fortran.pyf rename to scipy/io/test_fortran.pyf diff --git a/scipy/linalg/_generate_pyx.py b/scipy/linalg/_generate_pyx.py index cb12783918a8..8a00f5d279e1 100644 --- a/scipy/linalg/_generate_pyx.py +++ b/scipy/linalg/_generate_pyx.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """ Code generator script to make the Cython BLAS and LAPACK wrappers from the files "cython_blas_signatures.txt" and diff --git a/scipy/linalg/meson.build b/scipy/linalg/meson.build index e8e01e4f5d57..a058a86aef44 100644 --- a/scipy/linalg/meson.build +++ b/scipy/linalg/meson.build @@ -3,6 +3,7 @@ __init__py = fs.copyfile('__init__.py') _cy_array_utils_pxd = fs.copyfile('_cythonized_array_utils.pxd') +_generate_pyx = find_program('_generate_pyx.py') cython_linalg = custom_target('cython_linalg', output: [ 'cython_blas.pyx', @@ -13,7 +14,7 @@ cython_linalg = custom_target('cython_linalg', '_lapack_subroutines.h' ], input: '_generate_pyx.py', - command: [py3, '@INPUT@', '-o', '@OUTDIR@', accelerate_flag], + command: [_generate_pyx, '-o', '@OUTDIR@', accelerate_flag], depend_files: [ 'cython_blas_signatures.txt', 'cython_lapack_signatures.txt', @@ -30,10 +31,11 @@ cython_linalg = custom_target('cython_linalg', # warnings about multiple outputs. And using `depends: cython_linalg[2]` does # not work, because that generator depends does not accept CustomTargetIndex, # only CustomTarget. See https://github.com/mesonbuild/meson/issues/9837 +_echo = find_program('../_build_utils/echo.py') cython_blas_pxd = custom_target( output: '_dummy_cython_blas.pxd', input: cython_linalg[2], - command: [py3, '-c', '"@INPUT@"'], + command: [_echo, '@INPUT@'], ) # pyx -> c, pyx -> cpp generators, depending on __init__.py here. @@ -109,13 +111,6 @@ py3.extension_module('_flapack', # TODO: cblas/clapack are built *only* for ATLAS. Why? Is it still needed? -# _interpolative -interpolative_module = custom_target('interpolative_module', - output: '_interpolativemodule.c', - input: 'interpolative.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - # id_dist contains a copy of FFTPACK, which has type mismatch warnings # that are hard to fix. This code is terrible and noisy during the build, # silence it completely. @@ -159,7 +154,7 @@ py3.extension_module('_interpolative', 'src/id_dist/src/idzr_rid.f', 'src/id_dist/src/idzr_rsvd.f', 'src/id_dist/src/prini.f', - interpolative_module, + f2py_gen.process('interpolative.pyf'), ], fortran_args: [fortran_ignore_warnings, _suppress_all_warnings], link_args: version_link_args, diff --git a/scipy/meson.build b/scipy/meson.build index 5f7279fa8cb9..ad1cdd1eb4be 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -144,6 +144,15 @@ fortranobject_dep = declare_dependency( compile_args: _f2py_c_args, ) +f2py = find_program('f2py') +# Note: cannot handle .pyf.src or targets with #include's (due to no +# `depend_files` - see feature request at meson#8295) +f2py_gen = generator(generate_f2pymod, + arguments : ['@INPUT@', '-o', '@BUILD_DIR@'], + output : ['_@BASENAME@module.c', '_@BASENAME@-f2pywrappers.f'], +) + + # TODO: 64-bit BLAS and LAPACK # # Note that this works as long as BLAS and LAPACK are detected properly via @@ -252,6 +261,7 @@ endif scipy_dir = py3.get_install_dir() / 'scipy' +version_utils = find_program('../tools/version_utils.py') generate_version = custom_target( 'generate-version', install: true, @@ -259,7 +269,7 @@ generate_version = custom_target( build_by_default: true, output: 'version.py', input: '../tools/version_utils.py', - command: [py3, '@INPUT@', '--source-root', '@SOURCE_ROOT@'], + command: [version_utils, '--source-root', '@SOURCE_ROOT@'], install_dir: scipy_dir, install_tag: 'python-runtime', ) diff --git a/scipy/optimize/meson.build b/scipy/optimize/meson.build index e608325741df..f7655683df38 100644 --- a/scipy/optimize/meson.build +++ b/scipy/optimize/meson.build @@ -92,18 +92,12 @@ py3.extension_module('_zeros', subdir: 'scipy/optimize' ) -lbfgsb_module = custom_target('lbfgsb_module', - output: ['_lbfgsb-f2pywrappers.f', '_lbfgsbmodule.c'], - input: 'lbfgsb_src/lbfgsb.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - py3.extension_module('_lbfgsb', [ 'lbfgsb_src/lbfgsb.f', 'lbfgsb_src/linpack.f', 'lbfgsb_src/timer.f', - lbfgsb_module, + f2py_gen.process('lbfgsb_src/lbfgsb.pyf'), ], fortran_args: fortran_ignore_warnings, link_args: version_link_args, @@ -126,14 +120,8 @@ py3.extension_module('_moduleTNC', subdir: 'scipy/optimize' ) -cobyla_module = custom_target('cobyla_module', - output: ['_cobylamodule.c'], - input: 'cobyla/cobyla.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - py3.extension_module('_cobyla', - [cobyla_module, 'cobyla/cobyla2.f', 'cobyla/trstlp.f'], + [f2py_gen.process('cobyla/cobyla.pyf'), 'cobyla/cobyla2.f', 'cobyla/trstlp.f'], c_args: [Wno_unused_variable], fortran_args: fortran_ignore_warnings, link_args: version_link_args, @@ -143,14 +131,8 @@ py3.extension_module('_cobyla', subdir: 'scipy/optimize' ) -minpack2_module = custom_target('minpack2_module', - output: ['_minpack2module.c'], - input: 'minpack2/minpack2.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - py3.extension_module('_minpack2', - [minpack2_module, 'minpack2/dcsrch.f', 'minpack2/dcstep.f'], + [f2py_gen.process('minpack2/minpack2.pyf'), 'minpack2/dcsrch.f', 'minpack2/dcstep.f'], fortran_args: fortran_ignore_warnings, link_args: version_link_args, dependencies: [fortranobject_dep], @@ -160,14 +142,8 @@ py3.extension_module('_minpack2', subdir: 'scipy/optimize' ) -slsqp_module = custom_target('slsqp_module', - output: ['_slsqpmodule.c'], - input: 'slsqp/slsqp.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - py3.extension_module('_slsqp', - [slsqp_module, 'slsqp/slsqp_optmz.f'], + [f2py_gen.process('slsqp/slsqp.pyf'), 'slsqp/slsqp_optmz.f'], fortran_args: fortran_ignore_warnings, link_args: version_link_args, dependencies: [fortranobject_dep], diff --git a/scipy/sparse/_generate_sparsetools.py b/scipy/sparse/_generate_sparsetools.py index 9f6b87ee4c3a..da9a23b25163 100644 --- a/scipy/sparse/_generate_sparsetools.py +++ b/scipy/sparse/_generate_sparsetools.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """ python generate_sparsetools.py diff --git a/scipy/sparse/linalg/_propack/meson.build b/scipy/sparse/linalg/_propack/meson.build index df358df651ab..671472495859 100644 --- a/scipy/sparse/linalg/_propack/meson.build +++ b/scipy/sparse/linalg/_propack/meson.build @@ -97,14 +97,8 @@ foreach ele: elements gnu_symbol_visibility: 'hidden', ) - propack_module = custom_target('propack_module' + ele[0], - output: [ele[0] + '-f2pywrappers.f', ele[0] + 'module.c'], - input: ele[2], - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] - ) - propacklib = py3.extension_module(ele[0], - propack_module, + f2py_gen.process(ele[2]), link_with: propack_lib, c_args: ['-U_OPENMP', _cpp_Wno_cpp], fortran_args: _fflag_Wno_maybe_uninitialized, diff --git a/scipy/sparse/sparsetools/meson.build b/scipy/sparse/sparsetools/meson.build index 6568141a450f..82f7d4b188af 100644 --- a/scipy/sparse/sparsetools/meson.build +++ b/scipy/sparse/sparsetools/meson.build @@ -1,3 +1,4 @@ +gen_sparsetools = find_program('../_generate_sparsetools.py') _sparsetools_headers = custom_target('_sparsetools_headers', output: [ 'bsr_impl.h', @@ -7,7 +8,7 @@ _sparsetools_headers = custom_target('_sparsetools_headers', 'sparsetools_impl.h', ], input: '../_generate_sparsetools.py', - command: [py3, '@INPUT@', '--no-force', '-o', '@OUTDIR@'] + command: [gen_sparsetools, '--no-force', '-o', '@OUTDIR@'] ) py3.extension_module('_sparsetools', diff --git a/scipy/special/_generate_pyx.py b/scipy/special/_generate_pyx.py index 9f64eb4fd5f0..dae8d65288f2 100644 --- a/scipy/special/_generate_pyx.py +++ b/scipy/special/_generate_pyx.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """ python _generate_pyx.py diff --git a/scipy/special/meson.build b/scipy/special/meson.build index 4da069a83e54..9456b72846fe 100644 --- a/scipy/special/meson.build +++ b/scipy/special/meson.build @@ -92,6 +92,7 @@ py3.extension_module('_specfun', # There's no good way around this currently, because py3.install_sources # doesn't accept generated targets. See TODO near the end of # scipy/linalg/meson.build for more details. +_generate_pyx = find_program('_generate_pyx.py') cython_special = custom_target('cython_special', output: [ '_ufuncs.pyx', @@ -101,7 +102,7 @@ cython_special = custom_target('cython_special', '_ufuncs_cxx_defs.h' ], input: ['_generate_pyx.py', 'functions.json', '_add_newdocs.py'], - command: [py3, '@INPUT0@', '-o', '@OUTDIR@'], + command: [_generate_pyx, '-o', '@OUTDIR@'], install: true, install_dir: py3.get_install_dir() / 'scipy/special', install_tag: 'devel', diff --git a/scipy/stats/meson.build b/scipy/stats/meson.build index 01a377b05b90..51fd8c10255b 100644 --- a/scipy/stats/meson.build +++ b/scipy/stats/meson.build @@ -31,14 +31,9 @@ py3.extension_module('_ansari_swilk_statistics', subdir: 'scipy/stats' ) -mvn_module = custom_target('mvn_module', - output: ['_mvn-f2pywrappers.f', '_mvnmodule.c'], - input: 'mvn.pyf', - command: [generate_f2pymod, '@INPUT@', '-o', '@OUTDIR@'] -) - py3.extension_module('_mvn', - [mvn_module, 'mvndst.f'], + #[mvn_module, 'mvndst.f'], + [f2py_gen.process('mvn.pyf'), 'mvndst.f'], # Wno-surprising is to suppress a pointless warning with GCC 10-12 # (see GCC bug 98411: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98411) fortran_args: [fortran_ignore_warnings, _fflag_Wno_surprising], diff --git a/tools/generate_f2pymod.py b/tools/generate_f2pymod.py index 4b4ce105a011..b6bc02eb046d 100644 --- a/tools/generate_f2pymod.py +++ b/tools/generate_f2pymod.py @@ -5,11 +5,10 @@ Usage: python generate_pyf.py filename.pyf.src -o filename.pyf """ +import argparse import os -import sys import re import subprocess -import argparse # START OF CODE VENDORED FROM `numpy.distutils.from_template` @@ -284,7 +283,7 @@ def main(): # Now invoke f2py to generate the C API module file if args.infile.endswith(('.pyf.src', '.pyf')): - p = subprocess.Popen([sys.executable, '-m', 'numpy.f2py', fname_pyf, + p = subprocess.Popen(['f2py', fname_pyf, '--build-dir', outdir_abs], #'--quiet'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=os.getcwd()) diff --git a/tools/version_utils.py b/tools/version_utils.py index 277e2dd9ee4c..cea0e0af4b63 100644 --- a/tools/version_utils.py +++ b/tools/version_utils.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import os import subprocess import argparse From ca2243620b15fa55b059b214d83d88fa0c7132cd Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 29 Apr 2024 09:11:54 +0200 Subject: [PATCH 080/500] DEP: add deprecation shim for `interpolate.dfitpack` This private namespace was already deprecated in the `__getattr__` in `interpolate/__init__.py`, however since the f2py-generated extension module `dfitpack` had to be moved to `_dfitpack`, this .py file has to be added instead. Do so with the exact same pattern as `fitpack.py` and co. --- scipy/interpolate/dfitpack.py | 44 +++++++++++++++++++++++++++++++++++ scipy/interpolate/meson.build | 1 + 2 files changed, 45 insertions(+) create mode 100644 scipy/interpolate/dfitpack.py diff --git a/scipy/interpolate/dfitpack.py b/scipy/interpolate/dfitpack.py new file mode 100644 index 000000000000..e10da3b3fd0c --- /dev/null +++ b/scipy/interpolate/dfitpack.py @@ -0,0 +1,44 @@ +# This file is not meant for public use and will be removed in SciPy v2.0.0. +# Use the `scipy.interpolate` namespace for importing the functions +# included below. + +from scipy._lib.deprecation import _sub_module_deprecation + + +__all__ = [ # noqa: F822 + 'bispeu', + 'bispev', + 'curfit', + 'dblint', + 'fpchec', + 'fpcurf0', + 'fpcurf1', + 'fpcurfm1', + 'parcur', + 'parder', + 'pardeu', + 'pardtc', + 'percur', + 'regrid_smth', + 'regrid_smth_spher', + 'spalde', + 'spherfit_lsq', + 'spherfit_smth', + 'splder', + 'splev', + 'splint', + 'sproot', + 'surfit_lsq', + 'surfit_smth', + 'types', +] + + +def __dir__(): + return __all__ + + +def __getattr__(name): + return _sub_module_deprecation(sub_package="interpolate", module="dfitpack", + private_modules=["_dfitpack"], all=__all__, + attribute=name) diff --git a/scipy/interpolate/meson.build b/scipy/interpolate/meson.build index 61b55bcfaa87..69ec25f6af58 100644 --- a/scipy/interpolate/meson.build +++ b/scipy/interpolate/meson.build @@ -187,6 +187,7 @@ py3.install_sources([ '_rbfinterp.py', '_rgi.py', '_ndbspline.py', + 'dfitpack.py', 'fitpack.py', 'fitpack2.py', 'interpolate.py', From 477c7a4621dcbc724e75e1dc5377158c8f8a0f53 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 29 Apr 2024 11:30:26 +0200 Subject: [PATCH 081/500] TST: ensure `test_extending` compile tests work with any `meson` The `meson` executable isn't guaranteed to be installed for the same Python interpreter as we're running the tests for (this may for example happen in distro setups when we're testing a non-default Python). Explicitly passing the Python interpreter we're testing for to Meson via a native file should be a robust solution. --- scipy/_lib/_testutils.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scipy/_lib/_testutils.py b/scipy/_lib/_testutils.py index e71c665e51c7..aac70d79c0f2 100644 --- a/scipy/_lib/_testutils.py +++ b/scipy/_lib/_testutils.py @@ -280,20 +280,36 @@ def _test_cython_extension(tmp_path, srcdir): Helper function to test building and importing Cython modules that make use of the Cython APIs for BLAS, LAPACK, optimize, and special. """ + import pytest + try: + subprocess.check_call(["meson", "--version"]) + except FileNotFoundError: + pytest.skip("No usable 'meson' found") + # build the examples in a temporary directory mod_name = os.path.split(srcdir)[1] shutil.copytree(srcdir, tmp_path / mod_name) build_dir = tmp_path / mod_name / 'tests' / '_cython_examples' target_dir = build_dir / 'build' os.makedirs(target_dir, exist_ok=True) + + # Ensure we use the correct Python interpreter even when `meson` is + # installed in a different Python environment (see numpy#24956) + native_file = str(build_dir / 'interpreter-native-file.ini') + with open(native_file, 'w') as f: + f.write("[binaries]\n") + f.write(f"python = '{sys.executable}'") + if sys.platform == "win32": subprocess.check_call(["meson", "setup", "--buildtype=release", + "--native-file", native_file, "--vsenv", str(build_dir)], cwd=target_dir, ) else: - subprocess.check_call(["meson", "setup", str(build_dir)], + subprocess.check_call(["meson", "setup", + "--native-file", native_file, str(build_dir)], cwd=target_dir ) subprocess.check_call(["meson", "compile", "-vv"], cwd=target_dir) From 3c371129da9ee8229e6ca68cfb87b0605a4a56df Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Thu, 2 May 2024 21:27:10 +0200 Subject: [PATCH 082/500] BLD: check f2py version and add comments on use of `f2py` as executable --- meson.build | 2 ++ pyproject.toml | 2 +- scipy/meson.build | 13 +++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 756f81c3aae5..ed92bdde6d36 100644 --- a/meson.build +++ b/meson.build @@ -21,6 +21,8 @@ project( py3 = import('python').find_installation(pure: false) py3_dep = py3.dependency() +min_numpy_version = '1.23.5' # keep in sync with pyproject.toml + # Emit a warning for 32-bit Python installs on Windows; users are getting # unexpected from-source builds there because we no longer provide wheels. is_windows = host_machine.system() == 'windows' diff --git a/pyproject.toml b/pyproject.toml index bfe80e698c13..ab241ac2368e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ maintainers = [ # release branches, see: # https://scipy.github.io/devdocs/dev/core-dev/index.html#version-ranges-for-numpy-and-other-dependencies requires-python = ">=3.10" -dependencies = ["numpy>=1.23.5"] +dependencies = ["numpy>=1.23.5"] # keep in sync with `min_numpy_version` in meson.build readme = "README.rst" classifiers = [ "Development Status :: 5 - Production/Stable", diff --git a/scipy/meson.build b/scipy/meson.build index ad1cdd1eb4be..bbad81ae3df0 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -145,6 +145,19 @@ fortranobject_dep = declare_dependency( ) f2py = find_program('f2py') +# It should be quite rare for the `f2py` executable to not be the one from +# `numpy` installed in the Python env we are building for (unless we are +# cross-compiling). If it is from a different env, that is still fine as long +# as it's not too old. We are only using f2py as a code generator, and the +# output is not dependent on platform or Python version (see gh-20612 for more +# details). +# This should be robust enough. If not, we can make this more complex, using +# a fallback to `python -m f2py` rather than erroring out. +f2py_version = run_command([f2py, '-v'], check: true).stdout().strip() +if f2py_version.version_compare('<'+min_numpy_version) + error(f'Found f2py executable is too old: @f2py_version@') +endif + # Note: cannot handle .pyf.src or targets with #include's (due to no # `depend_files` - see feature request at meson#8295) f2py_gen = generator(generate_f2pymod, From d0ad5aa12fa4ef92a92ad55da23c472f1e2a7031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Fri, 3 May 2024 02:32:18 -0500 Subject: [PATCH 083/500] BUG: prevent QHull message stream being closed twice (#20611) --- scipy/spatial/_qhull.pyx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scipy/spatial/_qhull.pyx b/scipy/spatial/_qhull.pyx index e24f362c5136..143315aba74e 100644 --- a/scipy/spatial/_qhull.pyx +++ b/scipy/spatial/_qhull.pyx @@ -361,7 +361,8 @@ cdef class _Qhull: "qhull: did not free %d bytes (%d pieces)" % (totlong, curlong)) - self._messages.close() + if self._messages is not None: + self._messages.close() @cython.final def close(self): @@ -387,7 +388,8 @@ cdef class _Qhull: "qhull: did not free %d bytes (%d pieces)" % (totlong, curlong)) - self._messages.close() + if self._messages is not None: + self._messages.close() @cython.final def get_points(self): From fc75675e60371729bb4e1ac470df9ababe726ac2 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Fri, 3 May 2024 15:07:34 +0100 Subject: [PATCH 084/500] ENH: stats.sem: add array-API support (#20631) * ENH: stats.sem: add array-API support * Apply suggestions from code review Co-authored-by: Matt Haberland --------- Co-authored-by: Matt Haberland --- scipy/stats/_stats_py.py | 6 ++++-- scipy/stats/tests/test_stats.py | 34 ++++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index fb715e6010a2..2bf56dd555bd 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -67,7 +67,7 @@ from scipy import stats from scipy.optimize import root_scalar from scipy._lib._util import normalize_axis_index -from scipy._lib._array_api import array_namespace, is_numpy +from scipy._lib._array_api import array_namespace, is_numpy, atleast_nd from scipy._lib.array_api_compat import size as xp_size # In __all__ but deprecated for removal in SciPy 1.13.0 @@ -2827,8 +2827,10 @@ def sem(a, axis=0, ddof=1, nan_policy='propagate'): 1.2893796958227628 """ + xp = array_namespace(a) + a = atleast_nd(a, ndim=1, xp=xp) n = a.shape[axis] - s = np.std(a, axis=axis, ddof=ddof) / np.sqrt(n) + s = xp.std(a, axis=axis, correction=ddof) / n**0.5 return s diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 56e2acab88b9..fe640f5aeb11 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -43,6 +43,9 @@ copy, is_numpy, is_torch, SCIPY_ARRAY_API) +skip_xp_backends = pytest.mark.skip_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 @@ -2621,31 +2624,44 @@ def __array__(self, dtype=None, copy=None): with pytest.raises(TypeError, match=message): stats.mode(np.arange(3, dtype=object)) + +@array_api_compatible class TestSEM: - testcase = [1, 2, 3, 4] + testcase = [1., 2., 3., 4.] scalar_testcase = 4. - def test_sem(self): + def test_sem(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)[()] with suppress_warnings() as sup, np.errstate(invalid="ignore"): + # numpy sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice") - y = stats.sem(self.scalar_testcase) - assert_(np.isnan(y)) + # torch + sup.filter(UserWarning, "std*") + y = stats.sem(scalar_testcase) + assert xp.isnan(y) - y = stats.sem(self.testcase) - assert_approx_equal(y, 0.6454972244) + testcase = xp.asarray(self.testcase) + y = stats.sem(testcase) + xp_assert_close(y, xp.asarray(0.6454972244)) n = len(self.testcase) - assert_allclose(stats.sem(self.testcase, ddof=0) * np.sqrt(n/(n-2)), - stats.sem(self.testcase, ddof=2)) + assert_allclose(stats.sem(testcase, ddof=0) * (n/(n-2))**0.5, + stats.sem(testcase, ddof=2)) + x = xp.arange(10.) + x[9] = xp.nan + assert_equal(stats.sem(x), xp.asarray(xp.nan)) + + @skip_xp_backends(np_only=True, + reasons=['`nan_policy` only supports NumPy backend']) + def test_sem_nan_policy(self, xp): x = np.arange(10.) x[9] = np.nan - assert_equal(stats.sem(x), np.nan) assert_equal(stats.sem(x, nan_policy='omit'), 0.9128709291752769) assert_raises(ValueError, stats.sem, x, nan_policy='raise') assert_raises(ValueError, stats.sem, x, nan_policy='foobar') From 1464ae12df417a8c8b6750d86fca4437d1c732c0 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 3 May 2024 07:40:28 -0700 Subject: [PATCH 085/500] ENH: stats.circ___: add array-API support (#20595) * ENH: stats.circ___: add array-API support * TST: stats.circ___: make tests array-API compatible * MAINT: stats.circ___: fix test failures; improvements based on further consideration * MAINT: stats.circ___: more adjustments to fix tests * STY: stats.circ___: more linting whack-a-mole * TST: scipy.optimize: make tests accept nan_policy options in any order * MAINT: _lib._util._get_nan: patch for no data * MAINT: stats.circ___: attempt to fix remaining failures" * TST: cluster: tweak tolerances * TST: _array_api.xp_assert_close: adjust default tolerance * MAINT: stats.circ___: add comment to test, remove unneeded branch in _get_nan --- .github/workflows/array_api.yml | 1 + scipy/_lib/_array_api.py | 16 ++- scipy/_lib/_util.py | 21 +-- scipy/optimize/tests/test_minpack.py | 9 +- scipy/stats/_axis_nan_policy.py | 2 +- scipy/stats/_morestats.py | 78 ++++++----- scipy/stats/tests/test_morestats.py | 196 +++++++++++++-------------- scipy/stats/tests/test_stats.py | 2 +- 8 files changed, 167 insertions(+), 158 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index d046777ac3ca..86282286d3f9 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -97,3 +97,4 @@ jobs: python dev.py --no-build test -b all -t scipy._lib.tests.test_array_api python dev.py --no-build test -b all -t scipy._lib.tests.test__util -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.stats.tests.test_stats -- --durations 3 --timeout=60 + python dev.py --no-build test -b all -t scipy.stats.tests.test_morestats -- --durations 3 --timeout=60 diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 89a8171b58e8..ec53853c9bac 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -148,7 +148,7 @@ def _asarray( `check_finite` is also not a keyword in the array API standard; included here for convenience rather than that having to be a separate function call inside SciPy functions. - + `subok` is included to allow this function to preserve the behaviour of `np.asanyarray` for NumPy based inputs. """ @@ -319,7 +319,6 @@ def xp_assert_close(actual, desired, rtol=None, atol=0, check_namespace=True, # roughly half way between sqrt(eps) and the default for # `numpy.testing.assert_allclose`, 1e-7 rtol = xp.finfo(actual.dtype).eps**0.5 * 4 - elif rtol is None: rtol = 1e-7 @@ -387,3 +386,16 @@ def xp_unsupported_param_msg(param: Any) -> str: def is_complex(x: Array, xp: ModuleType) -> bool: return xp.isdtype(x.dtype, 'complex floating') + + +# temporary substitute for xp.minimum, which is not yet in all backends +# or covered by array_api_compat. +def xp_minimum(x1, x2): + # xp won't be passed in because it doesn't need to be passed in to xp.minimum + xp = array_namespace(x1, x2) + x1, x2 = xp.broadcast_arrays(x1, x2) + dtype = xp.result_type(x1.dtype, x2.dtype) + res = xp.asarray(x1, copy=True, dtype=dtype) + i = (x2 < x1) | xp.isnan(x2) + res[i] = x2[i] + return res diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py index 5a03de16824e..c3811d675f4b 100644 --- a/scipy/_lib/_util.py +++ b/scipy/_lib/_util.py @@ -268,8 +268,8 @@ def check_random_state(seed): if isinstance(seed, (np.random.RandomState, np.random.Generator)): return seed - raise ValueError('%r cannot be used to seed a numpy.random.RandomState' - ' instance' % seed) + raise ValueError(f"'{seed}' cannot be used to seed a numpy.random.RandomState" + " instance") def _asarray_validated(a, check_finite=True, @@ -716,10 +716,9 @@ def _contains_nan(a, nan_policy='propagate', use_summation=True, if not_numpy: use_summation = False # some array_likes ignore nans (e.g. pandas) if policies is None: - policies = ['propagate', 'raise', 'omit'] + policies = {'propagate', 'raise', 'omit'} if nan_policy not in policies: - raise ValueError("nan_policy must be one of {%s}" % - ', '.join("'%s'" % s for s in policies)) + raise ValueError(f"nan_policy must be one of {set(policies)}.") inexact = (xp.isdtype(a.dtype, "real floating") or xp.isdtype(a.dtype, "complex floating")) @@ -815,15 +814,17 @@ def _rng_spawn(rng, n_children): return child_rngs -def _get_nan(*data): +def _get_nan(*data, xp=None): + xp = array_namespace(*data) if xp is None else xp # Get NaN of appropriate dtype for data - data = [np.asarray(item) for item in data] + data = [xp.asarray(item) for item in data] try: - dtype = np.result_type(*data, np.half) # must be a float16 at least + min_float = getattr(xp, 'float16', xp.float32) + dtype = xp.result_type(*data, min_float) # must be at least a float except DTypePromotionError: # fallback to float64 - return np.array(np.nan, dtype=np.float64)[()] - return np.array(np.nan, dtype=dtype)[()] + dtype = xp.float64 + return xp.asarray(xp.nan, dtype=dtype)[()] def normalize_axis_index(axis, ndim): diff --git a/scipy/optimize/tests/test_minpack.py b/scipy/optimize/tests/test_minpack.py index b4d57b34a34e..b040b1e12531 100644 --- a/scipy/optimize/tests/test_minpack.py +++ b/scipy/optimize/tests/test_minpack.py @@ -321,7 +321,7 @@ def test_full_output(self): args=(self.y_meas, self.x), full_output=True) params_fit, cov_x, infodict, mesg, ier = full_output - assert_(ier in (1,2,3,4), 'solution not found: %s' % mesg) + assert_(ier in (1,2,3,4), f'solution not found: {mesg}') def test_input_untouched(self): p0 = array([0,0,0],dtype=float64) @@ -330,7 +330,7 @@ def test_input_untouched(self): args=(self.y_meas, self.x), full_output=True) params_fit, cov_x, infodict, mesg, ier = full_output - assert_(ier in (1,2,3,4), 'solution not found: %s' % mesg) + assert_(ier in (1,2,3,4), f'solution not found: {mesg}') assert_array_equal(p0, p0_copy) def test_wrong_shape_func_callable(self): @@ -602,8 +602,9 @@ def _check_nan_policy(f, xdata_with_nan, xdata_without_nan, assert_allclose(result_with_nan, result_without_nan) # not valid policy test - error_msg = ("nan_policy must be one of " - "{'None', 'raise', 'omit'}") + # check for argument names in any order + error_msg = (r"nan_policy must be one of \{(?:'raise'|'omit'|None)" + r"(?:, ?(?:'raise'|'omit'|None))*\}") with assert_raises(ValueError, match=error_msg): curve_fit(**kwargs, nan_policy="hi") diff --git a/scipy/stats/_axis_nan_policy.py b/scipy/stats/_axis_nan_policy.py index 7e6d8d2d66bb..f8155b04aca2 100644 --- a/scipy/stats/_axis_nan_policy.py +++ b/scipy/stats/_axis_nan_policy.py @@ -511,7 +511,7 @@ def hypotest_fun_out(*samples, **kwds): samples = [sample.reshape(new_shape) for sample, new_shape in zip(samples, new_shapes)] axis = -1 # work over the last axis - NaN = _get_nan(*samples) + NaN = _get_nan(*samples) if samples else np.nan # if axis is not needed, just handle nan_policy and return ndims = np.array([sample.ndim for sample in samples]) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 951593a49e9b..a22e4e3c321f 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -7,11 +7,12 @@ from numpy import (isscalar, r_, log, around, unique, asarray, zeros, arange, sort, amin, amax, sqrt, array, atleast_1d, # noqa: F401 compress, pi, exp, ravel, count_nonzero, sin, cos, # noqa: F401 - arctan2, hypot) + arctan2, hypot) # noqa: F401 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._array_api import array_namespace, xp_minimum, size as xp_size from ._ansari_swilk_statistics import gscale, swilk from . import _stats_py, _wilcoxon @@ -130,8 +131,7 @@ def bayes_mvs(data, alpha=0.90): """ m, v, s = mvsdist(data) if alpha >= 1 or alpha <= 0: - raise ValueError("0 < alpha < 1 is required, but alpha=%s was given." - % alpha) + 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)) @@ -454,7 +454,7 @@ def _parse_dist_kw(dist, enforce_subclass=True): try: dist = getattr(distributions, dist) except AttributeError as e: - raise ValueError("%s is not a valid distribution name" % dist) from 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.") @@ -831,7 +831,7 @@ def ppcc_plot(x, a, b, dist='tukeylambda', plot=None, N=80): plot.plot(svals, ppcc, 'x') _add_axis_labels_title(plot, xlabel='Shape Values', ylabel='Prob Plot Corr. Coef.', - title='(%s) PPCC Plot' % dist) + title=f'({dist}) PPCC Plot') return svals, ppcc @@ -1323,7 +1323,7 @@ def _all(x): 'mle': _mle, 'all': _all} if method not in methods.keys(): - raise ValueError("Method %s not recognized." % method) + raise ValueError(f"Method {method} not recognized.") optimfunc = methods[method] @@ -4311,11 +4311,9 @@ def median_test(*samples, ties='below', correction=True, lambda_=1, # a zero in the table of expected frequencies. rowsums = table.sum(axis=1) if rowsums[0] == 0: - raise ValueError("All values are below the grand median (%r)." % - grand_median) + raise ValueError(f"All values are below the grand median ({grand_median}).") if rowsums[1] == 0: - raise ValueError("All values are above the grand median (%r)." % - grand_median) + 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` @@ -4333,16 +4331,21 @@ def median_test(*samples, ties='below', correction=True, lambda_=1, return MedianTestResult(stat, p, grand_median, table) -def _circfuncs_common(samples, high, low): +def _circfuncs_common(samples, high, low, xp=None): + xp = array_namespace(samples) if xp is None else xp # Ensure samples are array-like and size is not zero - if samples.size == 0: - NaN = _get_nan(samples) + if xp_size(samples) == 0: + NaN = _get_nan(samples, xp=xp) return NaN, NaN, NaN + if xp.isdtype(samples.dtype, 'integral'): + dtype = xp.asarray(1.).dtype # get default float type + samples = xp.asarray(samples, dtype=dtype) + # Recast samples as radians that range between 0 and 2 pi and calculate # the sine and cosine - sin_samp = sin((samples - low)*2.*pi / (high - low)) - cos_samp = cos((samples - low)*2.*pi / (high - low)) + sin_samp = xp.sin((samples - low)*2.*xp.pi / (high - low)) + cos_samp = xp.cos((samples - low)*2.*xp.pi / (high - low)) return samples, sin_samp, cos_samp @@ -4403,16 +4406,17 @@ def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): >>> plt.show() """ - samples, sin_samp, cos_samp = _circfuncs_common(samples, high, low) - sin_sum = sin_samp.sum(axis) - cos_sum = cos_samp.sum(axis) - res = arctan2(sin_sum, cos_sum) + xp = array_namespace(samples) + samples, sin_samp, cos_samp = _circfuncs_common(samples, high, low, 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 = np.asarray(res) - res[res < 0] += 2*pi - res = res[()] + res = xp.asarray(res) + res[res < 0] += 2*xp.pi + res = res[()] if res.ndim == 0 else res - return res*(high - low)/2.0/pi + low + return res*(high - low)/2.0/xp.pi + low @_axis_nan_policy_factory( @@ -4482,12 +4486,14 @@ def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): >>> plt.show() """ - samples, sin_samp, cos_samp = _circfuncs_common(samples, high, low) - sin_mean = sin_samp.mean(axis) - cos_mean = cos_samp.mean(axis) - # hypot can go slightly above 1 due to rounding errors + xp = array_namespace(samples) + samples, sin_samp, cos_samp = _circfuncs_common(samples, high, low, 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 with np.errstate(invalid='ignore'): - R = np.minimum(1, hypot(sin_mean, cos_mean)) + R = xp_minimum(xp.asarray(1.), hypotenuse) res = 1. - R return res @@ -4579,16 +4585,18 @@ def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, >>> plt.show() """ - samples, sin_samp, cos_samp = _circfuncs_common(samples, high, low) - sin_mean = sin_samp.mean(axis) # [1] (2.2.3) - cos_mean = cos_samp.mean(axis) # [1] (2.2.3) - # hypot can go slightly above 1 due to rounding errors + xp = array_namespace(samples) + samples, sin_samp, cos_samp = _circfuncs_common(samples, high, low, 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 with np.errstate(invalid='ignore'): - R = np.minimum(1, hypot(sin_mean, cos_mean)) # [1] (2.2.4) + R = xp_minimum(xp.asarray(1.), hypotenuse) # [1] (2.2.4) - res = sqrt(-2*log(R)) + res = xp.sqrt(-2*xp.log(R)) if not normalize: - res *= (high-low)/(2.*pi) # [1] (2.3.14) w/ (2.3.7) + res *= (high-low)/(2.*xp.pi) # [1] (2.3.14) w/ (2.3.7) return res diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 9a6df9b55e4a..7d5e82998c3c 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -21,7 +21,9 @@ from .._hypotests import _get_wilcoxon_distr, _get_wilcoxon_distr2 from scipy.stats._binomtest import _binary_search_for_binom_tst from scipy.stats._distr_params import distcont -from scipy._lib._array_api import SCIPY_ARRAY_API +from scipy.conftest import array_api_compatible +from scipy._lib._array_api import (array_namespace, xp_assert_close, xp_assert_less, + SCIPY_ARRAY_API, xp_assert_equal) distcont = dict(distcont) # type: ignore @@ -2449,6 +2451,7 @@ def test_darwin_example(self): assert np.allclose(lmbda, 1.305, atol=1e-3) +@array_api_compatible class TestCircFuncs: # In gh-5747, the R package `circular` was used to calculate reference # values for the circular variance, e.g.: @@ -2458,108 +2461,84 @@ class TestCircFuncs: # var.circular(x) @pytest.mark.parametrize("test_func,expected", [(stats.circmean, 0.167690146), - (stats.circvar, 0.006455174270186603), + (stats.circvar, 0.006455174000787767), (stats.circstd, 6.520702116)]) - def test_circfuncs(self, test_func, expected): - x = np.array([355, 5, 2, 359, 10, 350]) - assert_allclose(test_func(x, high=360), expected, rtol=1e-7) - - def test_circfuncs_small(self): - x = np.array([20, 21, 22, 18, 19, 20.5, 19.2]) - M1 = x.mean() + 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) - assert_allclose(M2, M1, rtol=1e-5) + xp_assert_close(M2, M1, rtol=1e-5) - V1 = (x*np.pi/180).var() + # plain torch var/std ddof=1, so we need array_api_compat torch + xp_test = array_namespace(x) + V1 = xp_test.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) - assert_allclose(V2, V1, rtol=1e-4) + xp_assert_close(V2, V1, rtol=1e-4) - S1 = x.std() + S1 = xp_test.std(x, correction=0) S2 = stats.circstd(x, high=360) - assert_allclose(S2, S1, rtol=1e-4) + 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): + def test_circfuncs_close(self, test_func, numpy_func, xp): # circfuncs should handle very similar inputs (gh-12740) - x = np.array([0.12675364631578953] * 10 + [0.12675365920187928] * 100) - circstat = test_func(x) - normal = numpy_func(x) - assert_allclose(circstat, normal, atol=2e-8) - - def test_circmean_axis(self): - x = np.array([[355, 5, 2, 359, 10, 350], - [351, 7, 4, 352, 9, 349], - [357, 9, 8, 358, 4, 356]]) - M1 = stats.circmean(x, high=360) - M2 = stats.circmean(x.ravel(), high=360) - assert_allclose(M1, M2, rtol=1e-14) - - M1 = stats.circmean(x, high=360, axis=1) - M2 = [stats.circmean(x[i], high=360) for i in range(x.shape[0])] - assert_allclose(M1, M2, rtol=1e-14) - - M1 = stats.circmean(x, high=360, axis=0) - M2 = [stats.circmean(x[:, i], high=360) for i in range(x.shape[1])] - assert_allclose(M1, M2, rtol=1e-14) - - def test_circvar_axis(self): - x = np.array([[355, 5, 2, 359, 10, 350], - [351, 7, 4, 352, 9, 349], - [357, 9, 8, 358, 4, 356]]) - - V1 = stats.circvar(x, high=360) - V2 = stats.circvar(x.ravel(), high=360) - assert_allclose(V1, V2, rtol=1e-11) - - V1 = stats.circvar(x, high=360, axis=1) - V2 = [stats.circvar(x[i], high=360) for i in range(x.shape[0])] - assert_allclose(V1, V2, rtol=1e-11) - - V1 = stats.circvar(x, high=360, axis=0) - V2 = [stats.circvar(x[:, i], high=360) for i in range(x.shape[1])] - assert_allclose(V1, V2, rtol=1e-11) - - def test_circstd_axis(self): - x = np.array([[355, 5, 2, 359, 10, 350], - [351, 7, 4, 352, 9, 349], - [357, 9, 8, 358, 4, 356]]) - - S1 = stats.circstd(x, high=360) - S2 = stats.circstd(x.ravel(), high=360) - assert_allclose(S1, S2, rtol=1e-11) - - S1 = stats.circstd(x, high=360, axis=1) - S2 = [stats.circstd(x[i], high=360) for i in range(x.shape[0])] - assert_allclose(S1, S2, rtol=1e-11) - - S1 = stats.circstd(x, high=360, axis=0) - S2 = [stats.circstd(x[:, i], high=360) for i in range(x.shape[1])] - assert_allclose(S1, S2, rtol=1e-11) + 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, xp, circfunc): + 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.asarray(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.asarray(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): - x = [355, 5, 2, 359, 10, 350] - assert_allclose(test_func(x, high=360), expected, rtol=1e-7) + 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): - assert_(np.isnan(test_func([]))) + def test_empty(self, test_func, xp): + dtype = xp.float64 + x = xp.asarray([], dtype=dtype) + xp_assert_equal(test_func(x), xp.asarray(xp.nan, dtype=dtype)) @pytest.mark.parametrize("test_func", [stats.circmean, stats.circvar, stats.circstd]) - def test_nan_propagate(self, test_func): - x = [355, 5, 2, 359, 10, 350, np.nan] - assert_(np.isnan(test_func(x, high=360))) + 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, @@ -2570,18 +2549,46 @@ def test_nan_propagate(self, test_func): 1: 0.005545914017677123}), (stats.circstd, {None: np.nan, 0: 4.11093193, 1: 6.04265394})]) - def test_nan_propagate_array(self, test_func, expected): - x = np.array([[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]]) + 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: - assert_(np.isnan(out)) + xp_assert_equal(out, xp.asarray(xp.nan)) else: - assert_allclose(out[0], expected[axis], rtol=1e-7) - assert_(np.isnan(out[1:]).all()) + 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)) + + +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, @@ -2655,27 +2662,6 @@ def test_nan_raise(self, test_func, x): def test_bad_nan_policy(self, test_func, x): assert_raises(ValueError, test_func, x, high=360, nan_policy='foobar') - def test_circmean_scalar(self): - x = 1. - M1 = x - M2 = stats.circmean(x) - assert_allclose(M2, M1, rtol=1e-5) - - def test_circmean_range(self): - # regression test for gh-6420: circmean(..., high, low) must be - # between `high` and `low` - m = stats.circmean(np.arange(0, 2, 0.1), np.pi, -np.pi) - assert_(m < np.pi) - assert_(m > -np.pi) - - def test_circfuncs_uint8(self): - # regression test for gh-7255: overflow when working with - # numpy uint8 data type - x = np.array([150, 10], dtype='uint8') - assert_equal(stats.circmean(x, high=180), 170.0) - assert_allclose(stats.circvar(x, high=180), 0.2339555554617, rtol=1e-7) - assert_allclose(stats.circstd(x, high=180), 20.91551378, rtol=1e-7) - class TestMedianTest: diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index fe640f5aeb11..30276e1dcbfc 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -182,7 +182,7 @@ def test_tmin(self): assert_equal(stats.tmin(x, nan_policy='omit'), 0.) assert_raises(ValueError, stats.tmin, x, nan_policy='raise') assert_raises(ValueError, stats.tmin, x, nan_policy='foobar') - msg = "'propagate', 'raise', 'omit'" + msg = "nan_policy must be one of..." with assert_raises(ValueError, match=msg): stats.tmin(x, nan_policy='foo') From 59a03cf48cdaaab05cbd55fa30eddb12ea3095ad Mon Sep 17 00:00:00 2001 From: Christian Veenhuis <124370897+ChVeen@users.noreply.github.com> Date: Fri, 3 May 2024 20:33:36 +0200 Subject: [PATCH 086/500] Fix links in datasets._fetchers [skip ci] --- scipy/datasets/_fetchers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scipy/datasets/_fetchers.py b/scipy/datasets/_fetchers.py index 51dfbc4498f9..f273b9eb826d 100644 --- a/scipy/datasets/_fetchers.py +++ b/scipy/datasets/_fetchers.py @@ -36,8 +36,8 @@ def ascent(): Get an 8-bit grayscale bit-depth, 512 x 512 derived image for easy use in demos. - The image is derived from accent-to-the-top.jpg at - http://www.public-domain-image.com/people-public-domain-images-pictures/ + The image is derived from + https://pixnio.com/people/accent-to-the-top Parameters ---------- @@ -178,7 +178,8 @@ def face(gray=False): """ Get a 1024 x 768, color image of a raccoon face. - raccoon-procyon-lotor.jpg at http://www.public-domain-image.com + The image is derived from + https://pixnio.com/fauna-animals/raccoons/raccoon-procyon-lotor Parameters ---------- From 9c9122ab845d9910b83fa72e967ed6cc86996c0b Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 3 May 2024 13:03:58 -0700 Subject: [PATCH 087/500] DOC: stats.linregress: split stats/mstats documentation (#20547) --- scipy/stats/_mstats_basic.py | 105 +++++++++++++++++++++++++++- scipy/stats/_stats_mstats_common.py | 11 ++- scipy/stats/tests/test_stats.py | 2 +- 3 files changed, 107 insertions(+), 11 deletions(-) diff --git a/scipy/stats/_mstats_basic.py b/scipy/stats/_mstats_basic.py index 6b2d46b271d0..c18c1b066d1b 100644 --- a/scipy/stats/_mstats_basic.py +++ b/scipy/stats/_mstats_basic.py @@ -1043,10 +1043,109 @@ def pointbiserialr(x, y): def linregress(x, y=None): r""" - Linear regression calculation + Calculate a linear least-squares regression for two sets of measurements. - Note that the non-masked version is used, and that this docstring is - replaced by the non-masked docstring + some info on missing data. + 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: diff --git a/scipy/stats/_stats_mstats_common.py b/scipy/stats/_stats_mstats_common.py index 35e426ec2e31..e4ffd9ca58b8 100644 --- a/scipy/stats/_stats_mstats_common.py +++ b/scipy/stats/_stats_mstats_common.py @@ -3,7 +3,7 @@ from . import distributions from .._lib._bunch import _make_tuple_bunch from ._stats_pythran import siegelslopes as siegelslopes_pythran -from . import _mstats_basic +from scipy.stats import _stats_py __all__ = ['_find_repeats', 'linregress', 'theilslopes', 'siegelslopes'] @@ -26,11 +26,11 @@ def linregress(x, y=None, alternative='two-sided'): Parameters ---------- x, y : array_like - Two sets of measurements. Both arrays should have the same length. If + 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 2x2 array, ``linregress(x)`` is + the case where ``y=None`` and `x` is a 2xN array, ``linregress(x)`` is equivalent to ``linregress(x[0], x[1])``. alternative : {'two-sided', 'less', 'greater'}, optional Defines the alternative hypothesis. Default is 'two-sided'. @@ -75,9 +75,6 @@ def linregress(x, y=None, alternative='two-sided'): 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:: @@ -194,7 +191,7 @@ def linregress(x, y=None, alternative='two-sided'): # n-2 degrees of freedom because 2 has been used up # to estimate the mean and standard deviation t = r * np.sqrt(df / ((1.0 - r + TINY)*(1.0 + r + TINY))) - t, prob = _mstats_basic._ttest_finish(df, t, alternative) + prob = _stats_py._get_pvalue(t, distributions.t(df), alternative) slope_stderr = np.sqrt((1 - r**2) * ssym / ssxm / df) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 30276e1dcbfc..4e8386fadeaa 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -2020,7 +2020,7 @@ def test_regress_alternative(self): y = 0.2 * np.linspace(0, 100, 100) + 10 # slope is greater than zero y += np.sin(np.linspace(0, 20, 100)) - with pytest.raises(ValueError, match="alternative must be 'less'..."): + with pytest.raises(ValueError, match="`alternative` must be 'less'..."): stats.linregress(x, y, alternative="ekki-ekki") res1 = stats.linregress(x, y, alternative="two-sided") From ad538ec8eab4d835811ad4c3f6df24c812d8115b Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 3 May 2024 17:59:39 -0700 Subject: [PATCH 088/500] TST: adjust new array API test, slow tests --- scipy/linalg/tests/test_extending.py | 2 +- scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py | 2 +- scipy/stats/tests/test_hypotests.py | 2 +- scipy/stats/tests/test_stats.py | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scipy/linalg/tests/test_extending.py b/scipy/linalg/tests/test_extending.py index 56b22b2a1744..e6ea695b33b7 100644 --- a/scipy/linalg/tests/test_extending.py +++ b/scipy/linalg/tests/test_extending.py @@ -9,7 +9,7 @@ from scipy.linalg.lapack import dgtsv # type: ignore[attr-defined] -@pytest.mark.fail_slow(30) +@pytest.mark.fail_slow(60) # essential per https://github.com/scipy/scipy/pull/20487#discussion_r1567057247 @pytest.mark.skipif(IS_EDITABLE, reason='Editable install cannot find .pxd headers.') diff --git a/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py b/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py index 3fe07ba31296..38b5e3833588 100644 --- a/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py +++ b/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py @@ -632,7 +632,7 @@ def Mf32precond(x): # 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.95: + 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) diff --git a/scipy/stats/tests/test_hypotests.py b/scipy/stats/tests/test_hypotests.py index 45d871298c77..516d5b080b25 100644 --- a/scipy/stats/tests/test_hypotests.py +++ b/scipy/stats/tests/test_hypotests.py @@ -1353,7 +1353,7 @@ def test_exact_pvalue(self, statistic, m, n, pval): # The values are taken from Table 2, 3, 4 and 5 assert_equal(_pval_cvm_2samp_exact(statistic, m, n), pval) - @pytest.mark.slow + @pytest.mark.xslow def test_large_sample(self): # for large samples, the statistic U gets very large # do a sanity check that p-value is not 0, 1 or nan diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 4e8386fadeaa..b0b3bc4c574d 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -2650,13 +2650,13 @@ def test_sem(self, xp): y = stats.sem(testcase) xp_assert_close(y, xp.asarray(0.6454972244)) n = len(self.testcase) - assert_allclose(stats.sem(testcase, ddof=0) * (n/(n-2))**0.5, + xp_assert_close(stats.sem(testcase, ddof=0) * (n/(n-2))**0.5, stats.sem(testcase, ddof=2)) x = xp.arange(10.) x[9] = xp.nan - assert_equal(stats.sem(x), xp.asarray(xp.nan)) - + xp_assert_equal(stats.sem(x), xp.asarray(xp.nan)) + @skip_xp_backends(np_only=True, reasons=['`nan_policy` only supports NumPy backend']) def test_sem_nan_policy(self, xp): From aa257e0debe51179b5c303bea2405eac478d1431 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 4 May 2024 07:00:48 -0700 Subject: [PATCH 089/500] TST: stats.ttest_1samp: fix xslow test [skip ci] --- scipy/stats/tests/test_stats.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index b0b3bc4c574d..483c0a40124b 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -3711,8 +3711,8 @@ def test_1samp_ci_iv(self, xp): 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 = data.astype(copy=True) # ensure byte order - data = xp.asarray(data) + data = data.astype(np.float64, copy=True) # ensure byte order + data = xp.asarray(data, dtype=xp.float64) res = stats.ttest_1samp(data, 0., alternative=alternative, axis=axis) l, u = res.confidence_interval(confidence_level=alpha) @@ -3722,7 +3722,7 @@ def test_pvalue_ci(self, alpha, data_axis, alternative, xp): res = stats.ttest_1samp(data, popmean, alternative=alternative, axis=axis) shape = list(data.shape) shape.pop(axis) - ref = xp.broadcast_to(xp.asarray(1-alpha), shape) + ref = xp.broadcast_to(xp.asarray(1-alpha, dtype=xp.float64), shape) xp_assert_close(res.pvalue, ref) From adf1f0e3cb69aaf451eb636116fe1d5b2a99c736 Mon Sep 17 00:00:00 2001 From: Nick ODell Date: Mon, 29 Apr 2024 23:02:04 +0000 Subject: [PATCH 090/500] ENH: Reduce memory usage during approx_derivative Currently, the code generates all possible h vectors at once. When optimizing N dimensions, this uses a quadratic amount of memory, which is wasteful. Reduce this by computing those vectors one at a time. --- scipy/optimize/_numdiff.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/scipy/optimize/_numdiff.py b/scipy/optimize/_numdiff.py index d6bd0d37a460..0db1b8df68af 100644 --- a/scipy/optimize/_numdiff.py +++ b/scipy/optimize/_numdiff.py @@ -581,31 +581,37 @@ def _dense_difference(fun, x0, f0, h, use_one_sided, method): m = f0.size n = x0.size J_transposed = np.empty((n, m)) - h_vecs = np.diag(h) for i in range(h.size): if method == '2-point': - x = x0 + h_vecs[i] + x = x0.copy() + x[i] += h[i] dx = x[i] - x0[i] # Recompute dx as exactly representable number. df = fun(x) - f0 elif method == '3-point' and use_one_sided[i]: - x1 = x0 + h_vecs[i] - x2 = x0 + 2 * h_vecs[i] + x1 = x0.copy() + x2 = x0.copy() + x1[i] += h[i] + x2[i] += 2 * h[i] dx = x2[i] - x0[i] f1 = fun(x1) f2 = fun(x2) df = -3.0 * f0 + 4 * f1 - f2 elif method == '3-point' and not use_one_sided[i]: - x1 = x0 - h_vecs[i] - x2 = x0 + h_vecs[i] + x1 = x0.copy() + x1[i] -= h[i] + x2 = x0.copy() + x2[i] += h[i] dx = x2[i] - x1[i] f1 = fun(x1) f2 = fun(x2) df = f2 - f1 elif method == 'cs': - f1 = fun(x0 + h_vecs[i]*1.j) + x1 = x0.astype(complex, copy=True) + x1[i] += h[i] * 1.j + f1 = fun(x1) df = f1.imag - dx = h_vecs[i, i] + dx = h[i] else: raise RuntimeError("Never be here.") From e1a4c36ef0205f520a626c36d9a71fa3255fd565 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 4 May 2024 18:30:12 -0600 Subject: [PATCH 091/500] BUG: value_indices unfixed types * Fixes gh-19423 * Add a few more `case` statements to account for the (i.e., Windows) data types that don't have a fixed width, and add a regression test. [skip circle] [skip cirrus] --- scipy/ndimage/src/nd_image.c | 20 ++++++++++++++++++++ scipy/ndimage/tests/test_measurements.py | 10 ++++++++++ 2 files changed, 30 insertions(+) diff --git a/scipy/ndimage/src/nd_image.c b/scipy/ndimage/src/nd_image.c index 5f31e5acc911..0190855ac032 100644 --- a/scipy/ndimage/src/nd_image.c +++ b/scipy/ndimage/src/nd_image.c @@ -999,6 +999,11 @@ static PyObject *NI_ValueIndices(PyObject *self, PyObject *args) case NPY_UINT32: CASE_VALUEINDICES_SET_MINMAX(npy_uint32); break; case NPY_INT64: CASE_VALUEINDICES_SET_MINMAX(npy_int64); break; case NPY_UINT64: CASE_VALUEINDICES_SET_MINMAX(npy_uint64); break; + default: + switch(arrType) { + case NPY_UINT: CASE_VALUEINDICES_SET_MINMAX(npy_uint); break; + case NPY_INT: CASE_VALUEINDICES_SET_MINMAX(npy_int); break; + } } NI_ITERATOR_NEXT(ndiIter, arrData); } @@ -1016,6 +1021,11 @@ static PyObject *NI_ValueIndices(PyObject *self, PyObject *args) case NPY_UINT32: CASE_VALUEINDICES_MAKEHISTOGRAM(npy_uint32); break; case NPY_INT64: CASE_VALUEINDICES_MAKEHISTOGRAM(npy_int64); break; case NPY_UINT64: CASE_VALUEINDICES_MAKEHISTOGRAM(npy_uint64); break; + default: + switch(arrType) { + case NPY_INT: CASE_VALUEINDICES_MAKEHISTOGRAM(npy_int); break; + case NPY_UINT: CASE_VALUEINDICES_MAKEHISTOGRAM(npy_uint); break; + } } } @@ -1047,6 +1057,11 @@ static PyObject *NI_ValueIndices(PyObject *self, PyObject *args) case NPY_UINT32: CASE_VALUEINDICES_MAKE_VALUEOBJ_FROMOFFSET(npy_uint32, ii); break; case NPY_INT64: CASE_VALUEINDICES_MAKE_VALUEOBJ_FROMOFFSET(npy_int64, ii); break; case NPY_UINT64: CASE_VALUEINDICES_MAKE_VALUEOBJ_FROMOFFSET(npy_uint64, ii); break; + default: + switch(arrType) { + case NPY_INT: CASE_VALUEINDICES_MAKE_VALUEOBJ_FROMOFFSET(npy_int, ii); break; + case NPY_UINT: CASE_VALUEINDICES_MAKE_VALUEOBJ_FROMOFFSET(npy_uint, ii); break; + } } /* Create a tuple of index arrays */ t = PyTuple_New(ndim); @@ -1093,6 +1108,11 @@ static PyObject *NI_ValueIndices(PyObject *self, PyObject *args) case NPY_UINT32: CASE_VALUEINDICES_GET_VALUEOFFSET(npy_uint32); break; case NPY_INT64: CASE_VALUEINDICES_GET_VALUEOFFSET(npy_int64); break; case NPY_UINT64: CASE_VALUEINDICES_GET_VALUEOFFSET(npy_uint64); break; + default: + switch(arrType) { + case NPY_INT: CASE_VALUEINDICES_GET_VALUEOFFSET(npy_int); break; + case NPY_UINT: CASE_VALUEINDICES_GET_VALUEOFFSET(npy_uint); break; + } } if (ignoreValIsNone || (!valueIsIgnore)) { diff --git a/scipy/ndimage/tests/test_measurements.py b/scipy/ndimage/tests/test_measurements.py index 135e9a72c941..a55b1a601434 100644 --- a/scipy/ndimage/tests/test_measurements.py +++ b/scipy/ndimage/tests/test_measurements.py @@ -10,6 +10,7 @@ assert_equal, suppress_warnings, ) +import pytest from pytest import raises as assert_raises import scipy.ndimage as ndimage @@ -1407,3 +1408,12 @@ def test_watershed_ift09(self): expected = [[1, 1], [1, 1]] assert_allclose(out, expected) + + +@pytest.mark.parametrize("dt", [np.intc, np.uintc]) +def test_gh_19423(dt): + rng = np.random.default_rng(123) + max_val = 8 + image = rng.integers(low=0, high=max_val, size=(10, 12)).astype(dtype=dt) + val_idx = ndimage.value_indices(image) + assert len(val_idx.keys()) == max_val From 43db12f41f94f9d62ff1c7432aaad6b19a6c4b39 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 5 May 2024 11:00:13 -0700 Subject: [PATCH 092/500] TST: stats.ttest_1samp: adjust float generation strategy [skip ci] --- scipy/stats/tests/test_stats.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 483c0a40124b..676ef93d82e2 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -3559,10 +3559,12 @@ def test_kurtosis_constant_value(self): @hypothesis.strategies.composite def ttest_data_axis_strategy(draw): # draw an array under shape and value constraints - dtype = npst.floating_dtypes() elements = dict(allow_nan=False, allow_infinity=False) shape = npst.array_shapes(min_dims=1, min_side=2) - data = draw(npst.arrays(dtype=dtype, elements=elements, shape=shape)) + # 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 = [] @@ -3711,8 +3713,7 @@ def test_1samp_ci_iv(self, xp): 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 = data.astype(np.float64, copy=True) # ensure byte order - data = xp.asarray(data, dtype=xp.float64) + data = xp.asarray(data) res = stats.ttest_1samp(data, 0., alternative=alternative, axis=axis) l, u = res.confidence_interval(confidence_level=alpha) @@ -3722,6 +3723,7 @@ def test_pvalue_ci(self, alpha, data_axis, alternative, xp): 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) From 9ee34f9da7000b7988681a9f5b9ede4198620f57 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 5 May 2024 11:33:09 -0700 Subject: [PATCH 093/500] ENH: stats.monte_carlo_test: add array API support (#20604) * MAINT: stats._broadcast_arrays: add array API support * ENH: stats.monte_carlo_test: add array-API support to input validation * ENH: stats.monte_carlo_test: add array-API support * TST: stats.monte_carlo_test: convert input validation test to array API * TST: stats.monte_carlo_test: make test_axis array-API compatible * TST: stats.monte_carlo_test: array-API tests for batch and alternative * MAINT: stats.monte_carlo_test: preserve dtype * Update scipy/stats/tests/test_resampling.py * TST: stats.monte_carlo_test: comment on why xp_test is needed --- .github/workflows/array_api.yml | 1 + scipy/_lib/_array_api.py | 22 +++- scipy/stats/_axis_nan_policy.py | 23 ++-- scipy/stats/_resampling.py | 73 +++++++---- scipy/stats/_stats_py.py | 19 +-- scipy/stats/tests/test_resampling.py | 183 +++++++++++++++++++-------- 6 files changed, 215 insertions(+), 106 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 86282286d3f9..3b5c319a417d 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -98,3 +98,4 @@ jobs: python dev.py --no-build test -b all -t scipy._lib.tests.test__util -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.stats.tests.test_stats -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.stats.tests.test_morestats -- --durations 3 --timeout=60 + python dev.py --no-build test -b all -t scipy.stats.tests.test_resampling -- --durations 3 --timeout=60 diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index ec53853c9bac..f451fdae1e2a 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -398,4 +398,24 @@ def xp_minimum(x1, x2): res = xp.asarray(x1, copy=True, dtype=dtype) i = (x2 < x1) | xp.isnan(x2) res[i] = x2[i] - return res + return res[()] if res.ndim == 0 else res + + +# temporary substitute for xp.clip, which is not yet in all backends +# or covered by array_api_compat. +def xp_clip(x, a, b, xp=None): + xp = array_namespace(xp) if xp is None else xp + y = xp.asarray(x, copy=True) + y[y < a] = a + y[y > b] = b + return y[()] if y.ndim == 0 else y + + +# temporary substitute for xp.moveaxis, which is not yet in all backends +# or covered by array_api_compat. +def _move_axis_to_end(x, source, xp=None): + xp = array_namespace(xp) if xp is None else xp + axes = list(range(x.ndim)) + temp = axes.pop(source) + axes = axes + [temp] + return xp.permute_dims(x, axes) diff --git a/scipy/stats/_axis_nan_policy.py b/scipy/stats/_axis_nan_policy.py index f8155b04aca2..d504e426b155 100644 --- a/scipy/stats/_axis_nan_policy.py +++ b/scipy/stats/_axis_nan_policy.py @@ -12,25 +12,20 @@ import inspect -def _broadcast_arrays(arrays, axis=None): +def _broadcast_arrays(arrays, axis=None, xp=None): """ Broadcast shapes of arrays, ignoring incompatibility of specified axes """ - new_shapes = _broadcast_array_shapes(arrays, axis=axis) + 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 [np.broadcast_to(array, new_shape) + return [xp.broadcast_to(array, new_shape) for array, new_shape in zip(arrays, new_shapes)] -def _broadcast_array_shapes(arrays, axis=None): - """ - Broadcast shapes of arrays, ignoring incompatibility of specified axes - """ - shapes = [np.asarray(arr).shape for arr in arrays] - return _broadcast_shapes(shapes, axis) - - def _broadcast_shapes(shapes, axis=None): """ Broadcast shapes, ignoring incompatibility of specified axes @@ -103,10 +98,10 @@ def _broadcast_array_shapes_remove_axis(arrays, axis=None): Examples -------- >>> import numpy as np - >>> from scipy.stats._axis_nan_policy import _broadcast_array_shapes + >>> 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((a, b), 1) + >>> _broadcast_array_shapes_remove_axis((a, b), 1) (5, 3) """ # Note that here, `axis=None` means do not consume/drop any axes - _not_ @@ -119,7 +114,7 @@ def _broadcast_shapes_remove_axis(shapes, axis=None): """ Broadcast shapes, dropping specified axes - Same as _broadcast_array_shapes, but given a sequence + 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) diff --git a/scipy/stats/_resampling.py b/scipy/stats/_resampling.py index e45cd5733637..4de12960a9a0 100644 --- a/scipy/stats/_resampling.py +++ b/scipy/stats/_resampling.py @@ -4,12 +4,14 @@ import numpy as np from itertools import combinations, permutations, product from collections.abc import Sequence +from dataclasses import dataclass import inspect -from scipy._lib._util import check_random_state, _rename_parameter +from scipy._lib._util import check_random_state, _rename_parameter, rng_integers +from scipy._lib._array_api import (array_namespace, is_numpy, xp_minimum, + xp_clip, _move_axis_to_end) from scipy.special import ndtr, ndtri, comb, factorial -from scipy._lib._util import rng_integers -from dataclasses import dataclass + from ._common import ConfidenceInterval from ._axis_nan_policy import _broadcast_concatenate, _broadcast_arrays from ._warnings_errors import DegenerateDataWarning @@ -662,7 +664,6 @@ def percentile_fun(a, q): 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.") @@ -677,26 +678,45 @@ def _monte_carlo_test_iv(data, rvs, statistic, vectorized, n_resamples, 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): - message = "If `rvs` is a sequence, `len(rvs)` must equal `len(data)`." raise ValueError(message) if not callable(statistic): raise TypeError("`statistic` must be callable.") if vectorized is None: - vectorized = 'axis' in inspect.signature(statistic).parameters + 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) if not vectorized: - statistic_vectorized = _vectorize_statistic(statistic) + 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) + data = _broadcast_arrays(data, axis, xp=xp) data_iv = [] for sample in data: - sample = np.atleast_1d(sample) - sample = np.moveaxis(sample, axis_int, -1) + sample = xp.broadcast_to(sample, (1,)) if sample.ndim == 0 else sample + sample = _move_axis_to_end(sample, axis_int, xp=xp) data_iv.append(sample) n_resamples_int = int(n_resamples) @@ -715,8 +735,12 @@ def _monte_carlo_test_iv(data, rvs, statistic, vectorized, n_resamples, if alternative not in alternatives: raise ValueError(f"`alternative` must be in {alternatives}") + # Infer the desired p-value dtype based on the input types + min_float = getattr(xp, 'float16', xp.float32) + dtype = xp.result_type(*data_iv, min_float) + return (data_iv, rvs, statistic_vectorized, vectorized, n_resamples_int, - batch_iv, alternative, axis_int) + batch_iv, alternative, axis_int, dtype, xp) @dataclass @@ -908,11 +932,12 @@ def monte_carlo_test(data, rvs, statistic, *, vectorized=None, """ args = _monte_carlo_test_iv(data, rvs, statistic, vectorized, n_resamples, batch, alternative, axis) - (data, rvs, statistic, vectorized, - n_resamples, batch, alternative, axis) = args + (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 = np.asarray(statistic(*data, axis=-1))[()] + 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 @@ -922,29 +947,31 @@ def monte_carlo_test(data, rvs, statistic, *, vectorized=None, 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 = np.concatenate(null_distribution) - null_distribution = null_distribution.reshape([-1] + [1]*observed.ndim) + 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 np.issubdtype(observed.dtype, np.inexact) - else np.finfo(observed.dtype).eps*100) - gamma = np.abs(eps * observed) + 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 - pvalues = (cmps.sum(axis=0) + 1) / (n_resamples + 1) # see [1] + 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 - pvalues = (cmps.sum(axis=0) + 1) / (n_resamples + 1) # see [1] + 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 = np.minimum(pvalues_less, pvalues_greater) * 2 + pvalues = xp_minimum(pvalues_less, pvalues_greater) * 2 return pvalues compare = {"less": less, @@ -952,7 +979,7 @@ def two_sided(null_distribution, observed): "two-sided": two_sided} pvalues = compare[alternative](null_distribution, observed) - pvalues = np.clip(pvalues, 0, 1) + pvalues = xp_clip(pvalues, 0., 1., xp=xp) return MonteCarloTestResult(observed, pvalues, null_distribution) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 2bf56dd555bd..94c39034f387 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -67,7 +67,8 @@ from scipy import stats from scipy.optimize import root_scalar from scipy._lib._util import normalize_axis_index -from scipy._lib._array_api import array_namespace, is_numpy, atleast_nd +from scipy._lib._array_api import (array_namespace, is_numpy, atleast_nd, + xp_clip, _move_axis_to_end) from scipy._lib.array_api_compat import size as xp_size # In __all__ but deprecated for removal in SciPy 1.13.0 @@ -4535,20 +4536,6 @@ def confidence_interval(self, confidence_level=0.95, method=None): return ci -def _move_axis_to_end(x, source, xp): - axes = list(range(x.ndim)) - temp = axes.pop(source) - axes = axes + [temp] - return xp.permute_dims(x, axes) - - -def _clip(x, a, b, xp): - y = xp.asarray(x, copy=True) - y[y < a] = a - y[y > b] = b - return y - - def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0): r""" Pearson correlation coefficient and p-value for testing non-correlation. @@ -4934,7 +4921,7 @@ def statistic(x, y, axis): one = xp.asarray(1, dtype=dtype) # `clip` only recently added to array API, so it's not yet available in # array_api_strict. Replace with e.g. `xp.clip(r, -one, one)` when available. - r = xp.asarray(_clip(r, -one, one, xp)) + r = xp.asarray(xp_clip(r, -one, one, xp)) r[const_xy] = xp.nan # As explained in the docstring, the distribution of `r` under the null diff --git a/scipy/stats/tests/test_resampling.py b/scipy/stats/tests/test_resampling.py index 95b24e3c9066..2119da896750 100644 --- a/scipy/stats/tests/test_resampling.py +++ b/scipy/stats/tests/test_resampling.py @@ -1,13 +1,18 @@ -import numpy as np import pytest -from scipy.stats import bootstrap, monte_carlo_test, permutation_test, power + +import numpy as np from numpy.testing import assert_allclose, assert_equal, suppress_warnings -from scipy import stats -from scipy import special -from .. import _resampling as _resampling + +from scipy.conftest import array_api_compatible from scipy._lib._util import rng_integers +from scipy._lib._array_api import (is_numpy, xp_assert_close, + xp_assert_equal, array_namespace) +from scipy import stats, special from scipy.optimize import root +from scipy.stats import bootstrap, monte_carlo_test, permutation_test, power +import scipy.stats._resampling as _resampling + def test_bootstrap_iv(): @@ -732,125 +737,199 @@ def statistic_extradim(*args, axis): class TestMonteCarloHypothesisTest: atol = 2.5e-2 # for comparing p-value - def rvs(self, rvs_in, rs): - return lambda *args, **kwds: rvs_in(*args, random_state=rs, **kwds) + 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 test_input_validation(self): + 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 + + @array_api_compatible + def test_input_validation(self, xp): # test that the appropriate error messages are raised for invalid input - def stat(x): - return stats.skewnorm(x).statistic + data = xp.asarray([1., 2., 3.]) + def stat(x, axis=None): + return xp.mean(x, axis=axis) message = "Array shapes are incompatible for broadcasting." - data = (np.zeros((2, 5)), np.zeros((3, 5))) + 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(data, rvs, lambda x, y: 1, axis=-1) + 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([1, 2, 3], stats.norm.rvs, stat, axis=1.5) + 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([1, 2, 3], stats.norm.rvs, stat, vectorized=1.5) + 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([1, 2, 3], None, stat) + monte_carlo_test(data, None, stat) with pytest.raises(TypeError, match=message): - monte_carlo_test([[1, 2], [3, 4]], [lambda x: x, None], stat) + 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): - monte_carlo_test([[1, 2, 3]], [lambda x: x, lambda x: x], stat) + 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([1, 2, 3], stats.norm.rvs, None) + 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([1, 2, 3], stats.norm.rvs, stat, - n_resamples=-1000) + 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([1, 2, 3], stats.norm.rvs, stat, - n_resamples=1000.5) + 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([1, 2, 3], stats.norm.rvs, stat, batch=-1000) + 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([1, 2, 3], stats.norm.rvs, stat, batch=1000.5) + 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([1, 2, 3], stats.norm.rvs, stat, - alternative='ekki') + 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) + + @array_api_compatible + 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): + @array_api_compatible + 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 = rng.random(10) + x = xp.asarray(rng.standard_normal(size=10)) + xp_test = array_namespace(x) # numpy.std doesn't have `correction` def statistic(x, axis): - batch_size = 1 if x.ndim == 1 else len(x) + batch_size = 1 if x.ndim == 1 else x.shape[0] statistic.batch_size = max(batch_size, statistic.batch_size) statistic.counter += 1 - return stats.skewtest(x, axis=axis).statistic + return self.get_statistic(xp_test)(x, axis=axis) statistic.counter = 0 statistic.batch_size = 0 kwds = {'sample': x, 'statistic': statistic, 'n_resamples': 1000, 'vectorized': True} - kwds['rvs'] = self.rvs(stats.norm.rvs, np.random.default_rng(32842398)) + 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.rvs(stats.norm.rvs, np.random.default_rng(32842398)) + 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.rvs(stats.norm.rvs, np.random.default_rng(32842398)) + 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) - assert_equal(res1.pvalue, res3.pvalue) - assert_equal(res2.pvalue, res3.pvalue) + xp_assert_equal(res1.pvalue, res3.pvalue) + xp_assert_equal(res2.pvalue, res3.pvalue) + @array_api_compatible @pytest.mark.parametrize('axis', range(-3, 3)) - def test_axis(self, axis): + def test_axis_dtype(self, axis, xp): # test that Nd-array samples are handled correctly for valid values - # of the `axis` parameter + # of the `axis` parameter; also make sure non-default dtype is maintained rng = np.random.default_rng(2389234) - norm_rvs = self.rvs(stats.norm.rvs, rng) - size = [2, 3, 4] size[axis] = 100 - x = norm_rvs(size=size) - expected = stats.skewtest(x, axis=axis) - - def statistic(x, axis): - return stats.skewtest(x, axis=axis).statistic - res = monte_carlo_test(x, norm_rvs, statistic, vectorized=True, + # 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) + xp_test = array_namespace(x) # numpy.std doesn't have `correction` + statistic = self.get_statistic(xp_test) + 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) - assert_allclose(res.statistic, expected.statistic) - assert_allclose(res.pvalue, expected.pvalue, atol=self.atol) + 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) + + @array_api_compatible + @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) + xp_test = array_namespace(x) # numpy.std doesn't have `correction` + statistic = self.get_statistic(xp_test) + 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 @@ -865,7 +944,7 @@ def statistic1d(x): return stats.ks_1samp(x, stats.norm.cdf, mode='asymp', alternative=alternative).statistic - norm_rvs = self.rvs(stats.norm.rvs, rng) + norm_rvs = self.get_rvs(stats.norm.rvs, rng) res = monte_carlo_test(x, norm_rvs, statistic1d, n_resamples=1000, vectorized=False, alternative=alternative) @@ -889,7 +968,7 @@ def test_against_normality_tests(self, hypotest, alternative, a): def statistic(x, axis): return hypotest(x, axis=axis).statistic - norm_rvs = self.rvs(stats.norm.rvs, rng) + norm_rvs = self.get_rvs(stats.norm.rvs, rng) res = monte_carlo_test(x, norm_rvs, statistic, vectorized=True, alternative=alternative) @@ -907,7 +986,7 @@ def test_against_normaltest(self, a): def statistic(x, axis): return stats.normaltest(x, axis=axis).statistic - norm_rvs = self.rvs(stats.norm.rvs, rng) + norm_rvs = self.get_rvs(stats.norm.rvs, rng) res = monte_carlo_test(x, norm_rvs, statistic, vectorized=True, alternative='greater') @@ -926,7 +1005,7 @@ def test_against_cramervonmises(self, a): def statistic1d(x): return stats.cramervonmises(x, stats.norm.cdf).statistic - norm_rvs = self.rvs(stats.norm.rvs, rng) + norm_rvs = self.get_rvs(stats.norm.rvs, rng) res = monte_carlo_test(x, norm_rvs, statistic1d, n_resamples=1000, vectorized=False, alternative='greater') @@ -969,7 +1048,7 @@ def fun(a): def statistic1d(x): return stats.anderson(x, dist_name).statistic - dist_rvs = self.rvs(getattr(stats, dist_name).rvs, rng) + dist_rvs = self.get_rvs(getattr(stats, dist_name).rvs, rng) with suppress_warnings() as sup: sup.filter(RuntimeWarning) res = monte_carlo_test(x, dist_rvs, From eb34ef3edab9d501f51b92c8f44f42c12587c0a9 Mon Sep 17 00:00:00 2001 From: Nick ODell Date: Sun, 5 May 2024 17:32:06 +0000 Subject: [PATCH 094/500] ENH: Move array allocation out of loop --- scipy/optimize/_numdiff.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/scipy/optimize/_numdiff.py b/scipy/optimize/_numdiff.py index 0db1b8df68af..b5cb5724d863 100644 --- a/scipy/optimize/_numdiff.py +++ b/scipy/optimize/_numdiff.py @@ -581,16 +581,16 @@ def _dense_difference(fun, x0, f0, h, use_one_sided, method): m = f0.size n = x0.size J_transposed = np.empty((n, m)) + x1 = x0.copy() + x2 = x0.copy() + xc = x0.astype(complex, copy=True) for i in range(h.size): if method == '2-point': - x = x0.copy() - x[i] += h[i] - dx = x[i] - x0[i] # Recompute dx as exactly representable number. - df = fun(x) - f0 + x1[i] += h[i] + dx = x1[i] - x0[i] # Recompute dx as exactly representable number. + df = fun(x1) - f0 elif method == '3-point' and use_one_sided[i]: - x1 = x0.copy() - x2 = x0.copy() x1[i] += h[i] x2[i] += 2 * h[i] dx = x2[i] - x0[i] @@ -598,24 +598,22 @@ def _dense_difference(fun, x0, f0, h, use_one_sided, method): f2 = fun(x2) df = -3.0 * f0 + 4 * f1 - f2 elif method == '3-point' and not use_one_sided[i]: - x1 = x0.copy() x1[i] -= h[i] - x2 = x0.copy() x2[i] += h[i] dx = x2[i] - x1[i] f1 = fun(x1) f2 = fun(x2) df = f2 - f1 elif method == 'cs': - x1 = x0.astype(complex, copy=True) - x1[i] += h[i] * 1.j - f1 = fun(x1) + xc[i] += h[i] * 1.j + f1 = fun(xc) df = f1.imag dx = h[i] else: raise RuntimeError("Never be here.") J_transposed[i] = df / dx + x1[i] = x2[i] = xc[i] = x0[i] if m == 1: J_transposed = np.ravel(J_transposed) From 1ce327bdcdbe1c88f3b59d0b49f407dd2281b1d4 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Sun, 5 May 2024 18:36:28 -0400 Subject: [PATCH 095/500] fix origin handling for minimum_filter and maximum_filter with axes subset --- scipy/ndimage/_filters.py | 14 ++++++++++---- scipy/ndimage/tests/test_filters.py | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/scipy/ndimage/_filters.py b/scipy/ndimage/_filters.py index b132e8ed318b..635b2d336b34 100644 --- a/scipy/ndimage/_filters.py +++ b/scipy/ndimage/_filters.py @@ -1240,7 +1240,7 @@ def _min_or_max_filter(input, size, footprint, structure, output, mode, footprint = np.asarray(footprint, dtype=bool) input = np.asarray(input) if np.iscomplexobj(input): - raise TypeError('Complex type not supported') + raise TypeError("Complex type not supported") output = _ni_support._get_output(output, input) temp_needed = np.may_share_memory(input, output) if temp_needed: @@ -1266,7 +1266,7 @@ def _min_or_max_filter(input, size, footprint, structure, output, mode, else: output[...] = input[...] else: - origins = _ni_support._normalize_sequence(origin, input.ndim) + origins = _ni_support._normalize_sequence(origin, num_axes) if num_axes < input.ndim: if footprint.ndim != num_axes: raise RuntimeError("footprint array has incorrect shape") @@ -1274,17 +1274,23 @@ def _min_or_max_filter(input, size, footprint, structure, output, mode, footprint, tuple(ax for ax in range(input.ndim) if ax not in axes) ) + # set origin = 0 for any axes not being filtered + origins_temp = [0,] * input.ndim + for o, ax in zip(origins, axes): + origins_temp[ax] = o + origins = origins_temp + fshape = [ii for ii in footprint.shape if ii > 0] if len(fshape) != input.ndim: raise RuntimeError('footprint array has incorrect shape.') for origin, lenf in zip(origins, fshape): if (lenf // 2 + origin < 0) or (lenf // 2 + origin >= lenf): - raise ValueError('invalid origin') + raise ValueError("invalid origin") if not footprint.flags.contiguous: footprint = footprint.copy() if structure is not None: if len(structure.shape) != input.ndim: - raise RuntimeError('structure array has incorrect shape') + raise RuntimeError("structure array has incorrect shape") if num_axes != structure.ndim: structure = np.expand_dims( structure, diff --git a/scipy/ndimage/tests/test_filters.py b/scipy/ndimage/tests/test_filters.py index e630781c86e0..6782cf463cff 100644 --- a/scipy/ndimage/tests/test_filters.py +++ b/scipy/ndimage/tests/test_filters.py @@ -792,6 +792,32 @@ def test_filter_axes_kwargs(self, filter_func, size0, size, kwargs, axes): expected = filter_func(array, *args, size_3d, **kwargs) assert_allclose(output, expected) + @pytest.mark.parametrize("filter_func, kwargs", + [(ndimage.minimum_filter, {}), + (ndimage.maximum_filter, {}), + (ndimage.median_filter, {}), + (ndimage.rank_filter, {"rank": 1}), + (ndimage.percentile_filter, {"percentile": 30})]) + def test_filter_weights_subset_axes_origins(self, filter_func, kwargs): + axes = (-2, -1) + origins = (0, 1) + array = np.arange(6 * 8 * 12, dtype=np.float64).reshape(6, 8, 12) + axes = np.array(axes) + + # weights with ndim matching len(axes) + footprint = np.ones((3, 5), dtype=bool) + footprint[0, 1] = 0 # make non-separable + + output = filter_func( + array, footprint=footprint, axes=axes, origin=origins, **kwargs) + + output0 = filter_func( + array, footprint=footprint, axes=axes, origin=0, **kwargs) + + # output has origin shift on last axis relative to output0, so + # expect shifted arrays to be equal. + np.testing.assert_array_equal(output[:, :, 1:], output0[:, :, :-1]) + @pytest.mark.parametrize( 'filter_func, args', [(ndimage.gaussian_filter, (1.0,)), # args = (sigma,) From f89e7edad96771dbb326ccedf8582f9c6609f20a Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 6 May 2024 08:53:45 -0700 Subject: [PATCH 096/500] ENH: stats.kstat/kstatvar: add native support for `axis` (#20651) * ENH: stats.kstat: add native axis support * TST: stats.kstat: strengthen input validation tests * MAINT: stats.kstatvar: add native axis support; add tests * Apply suggestions from code review --------- Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/stats/_morestats.py | 48 +++++++++------ scipy/stats/tests/test_morestats.py | 92 +++++++++++++++++++++++++++-- 2 files changed, 116 insertions(+), 24 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index a22e4e3c321f..9b26e4e19fea 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -218,7 +218,7 @@ def mvsdist(data): @_axis_nan_policy_factory( lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1, default_axis=None ) -def kstat(data, n=2): +def kstat(data, n=2, *, axis=None): r""" Return the nth k-statistic (1<=n<=4 so far). @@ -228,9 +228,14 @@ def kstat(data, n=2): Parameters ---------- data : array_like - Input array. Note that n-D input gets flattened. + 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 ------- @@ -286,20 +291,18 @@ def kstat(data, n=2): if n > 4 or n < 1: raise ValueError("k-statistics only supported for 1<=n<=4") n = int(n) - S = np.zeros(n + 1, np.float64) - data = ravel(data) - N = data.size + data = np.asarray(data) + if axis is None: + data = ravel(data) + axis = 0 + + N = data.shape[axis] # raise ValueError on empty input if N == 0: raise ValueError("Data input must not be empty") - # on nan input, return nan without warning - if np.isnan(np.sum(data)): - return np.nan - - for k in range(1, n + 1): - S[k] = np.sum(data**k, axis=0) + S = [None] + [np.sum(data**k, axis=axis) for k in range(1, n + 1)] if n == 1: return S[1] * 1.0/N elif n == 2: @@ -317,7 +320,7 @@ def kstat(data, n=2): @_axis_nan_policy_factory( lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1, default_axis=None ) -def kstatvar(data, n=2): +def kstatvar(data, n=2, *, axis=None): r"""Return an unbiased estimator of the variance of the k-statistic. See `kstat` for more details of the k-statistic. @@ -325,9 +328,14 @@ def kstatvar(data, n=2): Parameters ---------- data : array_like - Input array. Note that n-D input gets flattened. + 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 ------- @@ -357,13 +365,17 @@ def kstatvar(data, n=2): \frac{144 n \kappa_{2} \kappa^2_{3}}{(n - 1) (n - 2)} + \frac{24 (n + 1) n \kappa^4_{2}}{(n - 1) (n - 2) (n - 3)} """ # noqa: E501 - data = ravel(data) - N = len(data) + data = np.asarray(data) + if axis is None: + data = ravel(data) + axis = 0 + N = data.shape[axis] + if n == 1: - return kstat(data, n=2) * 1.0/N + return kstat(data, n=2, axis=axis) * 1.0/N elif n == 2: - k2 = kstat(data, n=2) - k4 = kstat(data, n=4) + k2 = kstat(data, n=2, axis=axis) + k4 = kstat(data, n=4, axis=axis) return (2*N*k2**2 + (N-1)*k4) / (N*(N+1)) else: raise ValueError("Only n=1 or n=2 supported.") diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 7d5e82998c3c..2d94353bdae0 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -1661,6 +1661,14 @@ def test_permutation_method(self, size): assert_equal(res.pvalue, ref.pvalue) # random_state used +# 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] + + class TestKstat: def test_moments_normal_distribution(self): np.random.seed(32149) @@ -1677,7 +1685,9 @@ def test_moments_normal_distribution(self): assert_allclose((m1, m2, m3), expected[:-1], atol=0.02, rtol=1e-2) def test_empty_input(self): - assert_raises(ValueError, stats.kstat, []) + message = 'Data input must not be empty' + with pytest.raises(ValueError, match=message): + stats.kstat([]) def test_nan_input(self): data = np.arange(10.) @@ -1685,16 +1695,38 @@ def test_nan_input(self): assert_equal(stats.kstat(data), np.nan) - def test_kstat_bad_arg(self): + @pytest.mark.parametrize('n', [0, 4.001]) + def test_kstat_bad_arg(self, n): # Raise ValueError if n > 4 or n < 1. data = np.arange(10) - for n in [0, 4.001]: - assert_raises(ValueError, stats.kstat, data, n=n) + 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): + # 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(x_kstat, n) + assert_allclose(res, ref) + class TestKstatVar: def test_empty_input(self): - assert_raises(ValueError, stats.kstatvar, []) + message = 'Data input must not be empty' + with pytest.raises(ValueError, match=message): + stats.kstatvar([]) def test_nan_input(self): data = np.arange(10.) @@ -1706,7 +1738,27 @@ def test_bad_arg(self): # Raise ValueError is n is not 1 or 2. data = [1] n = 10 - assert_raises(ValueError, stats.kstatvar, data, n=n) + 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): + # 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(x_kstat, 1) + ref = k2 / n + assert_allclose(res, ref) + + res = stats.kstatvar(x_kstat, 2) + # *unbiased estimator* for var(k2) + ref = (2*k2**2*n + (n-1)*k4) / (n * (n+1)) + assert_allclose(res, ref) class TestPpccPlot: @@ -2957,3 +3009,31 @@ def test_edge_cases(self): assert_array_equal(stats.false_discovery_control([0.25]), [0.25]) assert_array_equal(stats.false_discovery_control(0.25), 0.25) assert_array_equal(stats.false_discovery_control([]), []) + + +class TestCommonAxis: + # More thorough testing of `axis` in `test_axis_nan_policy`, + # but those testa 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})]) + def test_axis(self, case): + fun, kwargs = case + rng = np.random.default_rng(24598245982345) + x = rng.random((6, 7)) + + res = fun(x, **kwargs, axis=0) + ref = [fun(x[:, i], **kwargs) for i in range(x.shape[1])] + assert_allclose(res, ref) + + res = fun(x, **kwargs, axis=1) + ref = [fun(x[i, :], **kwargs) for i in range(x.shape[0])] + assert_allclose(res, ref) + + res = fun(x, **kwargs, axis=None) + ref = fun(x.ravel(), **kwargs) + assert_allclose(res, ref) From 44c3b6f424e54c406c9ebe9e5080ae2fecd46cb5 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Mon, 6 May 2024 18:02:26 +0100 Subject: [PATCH 097/500] ENH: stats.variation: add array-API support (#20647) * ENH: stats.variation: add array-API support Co-authored-by: Matt Haberland --- .github/workflows/array_api.yml | 1 + scipy/_lib/_array_api.py | 11 +- scipy/stats/_resampling.py | 4 +- scipy/stats/_stats_py.py | 6 +- scipy/stats/_variation.py | 33 +++-- scipy/stats/tests/test_variation.py | 195 +++++++++++++++++----------- 6 files changed, 153 insertions(+), 97 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 3b5c319a417d..389dad418070 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -98,4 +98,5 @@ jobs: python dev.py --no-build test -b all -t scipy._lib.tests.test__util -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.stats.tests.test_stats -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.stats.tests.test_morestats -- --durations 3 --timeout=60 + python dev.py --no-build test -b all -t scipy.stats.tests.test_variation -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.stats.tests.test_resampling -- --durations 3 --timeout=60 diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index f451fdae1e2a..db86f20716e6 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -413,9 +413,18 @@ def xp_clip(x, a, b, xp=None): # temporary substitute for xp.moveaxis, which is not yet in all backends # or covered by array_api_compat. -def _move_axis_to_end(x, source, xp=None): +def xp_moveaxis_to_end(x, source, xp=None): xp = array_namespace(xp) if xp is None else xp axes = list(range(x.ndim)) temp = axes.pop(source) axes = axes + [temp] return xp.permute_dims(x, axes) + + +# temporary substitute for xp.copysign, which is not yet in all backends +# or covered by array_api_compat. +def xp_copysign(x1, x2, xp=None): + # no attempt to account for special cases + xp = array_namespace(x1, x2) if xp is None else xp + abs_x1 = xp.abs(x1) + return xp.where(x2 >= 0, abs_x1, -abs_x1) diff --git a/scipy/stats/_resampling.py b/scipy/stats/_resampling.py index 4de12960a9a0..d89a0b993038 100644 --- a/scipy/stats/_resampling.py +++ b/scipy/stats/_resampling.py @@ -9,7 +9,7 @@ from scipy._lib._util import check_random_state, _rename_parameter, rng_integers from scipy._lib._array_api import (array_namespace, is_numpy, xp_minimum, - xp_clip, _move_axis_to_end) + xp_clip, xp_moveaxis_to_end) from scipy.special import ndtr, ndtri, comb, factorial from ._common import ConfidenceInterval @@ -716,7 +716,7 @@ def _monte_carlo_test_iv(data, rvs, statistic, vectorized, n_resamples, data_iv = [] for sample in data: sample = xp.broadcast_to(sample, (1,)) if sample.ndim == 0 else sample - sample = _move_axis_to_end(sample, axis_int, xp=xp) + sample = xp_moveaxis_to_end(sample, axis_int, xp=xp) data_iv.append(sample) n_resamples_int = int(n_resamples) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 94c39034f387..0a8a949bd980 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -68,7 +68,7 @@ from scipy.optimize import root_scalar from scipy._lib._util import normalize_axis_index from scipy._lib._array_api import (array_namespace, is_numpy, atleast_nd, - xp_clip, _move_axis_to_end) + xp_clip, xp_moveaxis_to_end) from scipy._lib.array_api_compat import size as xp_size # In __all__ but deprecated for removal in SciPy 1.13.0 @@ -4821,8 +4821,8 @@ def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0): # `moveaxis` only recently added to array API, so it's not yey available in # array_api_strict. Replace with e.g. `xp.moveaxis(x, axis, -1)` when available. - x = _move_axis_to_end(x, axis, xp) - y = _move_axis_to_end(y, axis, xp) + x = xp_moveaxis_to_end(x, axis, xp) + y = xp_moveaxis_to_end(y, axis, xp) axis = -1 dtype = xp.result_type(x.dtype, y.dtype) diff --git a/scipy/stats/_variation.py b/scipy/stats/_variation.py index b51cb856e213..9febde9ec8f1 100644 --- a/scipy/stats/_variation.py +++ b/scipy/stats/_variation.py @@ -1,5 +1,8 @@ import numpy as np + from scipy._lib._util import _get_nan +from scipy._lib._array_api import array_namespace, xp_copysign + from ._axis_nan_policy import _axis_nan_policy_factory @@ -91,31 +94,35 @@ def variation(a, axis=0, nan_policy='propagate', ddof=0, *, keepdims=False): array([1.05109361, 0.31428986, 0.146483 ]) """ + xp = array_namespace(a) + a = xp.asarray(a) # `nan_policy` and `keepdims` are handled by `_axis_nan_policy` + # `axis=None` is only handled for NumPy backend + if axis is None: + a = xp.reshape(a, (-1,)) + axis = 0 + n = a.shape[axis] NaN = _get_nan(a) if a.size == 0 or ddof > n: # Handle as a special case to avoid spurious warnings. # The return values, if any, are all nan. - shp = np.asarray(a.shape) - shp = np.delete(shp, axis) - result = np.full(shp, fill_value=NaN) - return result[()] + shp = list(a.shape) + shp.pop(axis) + result = xp.full(shp, fill_value=NaN) + return result[()] if result.ndim == 0 else result - mean_a = a.mean(axis) + mean_a = xp.mean(a, axis=axis) if ddof == n: # Another special case. Result is either inf or nan. - std_a = a.std(axis=axis, ddof=0) - result = np.full_like(std_a, fill_value=NaN) - i = std_a > 0 - result[i] = np.inf - result[i] = np.copysign(result[i], mean_a[i]) - return result[()] + std_a = xp.std(a, axis=axis, correction=0) + result = xp.where(std_a > 0, xp_copysign(xp.asarray(xp.inf), mean_a), NaN) + return result[()] if result.ndim == 0 else result with np.errstate(divide='ignore', invalid='ignore'): - std_a = a.std(axis, ddof=ddof) + std_a = xp.std(a, axis=axis, correction=ddof) result = std_a / mean_a - return result[()] + return result[()] if result.ndim == 0 else result diff --git a/scipy/stats/tests/test_variation.py b/scipy/stats/tests/test_variation.py index 133978d681f0..e13287fca916 100644 --- a/scipy/stats/tests/test_variation.py +++ b/scipy/stats/tests/test_variation.py @@ -1,8 +1,16 @@ +import math + import numpy as np -from numpy.testing import assert_equal, assert_allclose import pytest +from numpy.testing import suppress_warnings + from scipy.stats import variation from scipy._lib._util import AxisError +from scipy.conftest import array_api_compatible +from scipy._lib._array_api import xp_assert_equal, xp_assert_close + +pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends")] +skip_xp_backends = pytest.mark.skip_xp_backends class TestVariation: @@ -10,150 +18,181 @@ class TestVariation: Test class for scipy.stats.variation """ - def test_ddof(self): - x = np.arange(9.0) - assert_allclose(variation(x, ddof=1), np.sqrt(60/8)/4) + 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): - x = np.array([1, 2, 3, 4, 5]) + def test_sign(self, sgn, xp): + x = xp.asarray([1., 2., 3., 4., 5.]) v = variation(sgn*x) - expected = sgn*np.sqrt(2)/3 - assert_allclose(v, expected, rtol=1e-10) + expected = xp.asarray(sgn*math.sqrt(2)/3) + xp_assert_close(v, expected, rtol=1e-10) - def test_scalar(self): + def test_scalar(self, xp): # A scalar is treated like a 1-d sequence with length 1. - assert_equal(variation(4.0), 0.0) + xp_assert_equal(variation(4.0), 0.0) @pytest.mark.parametrize('nan_policy, expected', [('propagate', np.nan), ('omit', np.sqrt(20/3)/4)]) - def test_variation_nan(self, nan_policy, expected): - x = np.arange(10.) - x[9] = np.nan - assert_allclose(variation(x, nan_policy=nan_policy), expected) - - def test_nan_policy_raise(self): - x = np.array([1.0, 2.0, np.nan, 3.0]) + @skip_xp_backends(np_only=True, + reasons=['`nan_policy` only supports NumPy backend']) + 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), expected) + + @skip_xp_backends(np_only=True, + reasons=['`nan_policy` only supports NumPy backend']) + 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') - def test_bad_nan_policy(self): + @skip_xp_backends(np_only=True, + reasons=['`nan_policy` only supports NumPy backend']) + def test_bad_nan_policy(self, xp): with pytest.raises(ValueError, match='must be one of'): variation([1, 2, 3], nan_policy='foobar') - def test_keepdims(self): - x = np.arange(10).reshape(2, 5) + @skip_xp_backends(np_only=True, + reasons=['`keepdims` only supports NumPy backend']) + def test_keepdims(self, xp): + x = xp.reshape(xp.arange(10), (2, 5)) y = variation(x, axis=1, keepdims=True) expected = np.array([[np.sqrt(2)/2], [np.sqrt(2)/7]]) - assert_allclose(y, expected) + xp_assert_close(y, expected) + @skip_xp_backends(np_only=True, + reasons=['`keepdims` only supports NumPy backend']) @pytest.mark.parametrize('axis, expected', [(0, np.empty((1, 0))), (1, np.full((5, 1), fill_value=np.nan))]) - def test_keepdims_size0(self, axis, expected): - x = np.zeros((5, 0)) + def test_keepdims_size0(self, axis, expected, xp): + x = xp.zeros((5, 0)) y = variation(x, axis=axis, keepdims=True) - assert_equal(y, expected) + xp_assert_equal(y, expected) + @skip_xp_backends(np_only=True, + reasons=['`keepdims` only supports NumPy backend']) @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): - x = np.array([[1, 1, 2, 2], [1, 2, 3, 3]]) + 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) - assert_equal(y, np.full((2, 1), fill_value=expected_fill)) + xp_assert_equal(y, xp.full((2, 1), fill_value=expected_fill)) - def test_propagate_nan(self): + @skip_xp_backends(np_only=True, + reasons=['`nan_policy` only supports NumPy backend']) + 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 = np.arange(8).reshape(2, -1).astype(float) - a[1, 0] = np.nan + a = xp.reshape(xp.arange(8, dtype=float), (2, -1)) + a[1, 0] = xp.nan v = variation(a, axis=1, nan_policy="propagate") - assert_allclose(v, [np.sqrt(5/4)/1.5, np.nan], atol=1e-15) + xp_assert_close(v, [math.sqrt(5/4)/1.5, xp.nan], atol=1e-15) - def test_axis_none(self): + @skip_xp_backends(np_only=True, reasons=['Python list input uses NumPy backend']) + def test_axis_none(self, xp): # Check that `variation` computes the result on the flattened # input when axis is None. y = variation([[0, 1], [2, 3]], axis=None) - assert_allclose(y, np.sqrt(5/4)/1.5) + xp_assert_close(y, math.sqrt(5/4)/1.5) - def test_bad_axis(self): + def test_bad_axis(self, xp): # Check that an invalid axis raises np.exceptions.AxisError. - x = np.array([[1, 2, 3], [4, 5, 6]]) - with pytest.raises(AxisError): + x = xp.asarray([[1, 2, 3], [4, 5, 6]]) + with pytest.raises((AxisError, IndexError)): variation(x, axis=10) - def test_mean_zero(self): + 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 = np.array([10, -3, 1, -4, -4]) + x = xp.asarray([10., -3., 1., -4., -4.]) y = variation(x) - assert_equal(y, np.inf) + xp_assert_equal(y, xp.asarray(xp.inf)) - x2 = np.array([x, -10*x]) + x2 = xp.stack([x, -10.*x]) y2 = variation(x2, axis=1) - assert_equal(y2, [np.inf, np.inf]) + xp_assert_equal(y2, xp.asarray([xp.inf, xp.inf])) - @pytest.mark.parametrize('x', [np.zeros(5), [], [1, 2, np.inf, 9]]) - def test_return_nan(self, x): + @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) - assert_equal(y, np.nan) + with suppress_warnings() as sup: + # torch + sup.filter(UserWarning, "std*") + y = variation(x) + xp_assert_equal(y, xp.asarray(xp.nan, dtype=x.dtype)) @pytest.mark.parametrize('axis, expected', [(0, []), (1, [np.nan]*3), (None, np.nan)]) - def test_2d_size_zero_with_axis(self, axis, expected): - x = np.empty((3, 0)) - y = variation(x, axis=axis) - assert_equal(y, expected) - - def test_neg_inf(self): + def test_2d_size_zero_with_axis(self, axis, expected, xp): + x = xp.empty((3, 0)) + with suppress_warnings() as sup: + # torch + sup.filter(UserWarning, "std*") + y = variation(x, axis=axis) + xp_assert_equal(y, xp.asarray(expected)) + + 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 = np.array([-3, -5]) - assert_equal(variation(x1, ddof=2), -np.inf) - - x2 = np.array([[np.nan, 1, -10, np.nan], - [-20, -3, np.nan, np.nan]]) - assert_equal(variation(x2, axis=1, ddof=2, nan_policy='omit'), - [-np.inf, -np.inf]) - + x1 = xp.asarray([-3., -5.]) + xp_assert_equal(variation(x1, ddof=2), xp.asarray(-xp.inf)) + + @skip_xp_backends(np_only=True, + reasons=['`nan_policy` only supports NumPy backend']) + 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.inf, -xp.inf]) + + @skip_xp_backends(np_only=True, + reasons=['`nan_policy` only supports NumPy backend']) @pytest.mark.parametrize("nan_policy", ['propagate', 'omit']) - def test_combined_edge_cases(self, nan_policy): - x = np.array([[0, 10, np.nan, 1], - [0, -5, np.nan, 2], - [0, -5, np.nan, 3]]) + def test_combined_edge_cases(self, nan_policy, xp): + x = xp.array([[0, 10, xp.nan, 1], + [0, -5, xp.nan, 2], + [0, -5, xp.nan, 3]]) y = variation(x, axis=0, nan_policy=nan_policy) - assert_allclose(y, [np.nan, np.inf, np.nan, np.sqrt(2/3)/2]) + xp_assert_close(y, [xp.nan, xp.inf, xp.nan, math.sqrt(2/3)/2]) + @skip_xp_backends(np_only=True, + reasons=['`nan_policy` only supports NumPy backend']) @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): + 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 = np.nan - x = np.array([[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]]) + 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]]) v = variation(x, axis=1, ddof=ddof, nan_policy='omit') - assert_allclose(v, expected) + xp_assert_close(v, expected) - def test_variation_ddof(self): + @skip_xp_backends(np_only=True, + reasons=['`nan_policy` only supports NumPy backend']) + def test_variation_ddof(self, xp): # test variation with delta degrees of freedom # regression test for gh-13341 - a = np.array([1, 2, 3, 4, 5]) - nan_a = np.array([1, 2, 3, np.nan, 4, 5, np.nan]) + 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) - assert_allclose(y, np.sqrt(5/2)/3) + xp_assert_close(y, math.sqrt(5/2)/3) assert y == nan_y From 19966584c4f131ee063e116e8f80ed5134fd0373 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 6 May 2024 12:35:53 -0700 Subject: [PATCH 098/500] MAINT: stats.yulesimon: fix kurtosis --- scipy/stats/_discrete_distns.py | 2 +- scipy/stats/_distr_params.py | 2 +- scipy/stats/tests/test_discrete_basic.py | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scipy/stats/_discrete_distns.py b/scipy/stats/_discrete_distns.py index 3bad9c488e1a..cda7c4e11a5e 100644 --- a/scipy/stats/_discrete_distns.py +++ b/scipy/stats/_discrete_distns.py @@ -1666,7 +1666,7 @@ def _stats(self, alpha): np.inf) g1 = np.where(alpha <= 2, np.nan, g1) g2 = np.where(alpha > 4, - alpha + 3 + ((alpha**3 - 49 * alpha - 22) / + alpha + 3 + ((11 * alpha**3 - 49 * alpha - 22) / (alpha * (alpha - 4) * (alpha - 3))), np.inf) g2 = np.where(alpha <= 2, np.nan, g2) diff --git a/scipy/stats/_distr_params.py b/scipy/stats/_distr_params.py index c70299a5abdb..2b86298d592f 100644 --- a/scipy/stats/_distr_params.py +++ b/scipy/stats/_distr_params.py @@ -142,7 +142,7 @@ ['poisson', (0.6,)], ['randint', (7, 31)], ['skellam', (15, 8)], - ['zipf', (6.5,)], + ['zipf', (6.6,)], ['zipfian', (0.75, 15)], ['zipfian', (1.25, 10)], ['yulesimon', (11.0,)], diff --git a/scipy/stats/tests/test_discrete_basic.py b/scipy/stats/tests/test_discrete_basic.py index bce36b97c1f4..012e68a8da2d 100644 --- a/scipy/stats/tests/test_discrete_basic.py +++ b/scipy/stats/tests/test_discrete_basic.py @@ -91,7 +91,9 @@ def test_moments(distname, arg): check_mean_expect(distfn, arg, m, distname) check_var_expect(distfn, arg, m, v, distname) check_skew_expect(distfn, arg, m, v, s, distname) - if distname not in ['zipf', 'yulesimon', 'betanbinom']: + with np.testing.suppress_warnings() as sup: + if distname in ['zipf', 'betanbinom']: + sup.filter(RuntimeWarning) check_kurt_expect(distfn, arg, m, v, k, distname) # frozen distr moments From ee8c65576a9a248fe771550041b7e35846fc9151 Mon Sep 17 00:00:00 2001 From: Daniel Schmitz <40656107+dschmitz89@users.noreply.github.com> Date: Tue, 7 May 2024 00:32:57 +0200 Subject: [PATCH 099/500] MAINT: stats: update boost to improve `skewnorm.ppf` (#20643) * MAINT: stats: update boost to fix improve `skewnorm.ppf` --- scipy/_lib/boost_math | 2 +- scipy/stats/tests/test_distributions.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/scipy/_lib/boost_math b/scipy/_lib/boost_math index 15c40fae1610..a53b013c735c 160000 --- a/scipy/_lib/boost_math +++ b/scipy/_lib/boost_math @@ -1 +1 @@ -Subproject commit 15c40fae1610fd83728d75e96d7377fe55f976f7 +Subproject commit a53b013c735caa98179532a32ad24d34569b9710 diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py index ad9fc1d37516..a90af1301691 100644 --- a/scipy/stats/tests/test_distributions.py +++ b/scipy/stats/tests/test_distributions.py @@ -4009,6 +4009,12 @@ def optimizer(fun, bounds): 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): From 0189c29d147707bbcdd9296b106268379a9fc765 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Tue, 7 May 2024 13:50:27 +1000 Subject: [PATCH 100/500] MAINT: remove unused variable --- scipy/special/special/tools.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/special/special/tools.h b/scipy/special/special/tools.h index 2e8a0eb22e34..66fcef58f17c 100644 --- a/scipy/special/special/tools.h +++ b/scipy/special/special/tools.h @@ -41,7 +41,7 @@ namespace detail { * of non-convergence. */ T result = init_val; - T term, previous; + T term; for (std::uint64_t i = 0; i < max_terms; ++i) { term = g(); result += term; From 811b13448c7d76cbb6c1c83d2a69d01ead667ac8 Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Mon, 6 May 2024 23:59:29 -0400 Subject: [PATCH 101/500] ENH: sparse: Add csr 1d sparse arrays and test_arithmetic1d (#19833) * add csr 1d and test_arithmetic1d * clean up after rebase of _mul to _matmul namechange * change matmul to produce 0d with vec @ vec. Adjust tests. clean up comments * Add tests for 1d@1d. Fix 1d on the right of @. Slow to convert to csc * fixup _coo_to_compressed from setdiag fix to work for 1d * fix _coo.py review comments; add copy flag to 1D tocsr * fix other review comments * rewrite fin new_shape logic. make copy of coords if asked. * use np.testing assert_equal and assert_allclose --- scipy/sparse/_base.py | 2 - scipy/sparse/_compressed.py | 381 +++++++++++++++--------- scipy/sparse/_coo.py | 22 +- scipy/sparse/_csr.py | 78 ++++- scipy/sparse/tests/meson.build | 1 + scipy/sparse/tests/test_arithmetic1d.py | 338 +++++++++++++++++++++ scipy/sparse/tests/test_common1d.py | 154 +++++----- scipy/sparse/tests/test_coo.py | 80 ++--- 8 files changed, 774 insertions(+), 282 deletions(-) create mode 100644 scipy/sparse/tests/test_arithmetic1d.py diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index e2cdedd6f007..22b9a2454c8d 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -569,8 +569,6 @@ def _matmul_dispatch(self, other): if issparse(other): if self.shape[-1] != other.shape[0]: raise ValueError('dimension mismatch') - if other.ndim == 1: - raise ValueError('Cannot yet multiply a 1d sparse array') return self._matmul_sparse(other) # If it's a list or whatever, treat it like an array diff --git a/scipy/sparse/_compressed.py b/scipy/sparse/_compressed.py index e1a725144ae5..2a5a0fe23bf8 100644 --- a/scipy/sparse/_compressed.py +++ b/scipy/sparse/_compressed.py @@ -7,7 +7,7 @@ import numpy as np from scipy._lib._util import _prune_array, copy_if_needed -from ._base import _spbase, issparse, SparseEfficiencyWarning +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, @@ -26,6 +26,7 @@ class _cs_matrix(_data_matrix, _minmax_mixin, IndexMixin): def __init__(self, arg1, shape=None, dtype=None, copy=False): _data_matrix.__init__(self) + is_array = isinstance(self, sparray) if issparse(arg1): if arg1.format == self.format and copy: @@ -37,18 +38,17 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): ) elif isinstance(arg1, tuple): - if isshape(arg1): + if isshape(arg1, allow_1d=is_array): # It's a tuple of matrix dimensions (M, N) # create empty matrix - self._shape = check_shape(arg1) - M, N = self.shape + self._shape = check_shape(arg1, allow_1d=is_array) + 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(M, N)) + 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(self._swap((M, N))[0] + 1, - dtype=idx_dtype) + self.indptr = np.zeros(M + 1, dtype=idx_dtype) else: if len(arg1) == 2: # (data, ij) format @@ -62,7 +62,7 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): # Select index dtype large enough to pass array and # scalar parameters to sparsetools maxval = None - if shape is not None: + if shape is not None and 0 not in shape: maxval = max(shape) idx_dtype = self._get_index_dtype((indices, indptr), maxval=maxval, @@ -74,33 +74,32 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): 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.format}_matrix " - "constructor usage") + raise ValueError(f"unrecognized {self.__class__.__name__} " + f"constructor input: {arg1}") else: # must be dense try: arg1 = np.asarray(arg1) except Exception as e: - msg = f"unrecognized {self.format}_matrix constructor usage" - raise ValueError(msg) from e + raise ValueError(f"unrecognized {self.__class__.__name__} " + f"constructor input: {arg1}") from e 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) - else: - if self.shape is None: - # shape not already set, try to infer dimensions - try: - major_dim = len(self.indptr) - 1 - minor_dim = self.indices.max() + 1 - except Exception as e: - raise ValueError('unable to infer matrix dimensions') from e - else: - self._shape = check_shape(self._swap((major_dim, minor_dim))) + self._shape = check_shape(shape, allow_1d=is_array) + elif self.shape is None: + # shape not already set, try to infer dimensions + try: + major_d = len(self.indptr) - 1 + minor_d = self.indices.max() + 1 + except Exception as e: + raise ValueError('unable to infer matrix dimensions') from e + + self._shape = check_shape(self._swap((major_d, minor_d)), allow_1d=is_array) if dtype is not None: self.data = self.data.astype(dtype, copy=False) @@ -110,6 +109,10 @@ def __init__(self, arg1, shape=None, dtype=None, copy=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 @@ -136,10 +139,6 @@ def check_format(self, full_check=True): If `False`, run basic checks on attributes. O(1) operations. Default is `True`. """ - # use _swap to determine proper bounds - major_name, minor_name = self._swap(('row', 'column')) - major_dim, minor_dim = self._swap(self.shape) - # 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})", @@ -153,11 +152,11 @@ def check_format(self, full_check=True): if x != 1: raise ValueError('data, indices, and indptr should be 1-D') - # check index pointer - if (len(self.indptr) != major_dim + 1): - raise ValueError( - f"index pointer size ({len(self.indptr)}) should be ({major_dim + 1})" - ) + # 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") @@ -173,13 +172,12 @@ def check_format(self, full_check=True): if full_check: # check format validity (more expensive) if self.nnz > 0: - if self.indices.max() >= minor_dim: - raise ValueError(f"{minor_name} index values must be < {minor_dim}") + if self.indices.max() >= N: + raise ValueError(f"indices must be < {N}") if self.indices.min() < 0: - raise ValueError(f"{minor_name} index values must be >= 0") + raise ValueError("indices must be >= 0") if np.diff(self.indptr).min() < 0: - raise ValueError("index pointer values must form a " - "non-decreasing sequence") + 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) @@ -344,8 +342,8 @@ def _add_dense(self, other): dtype = upcast_char(self.dtype.char, other.dtype.char) order = self._swap('CF')[0] result = np.array(other, dtype=dtype, order=order, copy=True) - M, N = self._swap(self.shape) 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) @@ -356,9 +354,7 @@ def _sub_sparse(self, other): return self._binopt(other, '_minus_') def multiply(self, other): - """Point-wise multiplication by another array/matrix, vector, or - scalar. - """ + """Point-wise multiplication by array/matrix, vector, or scalar.""" # Scalar multiplication. if isscalarlike(other): return self._mul_scalar(other) @@ -367,105 +363,110 @@ def multiply(self, other): if self.shape == other.shape: other = self.__class__(other) return self._binopt(other, '_elmul_') - if other.ndim == 1: - raise TypeError("broadcast from a 1d array not yet supported") # Single element. - elif other.shape == (1, 1): - return self._mul_scalar(other.toarray()[0, 0]) - elif self.shape == (1, 1): - return other._mul_scalar(self.toarray()[0, 0]) + 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. - elif self.shape[1] == 1 and other.shape[0] == 1: - return self._matmul_sparse(other.tocsc()) - elif self.shape[0] == 1 and other.shape[1] == 1: - return other._matmul_sparse(self.tocsc()) - # Row vector times matrix. other is a row. - elif other.shape[0] == 1 and self.shape[1] == other.shape[1]: - other = self._dia_container( - (other.toarray().ravel(), [0]), - shape=(other.shape[1], other.shape[1]) - ) - return self._matmul_sparse(other) + 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. - elif self.shape[0] == 1 and self.shape[1] == other.shape[1]: - copy = self._dia_container( - (self.toarray().ravel(), [0]), - shape=(self.shape[1], self.shape[1]) - ) + if sM == 1 and sN == oN: + copy = _make_diagonal_csr(self.toarray().ravel(), is_array) return other._matmul_sparse(copy) - # Column vector times matrix. other is a column. - elif other.shape[1] == 1 and self.shape[0] == other.shape[0]: - other = self._dia_container( - (other.toarray().ravel(), [0]), - shape=(other.shape[0], other.shape[0]) - ) - return other._matmul_sparse(self) + + # 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. - elif self.shape[1] == 1 and self.shape[0] == other.shape[0]: - copy = self._dia_container( - (self.toarray().ravel(), [0]), - shape=(self.shape[0], self.shape[0]) - ) - return copy._matmul_sparse(other) - else: - raise ValueError("inconsistent shapes") + if sN == 1 and sM == oM: + new_self = _make_diagonal_csr(self.toarray().ravel(), is_array) + return new_self._matmul_sparse(other) + raise ValueError("inconsistent shapes") # Assume other is a dense matrix/array, which produces a single-item # object array if other isn't convertible to ndarray. - other = np.atleast_2d(other) + other = np.asanyarray(other) - if other.ndim != 2: + 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 - return self._mul_scalar(other.flat[0]) + bshape = np.broadcast_shapes(self.shape, other.shape) + return self._mul_scalar(other.flat[0]).reshape(bshape) # Fast case for trivial sparse matrix. - elif self.shape == (1, 1): - return np.multiply(self.toarray()[0, 0], other) + if self.shape in ((1,), (1, 1)): + bshape = np.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.row, ret.col]) + 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... - elif self.shape[0] == 1: - if other.shape[1] == 1: # Dense column vector. - data = np.multiply(ret.data, other) - elif other.shape[1] == self.shape[1]: # Dense matrix. - data = np.multiply(ret.data, other[:, ret.col]) + 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("inconsistent shapes") - row = np.repeat(np.arange(other.shape[0]), len(ret.row)) - col = np.tile(ret.col, other.shape[0]) + row = np.repeat(np.arange(other2d.shape[0]), ret.nnz) + col = np.tile(ret.col, other2d.shape[0]) return self._coo_container( (data.view(np.ndarray).ravel(), (row, col)), - shape=(other.shape[0], self.shape[1]), + shape=(other2d.shape[0], self.shape[-1]), copy=False ) # Sparse column vector times... - elif self.shape[1] == 1: - if other.shape[0] == 1: # Dense row vector. - data = np.multiply(ret.data[:, None], other) - elif other.shape[0] == self.shape[0]: # Dense matrix. - data = np.multiply(ret.data[:, None], other[ret.row]) + 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("inconsistent shapes") - row = np.repeat(ret.row, other.shape[1]) - col = np.tile(np.arange(other.shape[1]), len(ret.col)) + row = np.repeat(ret.row, other2d.shape[1]) + col = np.tile(np.arange(other2d.shape[1]), len(ret.col)) return self._coo_container( (data.view(np.ndarray).ravel(), (row, col)), - shape=(self.shape[0], other.shape[1]), + shape=(self.shape[0], other2d.shape[1]), copy=False ) # Sparse matrix times dense row vector. - elif other.shape[0] == 1 and self.shape[1] == other.shape[1]: - data = np.multiply(ret.data, other[:, ret.col].ravel()) + 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 other.shape[1] == 1 and self.shape[0] == other.shape[0]: - data = np.multiply(ret.data, other[ret.row].ravel()) + elif other2d.shape[1] == 1 and self.shape[0] == other2d.shape[0]: + data = np.multiply(ret.data, other2d[ret.row].ravel()) else: raise ValueError("inconsistent shapes") ret.data = data.view(np.ndarray).ravel() @@ -476,21 +477,20 @@ def multiply(self, other): ########################### def _matmul_vector(self, other): - M, N = self.shape + M, N = self._shape_as_2d # output array - result = np.zeros(M, dtype=upcast_char(self.dtype.char, - other.dtype.char)) + 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 + return result[0] if self.ndim == 1 else result def _matmul_multivector(self, other): - M, N = self.shape - n_vecs = other.shape[1] # number of column vectors + 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)) @@ -500,13 +500,27 @@ def _matmul_multivector(self, other): 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 - K2, N = other.shape - - major_axis = self._swap((M, N))[0] + 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 + + # find new_shape: (M, N), (M,), (N,) or () + new_shape = () + if self.ndim == 2: + new_shape += (M,) + if o_ndim == 2: + new_shape += (N,) + + major_dim = self._swap((M, N))[0] other = self.__class__(other) # convert to this format idx_dtype = self._get_index_dtype((self.indptr, self.indices, @@ -518,12 +532,16 @@ def _matmul_sparse(self, other): np.asarray(self.indices, dtype=idx_dtype), np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype)) + 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)) idx_dtype = self._get_index_dtype((self.indptr, self.indices, other.indptr, other.indices), maxval=nnz) - indptr = np.empty(major_axis + 1, dtype=idx_dtype) + indptr = np.empty(major_dim + 1, dtype=idx_dtype) indices = np.empty(nnz, dtype=idx_dtype) data = np.empty(nnz, dtype=upcast(self.dtype, other.dtype)) @@ -536,7 +554,9 @@ def _matmul_sparse(self, other): other.data, indptr, indices, data) - return self.__class__((data, indices, indptr), shape=(M, N)) + if new_shape == (): + return np.array(data[0]) + return self.__class__((data, indices, indptr), shape=new_shape) def diagonal(self, k=0): rows, cols = self.shape @@ -600,7 +620,7 @@ def sum(self, axis=None, dtype=None, out=None): """ # The _spbase base class already does axis=0 and axis=1 efficiently # so we only do the case axis=None here - if (not hasattr(self, 'blocksize') and + 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) @@ -616,9 +636,8 @@ def sum(self, axis=None, dtype=None, out=None): raise ValueError('dimensions do not match') return ret.sum(axis=(), dtype=dtype, out=out) - # _spbase will handle the remaining situations when axis - # is in {None, -1, 0, 1} else: + # _spbase handles the situations when axis is in {None, -2, -1, 0, 1} return _spbase.sum(self, axis=axis, dtype=dtype, out=out) sum.__doc__ = _spbase.sum.__doc__ @@ -649,6 +668,43 @@ def _minor_reduce(self, ufunc, data=None): # Getting and Setting # ####################### + def _get_int(self, idx): + if 0 <= idx <= self.shape[0]: + spot = np.flatnonzero(self.indices == idx) + if spot.size: + return self.data[spot[0]] + return self.data.dtype.type(0) + raise IndexError(f'index ({idx}) out of range') + +# For now, 1d only has integer indexing. Soon we will add get_slice/array +# def _get_slice(self, idx): +# if idx == slice(None): +# return self.copy() +# if idx.step in (1, None): +# major, minor = self._swap((0, idx)) +# ret = self._get_submatrix(major, minor, copy=True) +# return ret.reshape(ret.shape[-1]) +# +# _slice = self._swap((self._minor_slice, self._major_slice))[0] +# return _slice(idx) +# +# def _get_array(self, idx): +# idx = np.asarray(idx) +# idx_dtype = self.indices.dtype +# M, N = self._swap((1, self.shape[0])) +# row = np.zeros_like(idx, dtype=idx_dtype) +# major, minor = self._swap((row, idx)) +# major = np.asarray(major, dtype=idx_dtype) +# minor = np.asarray(minor, dtype=idx_dtype) +# if minor.size == 0: +# return self.__class__([], dtype=self.dtype) +# new_shape = minor.shape if minor.shape[0] > 1 else (minor.shape[-1],) +# +# 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) +# return self.__class__(val.reshape(new_shape)) + def _get_intXint(self, row, col): M, N = self._swap(self.shape) major, minor = self._swap((row, col)) @@ -689,9 +745,9 @@ def _major_index_fancy(self, idx): idx_dtype = self._get_index_dtype((self.indptr, self.indices)) indices = np.asarray(idx, dtype=idx_dtype).ravel() - _, N = self._swap(self.shape) + N = self._swap(self._shape_as_2d)[1] M = len(indices) - new_shape = self._swap((M, N)) + new_shape = self._swap((M, N)) if self.ndim == 2 else (M,) if M == 0: return self.__class__(new_shape, dtype=self.dtype) @@ -722,10 +778,10 @@ def _major_slice(self, idx, copy=False): if idx == slice(None): return self.copy() if copy else self - M, N = self._swap(self.shape) + 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)) + new_shape = self._swap((M, N)) if self.ndim == 2 else (M,) if M == 0: return self.__class__(new_shape, dtype=self.dtype) @@ -765,9 +821,9 @@ def _minor_index_fancy(self, idx): idx = np.asarray(idx, dtype=idx_dtype).ravel() - M, N = self._swap(self.shape) + M, N = self._swap(self._shape_as_2d) k = len(idx) - new_shape = self._swap((M, k)) + new_shape = self._swap((M, k)) if self.ndim == 2 else (k,) if k == 0: return self.__class__(new_shape, dtype=self.dtype) @@ -801,7 +857,7 @@ def _minor_slice(self, idx, copy=False): if idx == slice(None): return self.copy() if copy else self - M, N = self._swap(self.shape) + M, N = self._swap(self._shape_as_2d) start, stop, step = idx.indices(N) N = len(range(start, stop, step)) if N == 0: @@ -816,7 +872,7 @@ def _get_submatrix(self, major=None, minor=None, copy=False): major, minor: None, int, or slice with step 1 """ - M, N = self._swap(self.shape) + M, N = self._swap(self._shape_as_2d) i0, i1 = _process_slice(major, M) j0, j1 = _process_slice(minor, N) @@ -827,9 +883,22 @@ def _get_submatrix(self, major=None, minor=None, copy=False): 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_int(self, idx, x): + major, minor = self._swap((0, idx)) + self._set_many(major, minor, x) + + def _set_array(self, idx, x): + major, minor = self._swap((np.zeros_like(idx), idx)) + broadcast = x.shape[-1] == 1 and minor.shape[-1] != 1 + if broadcast: + x = np.repeat(x.data, idx.shape[-1]) + self._set_many(major, minor, x) + def _set_intXint(self, row, col, x): i, j = self._swap((row, col)) self._set_many(i, j, x) @@ -866,6 +935,8 @@ def _set_arrayXarray_sparse(self, row, col, 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) @@ -930,7 +1001,7 @@ def _setdiag(self, values, k): self.indptr, self.indices, self.data, _ = arrays def _prepare_indices(self, i, j): - M, N = self._swap(self.shape) + M, N = self._swap(self._shape_as_2d) def check_bounds(indices, bound): idx = indices.max() @@ -1084,6 +1155,9 @@ def _insert_many(self, i, j, x): ###################### 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) @@ -1109,7 +1183,7 @@ def toarray(self, order=None, out=None): else: x = self.tocsc() y = out.T - M, N = x._swap(x.shape) + M, N = x._swap(x._shape_as_2d) csr_todense(M, N, x.indptr, x.indices, x.data, y) return out @@ -1124,9 +1198,8 @@ def eliminate_zeros(self): This is an *in place* operation. """ - M, N = self._swap(self.shape) - _sparsetools.csr_eliminate_zeros(M, N, self.indptr, self.indices, - self.data) + M, N = self._swap(self._shape_as_2d) + _sparsetools.csr_eliminate_zeros(M, N, self.indptr, self.indices, self.data) self.prune() # nnz may have changed @property @@ -1167,9 +1240,8 @@ def sum_duplicates(self): return self.sort_indices() - M, N = self._swap(self.shape) - _sparsetools.csr_sum_duplicates(M, N, self.indptr, self.indices, - self.data) + M, N = self._swap(self._shape_as_2d) + _sparsetools.csr_sum_duplicates(M, N, self.indptr, self.indices, self.data) self.prune() # nnz may have changed self.has_canonical_format = True @@ -1218,7 +1290,7 @@ def sort_indices(self): def prune(self): """Remove empty space after all non-zero elements. """ - major_dim = self._swap(self.shape)[0] + major_dim = self._swap(self._shape_as_2d)[0] if len(self.indptr) != major_dim + 1: raise ValueError('index pointer has invalid length') @@ -1231,7 +1303,8 @@ def prune(self): self.data = _prune_array(self.data[:self.nnz]) def resize(self, *shape): - shape = check_shape(shape) + shape = check_shape(shape, allow_1d=isinstance(self, sparray)) + if hasattr(self, 'blocksize'): bm, bn = self.blocksize new_M, rm = divmod(shape[0], bm) @@ -1241,8 +1314,8 @@ def resize(self, *shape): f" blocks. Got {shape}") M, N = self.shape[0] // bm, self.shape[1] // bn else: - new_M, new_N = self._swap(shape) - M, N = self._swap(self.shape) + 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]] @@ -1305,7 +1378,8 @@ def _binopt(self, other, op): else: data = np.empty(maxnnz, dtype=upcast(self.dtype, other.dtype)) - fn(self.shape[0], self.shape[1], + M, N = self._shape_as_2d + fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), self.data, @@ -1335,16 +1409,33 @@ def _divide_sparse(self, other): # inside it is either zero or defined by eldiv. out = np.empty(self.shape, dtype=self.dtype) out.fill(np.nan) - row, col = other.nonzero() - out[row, col] = 0 + coords = other.nonzero() + if self.ndim == 1: + coords = (coords[-1],) + out[coords] = 0 r = r.tocoo() - out[r.row, r.col] = r.data - out = self._container(out) + out[r.coords] = r.data + return self._container(out) else: # integers types go with nan <-> 0 out = r + return out - return out + +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) + indptr = np.arange(N + 1) + indices = indptr[:-1] + + return csr_array((data, indices, indptr), shape=(N, N)) def _process_slice(sl, num): diff --git a/scipy/sparse/_coo.py b/scipy/sparse/_coo.py index f8c5039f94ed..b52bc7af5760 100644 --- a/scipy/sparse/_coo.py +++ b/scipy/sparse/_coo.py @@ -335,27 +335,35 @@ def tocsr(self, copy=False): [0, 0, 0, 1]]) """ - if self.ndim != 2: - raise ValueError("Cannot convert a 1d sparse array to csr format") if self.nnz == 0: return self._csr_container(self.shape, dtype=self.dtype) else: from ._csr import csr_array - indptr, indices, data, shape = self._coo_to_compressed(csr_array._swap) + 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): + def _coo_to_compressed(self, swap, copy=False): """convert (shape, coords, data) to (indptr, indices, data, shape)""" - M, N = swap(self.shape) - major, minor = swap(self.coords) - nnz = len(major) + 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) diff --git a/scipy/sparse/_csr.py b/scipy/sparse/_csr.py index 37c6ffacd814..ffa2aa7e7822 100644 --- a/scipy/sparse/_csr.py +++ b/scipy/sparse/_csr.py @@ -18,12 +18,48 @@ class _csr_base(_cs_matrix): _format = 'csr' + # override IndexMixin.__getitem__ for 1d case until fully implemented + def __getitem__(self, key): + if self.ndim == 2: + return super().__getitem__(key) + + if isinstance(key, tuple) and len(key) == 1: + key = key[0] + INT_TYPES = (int, np.integer) + if isinstance(key, INT_TYPES): + if key < 0: + key += self.shape[-1] + if key < 0 or key >= self.shape[-1]: + raise IndexError('index value out of bounds') + return self._get_int(key) + else: + raise IndexError('array/slice index for 1d csr_array not yet supported') + + # override IndexMixin.__setitem__ for 1d case until fully implemented + def __setitem__(self, key, value): + if self.ndim == 2: + return super().__setitem__(key, value) + + if isinstance(key, tuple) and len(key) == 1: + key = key[0] + INT_TYPES = (int, np.integer) + if isinstance(key, INT_TYPES): + if key < 0: + key += self.shape[-1] + if key < 0 or key >= self.shape[-1]: + raise IndexError('index value out of bounds') + return self._set_int(key, value) + else: + raise IndexError('array index for 1d csr_array not yet provided') + 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) @@ -31,6 +67,8 @@ def transpose(self, axes=None, copy=False): 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() @@ -56,13 +94,16 @@ def tocsr(self, copy=False): tocsr.__doc__ = _spbase.tocsr.__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, self.shape[0])) - indptr = np.empty(self.shape[1] + 1, dtype=idx_dtype) + 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(self.shape[0], self.shape[1], + csr_tocsc(M, N, self.indptr.astype(idx_dtype), self.indices.astype(idx_dtype), self.data, @@ -77,6 +118,8 @@ def tocsc(self, copy=False): 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)) @@ -121,22 +164,38 @@ def _swap(x): 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) - shape = (1, self.shape[1]) + # 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 - ) + 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: @@ -149,9 +208,10 @@ def _getrow(self, i): dtype=self.dtype, copy=False) def _getcol(self, i): - """Returns a copy of column i of the matrix, as a (m x 1) - CSR matrix (column vector). + """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: diff --git a/scipy/sparse/tests/meson.build b/scipy/sparse/tests/meson.build index 4f76afb35401..17e86ff53345 100644 --- a/scipy/sparse/tests/meson.build +++ b/scipy/sparse/tests/meson.build @@ -1,5 +1,6 @@ python_sources = [ '__init__.py', + 'test_arithmetic1d.py', 'test_array_api.py', 'test_base.py', 'test_coo.py', diff --git a/scipy/sparse/tests/test_arithmetic1d.py b/scipy/sparse/tests/test_arithmetic1d.py new file mode 100644 index 000000000000..3d5d2ee2f1bc --- /dev/null +++ b/scipy/sparse/tests/test_arithmetic1d.py @@ -0,0 +1,338 @@ +"""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 + sp_mult = i.multiply(j) + 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'): + 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/scipy/sparse/tests/test_common1d.py b/scipy/sparse/tests/test_common1d.py index 1282f6b3c680..da2d010a1183 100644 --- a/scipy/sparse/tests/test_common1d.py +++ b/scipy/sparse/tests/test_common1d.py @@ -3,8 +3,9 @@ import pytest import numpy as np +from numpy.testing import assert_equal, assert_allclose -from scipy.sparse import coo_array, dok_array, SparseEfficiencyWarning +from scipy.sparse import coo_array, csr_array, dok_array, SparseEfficiencyWarning from scipy.sparse._sputils import supported_dtypes, matrix from scipy._lib._util import ComplexWarning @@ -13,7 +14,7 @@ sup_complex.filter(ComplexWarning) -spcreators = [coo_array, dok_array] +spcreators = [coo_array, csr_array, dok_array] math_dtypes = [np.int64, np.float64, np.complex128] @@ -36,9 +37,9 @@ class TestCommon1D: """test common functionality shared by 1D sparse formats""" def test_create_empty(self, spcreator): - assert np.array_equal(spcreator((3,)).toarray(), np.zeros(3)) - assert np.array_equal(spcreator((3,)).nnz, 0) - assert np.array_equal(spcreator((3,)).count_nonzero(), 0) + 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'): @@ -52,37 +53,37 @@ def test_str(self, spcreator, dat1d): def test_neg(self, spcreator): A = np.array([-1, 0, 17, 0, -5, 0, 1, -4, 0, 0, 0, 0], 'd') - assert np.array_equal(-A, (-spcreator(A)).toarray()) + assert_equal(-A, (-spcreator(A)).toarray()) 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 np.array_equal(y.toarray(), desired) + assert_equal(y.toarray(), desired) # remove a size-1 dimension 2d->1d x = spcreator(desired) y = x.reshape(12) - assert np.array_equal(y.toarray(), desired[0]) + 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 np.array_equal(y.toarray(), desired[0]) + 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 np.array_equal(y.toarray(), desired) + assert_equal(y.toarray(), desired) y = x.reshape((12,)) assert y is x y = x.reshape(12) - assert np.array_equal(y.toarray(), x.toarray()) + assert_equal(y.toarray(), x.toarray()) def test_sum(self, spcreator): np.random.seed(1234) @@ -96,10 +97,10 @@ def test_sum(self, spcreator): datsp = spcreator(dat) with np.errstate(over='ignore'): assert np.isscalar(datsp.sum()) - assert np.allclose(dat.sum(), datsp.sum()) - assert np.allclose(dat.sum(axis=None), datsp.sum(axis=None)) - assert np.allclose(dat.sum(axis=0), datsp.sum(axis=0)) - assert np.allclose(dat.sum(axis=-1), datsp.sum(axis=-1)) + 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(())) @@ -125,17 +126,17 @@ def test_numpy_sum(self, spcreator): dat_sum = np.sum(dat) datsp_sum = np.sum(datsp) - assert np.allclose(dat_sum, datsp_sum) + assert_allclose(dat_sum, datsp_sum) def test_mean(self, spcreator): dat = np.array([0, 1, 2]) datsp = spcreator(dat) - assert np.allclose(dat.mean(), datsp.mean()) + assert_allclose(dat.mean(), datsp.mean()) assert np.isscalar(datsp.mean(axis=None)) - assert np.allclose(dat.mean(axis=None), datsp.mean(axis=None)) - assert np.allclose(dat.mean(axis=0), datsp.mean(axis=0)) - assert np.allclose(dat.mean(axis=-1), datsp.mean(axis=-1)) + 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) @@ -146,11 +147,6 @@ 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]]) - if spcreator._format == 'uni': - with pytest.raises(ValueError, match='zq'): - spcreator(dat) - return - datsp = spcreator(dat) with pytest.raises(ValueError, match='axis out of range'): datsp.mean(axis=3) @@ -169,8 +165,8 @@ def test_sum_dtype(self, spcreator): dat_sum = dat.sum(dtype=dtype) datsp_sum = datsp.sum(dtype=dtype) - assert np.allclose(dat_sum, datsp_sum) - assert np.array_equal(dat_sum.dtype, datsp_sum.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]) @@ -180,8 +176,8 @@ def test_mean_dtype(self, spcreator): dat_mean = dat.mean(dtype=dtype) datsp_mean = datsp.mean(dtype=dtype) - assert np.allclose(dat_mean, datsp_mean) - assert np.array_equal(dat_mean.dtype, datsp_mean.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]) @@ -192,11 +188,11 @@ def test_mean_out(self, spcreator): dat.mean(out=dat_out, keepdims=True) datsp.mean(out=datsp_out) - assert np.allclose(dat_out, datsp_out) + assert_allclose(dat_out, datsp_out) dat.mean(axis=0, out=dat_out, keepdims=True) datsp.mean(axis=0, out=datsp_out) - assert np.allclose(dat_out, datsp_out) + assert_allclose(dat_out, datsp_out) def test_numpy_mean(self, spcreator): dat = np.array([0, 1, 2]) @@ -205,26 +201,26 @@ def test_numpy_mean(self, spcreator): dat_mean = np.mean(dat) datsp_mean = np.mean(datsp) - assert np.allclose(dat_mean, datsp_mean) - assert np.array_equal(dat_mean.dtype, datsp_mean.dtype) + assert_allclose(dat_mean, datsp_mean) + assert_equal(dat_mean.dtype, datsp_mean.dtype) @sup_complex def test_from_array(self, spcreator): A = np.array([2, 3, 4]) - assert np.array_equal(spcreator(A).toarray(), A) + assert_equal(spcreator(A).toarray(), A) A = np.array([1.0 + 3j, 0, -1]) - assert np.array_equal(spcreator(A).toarray(), A) - assert np.array_equal(spcreator(A, dtype='int16').toarray(), A.astype('int16')) + assert_equal(spcreator(A).toarray(), A) + assert_equal(spcreator(A, dtype='int16').toarray(), A.astype('int16')) @sup_complex def test_from_list(self, spcreator): A = [2, 3, 4] - assert np.array_equal(spcreator(A).toarray(), A) + assert_equal(spcreator(A).toarray(), A) A = [1.0 + 3j, 0, -1] - assert np.array_equal(spcreator(A).toarray(), np.array(A)) - assert np.array_equal( + assert_equal(spcreator(A).toarray(), np.array(A)) + assert_equal( spcreator(A, dtype='int16').toarray(), np.array(A).astype('int16') ) @@ -232,63 +228,63 @@ def test_from_list(self, spcreator): def test_from_sparse(self, spcreator): D = np.array([1, 0, 0]) S = coo_array(D) - assert np.array_equal(spcreator(S).toarray(), D) + assert_equal(spcreator(S).toarray(), D) S = spcreator(D) - assert np.array_equal(spcreator(S).toarray(), D) + assert_equal(spcreator(S).toarray(), D) D = np.array([1.0 + 3j, 0, -1]) S = coo_array(D) - assert np.array_equal(spcreator(S).toarray(), D) - assert np.array_equal(spcreator(S, dtype='int16').toarray(), D.astype('int16')) + assert_equal(spcreator(S).toarray(), D) + assert_equal(spcreator(S, dtype='int16').toarray(), D.astype('int16')) S = spcreator(D) - assert np.array_equal(spcreator(S).toarray(), D) - assert np.array_equal(spcreator(S, dtype='int16').toarray(), D.astype('int16')) + 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 np.array_equal(chk, dat1d) + assert_equal(chk, dat1d) assert chk.flags.c_contiguous == chk.flags.f_contiguous # Check C-contiguous (with arg). chk = datsp.toarray(order='C') - assert np.array_equal(chk, dat1d) + 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 np.array_equal(chk, dat1d) + 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 np.array_equal(out, dat1d) + assert_equal(out, dat1d) # Check that things are fine when we don't initialize with zeros. out[...] = 1.0 datsp.toarray(out=out) - assert np.array_equal(out, dat1d) + 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 np.array_equal(dense_dot_dense, check) + 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 np.array_equal(dense_dot_dense, check) + assert_equal(dense_dot_dense, check) # Check bool data works. spbool = spcreator(dat1d, dtype=bool) arrbool = dat1d.astype(bool) - assert np.array_equal(spbool.toarray(), arrbool) + assert_equal(spbool.toarray(), arrbool) def test_add(self, spcreator, datsp_math_dtypes): for dtype, dat, datsp in datsp_math_dtypes[spcreator]: @@ -296,12 +292,12 @@ def test_add(self, spcreator, datsp_math_dtypes): a[0] = 2.0 b = datsp c = b + a - assert np.array_equal(c, b.toarray() + 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 np.array_equal(c, b.toarray() + a[0]) + assert_equal(c, b.toarray() + a[0]) def test_radd(self, spcreator, datsp_math_dtypes): for dtype, dat, datsp in datsp_math_dtypes[spcreator]: @@ -309,7 +305,7 @@ def test_radd(self, spcreator, datsp_math_dtypes): a[0] = 2.0 b = datsp c = a + b - assert np.array_equal(c, a + b.toarray()) + assert_equal(c, a + b.toarray()) def test_rsub(self, spcreator, datsp_math_dtypes): for dtype, dat, datsp in datsp_math_dtypes[spcreator]: @@ -317,25 +313,25 @@ def test_rsub(self, spcreator, datsp_math_dtypes): # boolean array subtraction deprecated in 1.9.0 continue - assert np.array_equal((dat - datsp), [0, 0, 0, 0]) - assert np.array_equal((datsp - dat), [0, 0, 0, 0]) - assert np.array_equal((0 - datsp).toarray(), -dat) + 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 np.array_equal((dat - A), dat - A.toarray()) - assert np.array_equal((A - dat), A.toarray() - dat) - assert np.array_equal(A.toarray() - datsp, A.toarray() - dat) - assert np.array_equal(datsp - A.toarray(), dat - A.toarray()) + 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 np.array_equal(dat[:1] - datsp, dat[:1] - dat) + assert_equal(dat[:1] - datsp, dat[:1] - dat) def test_matvec(self, spcreator): A = np.array([2, 0, 3.0]) Asp = spcreator(A) col = np.array([[1, 2, 3]]).T - assert np.allclose(Asp @ col, Asp.toarray() @ col) + assert_allclose(Asp @ col, Asp.toarray() @ col) assert (A @ np.array([1, 2, 3])).shape == () assert Asp @ np.array([1, 2, 3]) == 11 @@ -354,29 +350,29 @@ def test_matvec(self, spcreator): # The current relationship between sparse matrix products and array # products is as follows: dot_result = np.dot(Asp.toarray(), [1, 2, 3]) - assert np.allclose(Asp @ np.array([1, 2, 3]), dot_result) - assert np.allclose(Asp @ [[1], [2], [3]], dot_result.T) + 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 np.allclose([1, 2, 3, 4] @ M, np.dot([1, 2, 3, 4], M.toarray())) + assert_allclose([1, 2, 3, 4] @ M, np.dot([1, 2, 3, 4], M.toarray())) row = np.array([[1, 2, 3, 4]]) - assert np.allclose(row @ M, row @ M.toarray()) + assert_allclose(row @ M, row @ M.toarray()) def test_transpose(self, spcreator, dat1d): for A in [dat1d, np.array([])]: B = spcreator(A) - assert np.array_equal(B.toarray(), A) - assert np.array_equal(B.transpose().toarray(), A) - assert np.array_equal(B.dtype, A.dtype) + 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 np.array_equal(sum1, dat + dat) + assert_equal(sum1, dat + dat) sum2 = datsp + dat - assert np.array_equal(sum2, dat + dat) + assert_equal(sum2, dat + dat) def test_iterator(self, spcreator): # test that __iter__ is compatible with NumPy @@ -385,19 +381,19 @@ def test_iterator(self, spcreator): if A.format not in ['coo', 'dia', 'bsr']: for x, y in zip(A, B): - assert np.array_equal(x, y) + 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 np.array_equal(S.toarray(), [1, 0, 3]) + assert_equal(S.toarray(), [1, 0, 3]) S.resize((5,)) - assert np.array_equal(S.toarray(), [1, 0, 3, 0, 0]) + assert_equal(S.toarray(), [1, 0, 3, 0, 0]) -@pytest.mark.parametrize("spcreator", [dok_array]) +@pytest.mark.parametrize("spcreator", [csr_array, dok_array]) class TestGetSet1D: def test_getelement(self, spcreator): D = np.array([4, 3, 0]) @@ -405,7 +401,7 @@ def test_getelement(self, spcreator): N = D.shape[0] for j in range(-N, N): - assert np.array_equal(A[j], D[j]) + assert_equal(A[j], D[j]) for ij in [3, -4]: with pytest.raises( diff --git a/scipy/sparse/tests/test_coo.py b/scipy/sparse/tests/test_coo.py index b9c80e9e16d6..ec00e1122913 100644 --- a/scipy/sparse/tests/test_coo.py +++ b/scipy/sparse/tests/test_coo.py @@ -1,4 +1,5 @@ import numpy as np +from numpy.testing import assert_equal import pytest from scipy.sparse import coo_array @@ -6,11 +7,11 @@ def test_shape_constructor(): empty1d = coo_array((3,)) assert empty1d.shape == (3,) - assert np.array_equal(empty1d.toarray(), np.zeros((3,))) + assert_equal(empty1d.toarray(), np.zeros((3,))) empty2d = coo_array((3, 2)) assert empty2d.shape == (3, 2) - assert np.array_equal(empty2d.toarray(), np.zeros((3, 2))) + assert_equal(empty2d.toarray(), np.zeros((3, 2))) with pytest.raises(TypeError, match='invalid input format'): coo_array((3, 2, 2)) @@ -19,11 +20,11 @@ def test_shape_constructor(): def test_dense_constructor(): res1d = coo_array([1, 2, 3]) assert res1d.shape == (3,) - assert np.array_equal(res1d.toarray(), np.array([1, 2, 3])) + assert_equal(res1d.toarray(), np.array([1, 2, 3])) res2d = coo_array([[1, 2, 3], [4, 5, 6]]) assert res2d.shape == (2, 3) - assert np.array_equal(res2d.toarray(), np.array([[1, 2, 3], [4, 5, 6]])) + assert_equal(res2d.toarray(), np.array([[1, 2, 3], [4, 5, 6]])) with pytest.raises(ValueError, match='shape must be a 1- or 2-tuple'): coo_array([[[3]], [[4]]]) @@ -32,11 +33,11 @@ def test_dense_constructor(): def test_dense_constructor_with_shape(): res1d = coo_array([1, 2, 3], shape=(3,)) assert res1d.shape == (3,) - assert np.array_equal(res1d.toarray(), np.array([1, 2, 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 np.array_equal(res2d.toarray(), np.array([[1, 2, 3], [4, 5, 6]])) + assert_equal(res2d.toarray(), np.array([[1, 2, 3], [4, 5, 6]])) with pytest.raises(ValueError, match='shape must be a 1- or 2-tuple'): coo_array([[[3]], [[4]]], shape=(2, 1, 1)) @@ -64,19 +65,19 @@ def test_1d_sparse_constructor(): empty1d = coo_array((3,)) res = coo_array(empty1d) assert res.shape == (3,) - assert np.array_equal(res.toarray(), np.zeros((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 np.array_equal(res.toarray(), np.array([0, 9, 8])) + 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 np.array_equal(res.toarray(), np.array([0, 9, 8, 0])) + assert_equal(res.toarray(), np.array([0, 9, 8, 0])) def test_non_subscriptability(): coo_2d = coo_array((2, 2)) @@ -95,18 +96,18 @@ def test_reshape(): col_vec = arr1d.reshape((3, 1)) assert col_vec.shape == (3, 1) - assert np.array_equal(col_vec.toarray(), np.array([[1], [0], [3]])) + assert_equal(col_vec.toarray(), np.array([[1], [0], [3]])) row_vec = arr1d.reshape((1, 3)) assert row_vec.shape == (1, 3) - assert np.array_equal(row_vec.toarray(), np.array([[1, 0, 3]])) + assert_equal(row_vec.toarray(), np.array([[1, 0, 3]])) arr2d = coo_array([[1, 2, 0], [0, 0, 3]]) assert arr2d.shape == (2, 3) flat = arr2d.reshape((6,)) assert flat.shape == (6,) - assert np.array_equal(flat.toarray(), np.array([1, 2, 0, 0, 0, 3])) + assert_equal(flat.toarray(), np.array([1, 2, 0, 0, 0, 3])) def test_nnz(): @@ -122,21 +123,21 @@ def test_nnz(): def test_transpose(): arr1d = coo_array([1, 0, 3]).T assert arr1d.shape == (3,) - assert np.array_equal(arr1d.toarray(), np.array([1, 0, 3])) + assert_equal(arr1d.toarray(), np.array([1, 0, 3])) arr2d = coo_array([[1, 2, 0], [0, 0, 3]]).T assert arr2d.shape == (3, 2) - assert np.array_equal(arr2d.toarray(), np.array([[1, 0], [2, 0], [0, 3]])) + assert_equal(arr2d.toarray(), np.array([[1, 0], [2, 0], [0, 3]])) def test_transpose_with_axis(): arr1d = coo_array([1, 0, 3]).transpose(axes=(0,)) assert arr1d.shape == (3,) - assert np.array_equal(arr1d.toarray(), np.array([1, 0, 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 np.array_equal(arr2d.toarray(), np.array([[1, 2, 0], [0, 0, 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)) @@ -147,14 +148,14 @@ def test_transpose_with_axis(): def test_1d_row_and_col(): res = coo_array([1, -2, -3]) - assert np.array_equal(res.col, np.array([0, 1, 2])) - assert np.array_equal(res.row, np.zeros_like(res.col)) + 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 np.array_equal(res.col, np.array([1, 2, 3])) + 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"): @@ -163,11 +164,11 @@ def test_1d_row_and_col(): def test_1d_toformats(): res = coo_array([1, -2, -3]) - for f in [res.tocsc, res.tocsr, res.todia, res.tolil, res.tobsr]: + 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.todok]: - assert np.array_equal(f().toarray(), res.toarray()) + for f in [res.tocoo, res.tocsr, res.todok]: + assert_equal(f().toarray(), res.toarray()) @pytest.mark.parametrize('arg', [1, 2, 4, 5, 8]) @@ -177,7 +178,7 @@ def test_1d_resize(arg: int): den.resize(arg, refcheck=False) res.resize(arg) assert res.shape == den.shape - assert np.array_equal(res.toarray(), den) + assert_equal(res.toarray(), den) @pytest.mark.parametrize('arg', zip([1, 2, 3, 4], [1, 2, 3, 4])) @@ -188,7 +189,7 @@ def test_1d_to_2d_resize(arg: tuple[int, int]): den.resize(arg, refcheck=False) res.resize(arg) assert res.shape == den.shape - assert np.array_equal(res.toarray(), den) + assert_equal(res.toarray(), den) @pytest.mark.parametrize('arg', [1, 4, 6, 8]) @@ -198,29 +199,29 @@ def test_2d_to_1d_resize(arg: int): den.resize(arg, refcheck=False) res.resize(arg) assert res.shape == den.shape - assert np.array_equal(res.toarray(), den) + assert_equal(res.toarray(), den) def test_sum_duplicates(): arr1d = coo_array(([2, 2, 2], ([1, 0, 1],))) assert arr1d.nnz == 3 - assert np.array_equal(arr1d.toarray(), np.array([2, 4])) + assert_equal(arr1d.toarray(), np.array([2, 4])) arr1d.sum_duplicates() assert arr1d.nnz == 2 - assert np.array_equal(arr1d.toarray(), np.array([2, 4])) + assert_equal(arr1d.toarray(), np.array([2, 4])) def test_eliminate_zeros(): arr1d = coo_array(([0, 0, 1], ([1, 0, 1],))) assert arr1d.nnz == 3 assert arr1d.count_nonzero() == 1 - assert np.array_equal(arr1d.toarray(), np.array([0, 1])) + assert_equal(arr1d.toarray(), np.array([0, 1])) arr1d.eliminate_zeros() assert arr1d.nnz == 1 assert arr1d.count_nonzero() == 1 - assert np.array_equal(arr1d.toarray(), np.array([0, 1])) - assert np.array_equal(arr1d.col, np.array([1])) - assert np.array_equal(arr1d.row, np.array([0])) + 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(): @@ -229,17 +230,16 @@ def test_1d_add_dense(): exp = den_a + den_b res = coo_array(den_a) + den_b assert type(res) == type(exp) - assert np.array_equal(res, 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]) - # Currently this routes through CSR format, so 1d sparse addition - # isn't supported. - with pytest.raises(ValueError, - match='Cannot convert a 1d sparse array'): - coo_array(den_a) + coo_array(den_b) + 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(): @@ -248,7 +248,7 @@ def test_1d_matmul_vector(): exp = den_a @ den_b res = coo_array(den_a) @ den_b assert np.ndim(res) == 0 - assert np.array_equal(res, exp) + assert_equal(res, exp) def test_1d_matmul_multivector(): @@ -257,7 +257,7 @@ def test_1d_matmul_multivector(): exp = den @ other res = coo_array(den) @ other assert type(res) == type(exp) - assert np.array_equal(res, exp) + assert_equal(res, exp) def test_2d_matmul_multivector(): @@ -265,7 +265,7 @@ def test_2d_matmul_multivector(): arr2d = coo_array(den) exp = den @ den.T res = arr2d @ arr2d.T - assert np.array_equal(res.toarray(), exp) + assert_equal(res.toarray(), exp) def test_1d_diagonal(): From 4c5cafcc7ce2efc8807995a04f2cd7aada398223 Mon Sep 17 00:00:00 2001 From: Christian Lorentzen Date: Tue, 7 May 2024 19:12:47 +0200 Subject: [PATCH 102/500] ENH: special: add `log_wright_bessel` (#20646) * ENH minor improvements to wright_bessel - wb_large_a more numerically stabel - wb_small_a factored out C and X coefficients - comments improved * ENH add log_wright_bessel * DOC add versionadded and fix example --- scipy/special/__init__.py | 47 ++--- scipy/special/_generate_pyx.py | 12 +- scipy/special/_special_ufuncs.cpp | 7 + scipy/special/_special_ufuncs_docs.cpp | 39 ++++ scipy/special/_ufuncs.pyi | 2 + scipy/special/cython_special.pxd | 1 + scipy/special/cython_special.pyx | 9 + scipy/special/special/wright_bessel.h | 228 ++++++++++++++------- scipy/special/special_wrappers.cpp | 1 + scipy/special/special_wrappers.h | 1 + scipy/special/tests/test_cython_special.py | 1 + scipy/special/tests/test_wright_bessel.py | 92 ++++++++- 12 files changed, 339 insertions(+), 101 deletions(-) diff --git a/scipy/special/__init__.py b/scipy/special/__init__.py index d0fa180bf8e5..39db38ec110f 100644 --- a/scipy/special/__init__.py +++ b/scipy/special/__init__.py @@ -76,29 +76,30 @@ .. 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. - 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. - iv -- Modified Bessel function of the first kind of real order. - ive -- Exponentially scaled modified Bessel function of the \ - first 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. + 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. + 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. + iv -- Modified Bessel function of the first kind of real order. + ive -- Exponentially scaled modified Bessel function of the \ + first 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): diff --git a/scipy/special/_generate_pyx.py b/scipy/special/_generate_pyx.py index 9f64eb4fd5f0..0ba659fb78a1 100644 --- a/scipy/special/_generate_pyx.py +++ b/scipy/special/_generate_pyx.py @@ -83,12 +83,12 @@ 'hankel1e', 'hankel2', 'hankel2e', 'hyp2f1', 'it2i0k0', 'it2j0y0', 'it2struve0', 'itairy', 'iti0k0', 'itj0y0', 'itmodstruve0', 'itstruve0', 'iv', 'ive', 'jv', 'jve', 'kei', 'keip', 'kelvin', 'ker', 'kerp', 'kv', 'kve', 'log_expit', - 'loggamma', 'logit', 'mathieu_a', 'mathieu_b', 'mathieu_cem', 'mathieu_modcem1', - 'mathieu_modcem2', 'mathieu_modsem1', 'mathieu_modsem2', 'mathieu_sem', - 'modfresnelm', 'modfresnelp', 'obl_ang1', 'obl_ang1_cv', 'obl_cv', 'obl_rad1', - 'obl_rad1_cv', 'obl_rad2', 'obl_rad2_cv', 'pbdv', 'pbvv', 'pbwa', 'pro_ang1', - 'pro_ang1_cv', 'pro_cv', 'pro_rad1', 'pro_rad1_cv', 'pro_rad2', 'pro_rad2_cv', - 'psi', 'rgamma', 'sph_harm', 'wright_bessel', 'yv', 'yve', '_zeta' + 'log_wright_bessel', 'loggamma', 'logit', 'mathieu_a', 'mathieu_b', 'mathieu_cem', + 'mathieu_modcem1', 'mathieu_modcem2', 'mathieu_modsem1', 'mathieu_modsem2', + 'mathieu_sem', 'modfresnelm', 'modfresnelp', 'obl_ang1', 'obl_ang1_cv', 'obl_cv', + 'obl_rad1', 'obl_rad1_cv', 'obl_rad2', 'obl_rad2_cv', 'pbdv', 'pbvv', 'pbwa', + 'pro_ang1', 'pro_ang1_cv', 'pro_cv', 'pro_rad1', 'pro_rad1_cv', 'pro_rad2', + 'pro_rad2_cv', 'psi', 'rgamma', 'sph_harm', 'wright_bessel', 'yv', 'yve', '_zeta' ] # ----------------------------------------------------------------------------- diff --git a/scipy/special/_special_ufuncs.cpp b/scipy/special/_special_ufuncs.cpp index 50824cc6778b..b74feafb5f9a 100644 --- a/scipy/special/_special_ufuncs.cpp +++ b/scipy/special/_special_ufuncs.cpp @@ -153,6 +153,7 @@ extern const char *lambertw_doc; extern const char *logit_doc; extern const char *loggamma_doc; extern const char *log_expit_doc; +extern const char *log_wright_bessel_doc; extern const char *mathieu_a_doc; extern const char *mathieu_b_doc; extern const char *mathieu_cem_doc; @@ -480,6 +481,12 @@ PyMODINIT_FUNC PyInit__special_ufuncs() { ); PyModule_AddObjectRef(_special_ufuncs, "log_expit", log_expit); + PyObject *log_wright_bessel = SpecFun_NewUFunc( + {static_cast(special::log_wright_bessel), static_cast(special::log_wright_bessel)}, + "log_wright_bessel", log_wright_bessel_doc + ); + PyModule_AddObjectRef(_special_ufuncs, "log_wright_bessel", log_wright_bessel); + PyObject *logit = SpecFun_NewUFunc( {static_cast(special::logit), static_cast(special::logit), static_cast(special::logit)}, diff --git a/scipy/special/_special_ufuncs_docs.cpp b/scipy/special/_special_ufuncs_docs.cpp index 0965b35fb6ae..a18fd29d1728 100644 --- a/scipy/special/_special_ufuncs_docs.cpp +++ b/scipy/special/_special_ufuncs_docs.cpp @@ -2754,6 +2754,43 @@ const char *log_expit_doc = R"( lose all precision and return 0. )"; +const char *log_wright_bessel_doc = R"( + log_wright_bessel(a, b, x, out=None) + + Natural logarithm of Wright's generalized Bessel function, see `wright_bessel`. + This function comes in handy in particular for large values of x. + + Parameters + ---------- + a : array_like of float + a >= 0 + b : array_like of float + b >= 0 + x : array_like of float + x >= 0 + out : ndarray, optional + Optional output array for the function results + + Returns + ------- + scalar or ndarray + Value of the logarithm of Wright's generalized Bessel function + + Notes + ----- + Due to the complexity of the function with its three parameters, only + non-negative arguments are implemented. + + .. versionadded:: 1.14.0 + + Examples + -------- + >>> from scipy.special import log_wright_bessel + >>> a, b, x = 1.5, 1.1, 2.5 + >>> log_wright_bessel(a, b, x) + 1.1947654935299217 + )"; + const char *mathieu_a_doc = R"( mathieu_a(m, q, out=None) @@ -3900,6 +3937,8 @@ const char *wright_bessel_doc = R"( Due to the complexity of the function with its three parameters, only non-negative arguments are implemented. + .. versionadded:: 1.7.0 + References ---------- .. [1] Digital Library of Mathematical Functions, 10.46. diff --git a/scipy/special/_ufuncs.pyi b/scipy/special/_ufuncs.pyi index e746f2ebe414..3f0cf70149ae 100644 --- a/scipy/special/_ufuncs.pyi +++ b/scipy/special/_ufuncs.pyi @@ -151,6 +151,7 @@ __all__ = [ 'log1p', 'log_expit', 'log_ndtr', + 'log_wright_bessel', 'loggamma', 'logit', 'lpmv', @@ -432,6 +433,7 @@ 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 diff --git a/scipy/special/cython_special.pxd b/scipy/special/cython_special.pxd index cde9e6b18572..731c17b30b2c 100644 --- a/scipy/special/cython_special.pxd +++ b/scipy/special/cython_special.pxd @@ -255,4 +255,5 @@ 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 \ No newline at end of file diff --git a/scipy/special/cython_special.pyx b/scipy/special/cython_special.pyx index 18051274b5eb..187f2505f365 100644 --- a/scipy/special/cython_special.pyx +++ b/scipy/special/cython_special.pyx @@ -1043,6 +1043,10 @@ Available functions double wright_bessel(double, double, double) +- :py:func:`~scipy.special.log_wright_bessel`:: + + double log_wright_bessel(double, double, double) + - :py:func:`~scipy.special.ndtri_exp`:: double ndtri_exp(double) @@ -1261,6 +1265,7 @@ cdef extern from r"special_wrappers.h": double _func_cephes_iv_wrap "cephes_iv_wrap"(double, double) nogil npy_double special_wright_bessel(npy_double, npy_double, npy_double) nogil + npy_double special_log_wright_bessel(npy_double, npy_double, npy_double) nogil double special_ellipk(double m) nogil double cephes_besselpoly(double a, double lmbda, double nu) nogil @@ -3508,6 +3513,10 @@ cpdef double wright_bessel(double x0, double x1, double x2) noexcept nogil: """See the documentation for scipy.special.wright_bessel""" return special_wright_bessel(x0, x1, x2) +cpdef double log_wright_bessel(double x0, double x1, double x2) noexcept nogil: + """See the documentation for scipy.special.log_wright_bessel""" + return special_log_wright_bessel(x0, x1, x2) + cpdef double ndtri_exp(double x0) noexcept nogil: """See the documentation for scipy.special.ndtri_exp""" return _func_ndtri_exp(x0) diff --git a/scipy/special/special/wright_bessel.h b/scipy/special/special/wright_bessel.h index b97f0864d5c6..2d21771dde87 100644 --- a/scipy/special/special/wright_bessel.h +++ b/scipy/special/special/wright_bessel.h @@ -68,10 +68,11 @@ namespace detail { return res; } + template SPECFUN_HOST_DEVICE inline double wb_large_a(double a, double b, double x, int n) { /* 2. Taylor series expansion in x=0, for large a. * - * Phi(a, b, x) = sum_k x^k / k! / Gamma(a*k + b). + * Phi(a, b, x) = sum_k x^k / k! / Gamma(a*k + b) * * Use Stirling's formula to find k=k_max, the maximum term. * Then use n terms of Taylor series around k_max. @@ -85,13 +86,23 @@ namespace detail { double res = 0; double lnx = std::log(x); + // For numerical stability, we factor out the maximum term exp(..) with k=k_max + // but only if it is larger than 0. + double max_exponent = std::fmax(0, k_max * lnx - cephes::lgam(k_max + 1) - cephes::lgam(a * k_max + b)); for (int k = nstart; k < nstart + n; k++) { - res += std::exp(k * lnx - cephes::lgam(k + 1) - cephes::lgam(a * k + b)); + res += std::exp(k * lnx - cephes::lgam(k + 1) - cephes::lgam(a * k + b) - max_exponent); } + if (!log_wb) { + res *= std::exp(max_exponent); + } else { + // logarithm of Wright's function + res = max_exponent + std::log(res); + } return res; } + template SPECFUN_HOST_DEVICE inline double wb_small_a(double a, double b, double x, int order) { /* 3. Taylor series in a=0 up to order 5, for tiny a and not too large x * @@ -115,8 +126,21 @@ namespace detail { */ double A[6]; // coefficients of a^k (1, -x * Psi(b), ...) double B[6]; // powers of b^k/k! or terms in polygamma functions - double C[6]; // coefficients of a^k1 * b^k2 - double X[6]; // polynomials in x; + constexpr double C[5] = { // coefficients of a^k1 * b^k2 + 1.0000000000000000, // C[0] + 1.1544313298030657, // C[1] + -3.9352684291215233, // C[2] + -1.0080632408182857, // C[3] + 19.984633365874979, // C[4] + }; + double X[6] = { // polynomials in x; + 1, // X[0] + x, // X[1] + x * (x + 1), // X[2] + x * (x * (x + 3) + 1), // X[3] + x * (x * (x * (x + 6) + 7) + 1), // X[4] + x * (x * (x * (x * (x + 10) + 25) + 15) + 1), // X[5] + }; double res; if (b <= 1E-3) { @@ -124,35 +148,30 @@ namespace detail { * M_PI = pi * M_EG = Euler Gamma aka Euler Mascheroni constant * M_Z3 = zeta(3) - * C[0] = 1 */ - C[0] = 1.0000000000000000; - // C[1] = 2*M_EG - C[1] = 1.1544313298030657; - // C[2] = 3*M_EG^2 - M_PI^2/2 - C[2] = -3.9352684291215233; - // C[3] = 4*M_EG^3 - 2*M_EG*M_PI^2 + 8*M_Z3 - C[3] = -1.0080632408182857; - // C[4] = 5*M_EG^4 - 5*M_EG^2*M_PI^2 + 40*M_EG*M_Z3 + M_PI^4/12 - C[4] = 19.984633365874979; - X[0] = 1; - X[1] = x; - X[2] = x * (x + 1); - X[3] = x * (x * (x + 3) + 1); - X[4] = x * (x * (x * (x + 6) + 7) + 1); - X[5] = x * (x * (x * (x * (x + 10) + 25) + 15) + 1); + * C[0] = 1 + * C[1] = 2*M_EG + * C[2] = 3*M_EG^2 - M_PI^2/2 + * C[3] = 4*M_EG^3 - 2*M_EG*M_PI^2 + 8*M_Z3 + * C[4] = 5*M_EG^4 - 5*M_EG^2*M_PI^2 + 40*M_EG*M_Z3 + M_PI^4/12 + */ B[0] = 1.; for (int k = 1; k < 5; k++) { B[k] = b / k * B[k - 1]; } // Note that polevl assumes inverse ordering => A[5] = 0th term A[5] = cephes::rgamma(b); - A[4] = x * (C[0] + C[1] * b + C[2] * B[2] + C[3] * B[3] + C[4] * B[4]); - A[3] = X[2] / 2. * (C[1] + C[2] * b + C[3] * B[2] + C[4] * B[3]); - A[2] = X[3] / 6. * (C[2] + C[3] * b + C[4] * B[2]); - A[1] = X[4] / 24. * (C[3] + C[4] * b); + A[4] = X[1] * (C[0] + C[1] * b + C[2] * B[2] + C[3] * B[3] + C[4] * B[4]); + A[3] = X[2] / 2. * (C[1] + C[2] * b + C[3] * B[2] + C[4] * B[3]); + A[2] = X[3] / 6. * (C[2] + C[3] * b + C[4] * B[2]); + A[1] = X[4] / 24. * (C[3] + C[4] * b); A[0] = X[5] / 120. * C[4]; - res = exp(x) * cephes::polevl(a, A, 5); - // res = exp(x) * (A[5] + A[4] * a + A[3] * a^2 + A[2] * a^3) + // res = exp(x) * (A[5] + A[4] * a + A[3] * a^2 + A[2] * a^3 + ...) + if (!log_wb) { + res = exp(x) * cephes::polevl(a, A, 5); + } else { + // logarithm of Wright's function + res = x + std::log(cephes::polevl(a, A, 5)); + } } else { /* Phi(a, b, x) = exp(x)/gamma(b) * sum(A[i] * X[i] * B[i], i=0..5) * A[n] = a^n/n! @@ -169,10 +188,6 @@ namespace detail { } // pg2 = polygamma(2, b) double pg2 = -2 * cephes::zeta(3, b); - X[0] = 1; - X[1] = x; - X[2] = x * (x + 1); - X[3] = x * (x * (x + 3) + 1); B[0] = 1; B[1] = -dg; B[2] = dg * dg - pg1; @@ -184,13 +199,11 @@ namespace detail { if (order >= 4) { // double pg3 = polygamma(3, b) double pg3 = 6 * cephes::zeta(4, b); - X[4] = x * (x * (x * (x + 6) + 7) + 1); B[4] = ((dg * dg - 6 * pg1) * dg + 4 * pg2) * dg + 3 * pg1 * pg1 - pg3; A[order - 4] = X[4] * B[4] / 24.; if (order >= 5) { // pg4 = polygamma(4, b) double pg4 = -24 * cephes::zeta(5, b); - X[5] = x * (x * (x * (x * (x + 10) + 25) + 15) + 1); B[5] = ((((-dg * dg + 10 * pg1) * dg - 10 * pg2) * dg - 15 * pg1 * pg1 + 5 * pg3) * dg + 10 * pg1 * pg2 - pg4); @@ -200,11 +213,17 @@ namespace detail { res = cephes::polevl(a, A, order); } // res *= exp(x) * rgamma(b) - res *= exp_rgamma(x, b); + if (!log_wb) { + res *= exp_rgamma(x, b); + } else { + // logarithm of Wright's function + res = x - cephes::lgam(b) + std::log(res); + } } return res; } + template SPECFUN_HOST_DEVICE inline double wb_asymptotic(double a, double b, double x) { /* 4. Asymptotic expansion for large x up to order 8 * @@ -425,30 +444,43 @@ namespace detail { Zp /= Z; res += (k % 2 == 0 ? 1 : -1) * C[k] * Zp; } - res *= std::pow(Z, 0.5 - b) * std::exp(Ap1[1] / a * Z); + if (!log_wb) { + res *= std::pow(Z, 0.5 - b) * std::exp(Ap1[1] / a * Z); + } else { + // logarithm of Wright's function + res = std::log(Z) * (0.5 - b) + Ap1[1] / a * Z + std::log(res); + } return res; } - SPECFUN_HOST_DEVICE inline double wb_Kmod(double eps, double a, double b, double x, double r) { + SPECFUN_HOST_DEVICE inline double wb_Kmod(double exp_term, double eps, double a, double b, double x, double r) { /* Compute integrand Kmod(eps, a, b, x, r) for Gauss-Laguerre quadrature. * * K(a, b, x, r+eps) = exp(-r-eps) * Kmod(eps, a, b, x, r) + * + * Kmod(eps, a, b, x, r) = exp(x * (r+eps)^(-a) * cos(pi*a)) * (r+eps)^(-b) + * * sin(x * (r+eps)^(-a) * sin(pi*a) + pi * b) + * + * Note that we additionally factor out exp(exp_term) which helps with large + * terms in the exponent of exp(...) */ double x_r_a = x * std::pow(r + eps, -a); - return std::exp(x_r_a * cephes::cospi(a)) * std::pow(r + eps, -b) * + return std::exp(x_r_a * cephes::cospi(a) + exp_term) * std::pow(r + eps, -b) * std::sin(x_r_a * cephes::sinpi(a) + M_PI * b); } - SPECFUN_HOST_DEVICE inline double wb_P(double eps, double a, double b, double x, double phi) { + SPECFUN_HOST_DEVICE inline double wb_P(double exp_term, double eps, double a, double b, double x, double phi) { /* Compute integrand P for Gauss-Legendre quadrature. * - * P(eps, a, b, x, phi) = - * * exp(eps * cos(phi) + x * eps^(-a) * cos(a*phi)) + * P(eps, a, b, x, phi) = exp(eps * cos(phi) + x * eps^(-a) * cos(a*phi)) * * cos(eps * sin(phi) - x * eps^(-a) * sin(a*phi) - + (1-b)*phi) - */ + * + (1-b)*phi) + * + * Note that we additionally factor out exp(exp_term) which helps with large + * terms in the exponent of exp(...) + */ double x_eps_a = x * std::pow(eps, -a); - return std::exp(eps * std::cos(phi) + x_eps_a * std::cos(a * phi)) * + return std::exp(eps * std::cos(phi) + x_eps_a * std::cos(a * phi) + exp_term) * std::cos(eps * std::sin(phi) - x_eps_a * std::sin(a * phi) + (1 - b) * phi); } @@ -516,13 +548,13 @@ namespace detail { * Call: python _precompute/wright_bessel.py 4 */ constexpr double wb_A[] = {0.41037, 0.30833, 6.9952, 18.382, -2.8566, 2.1122}; + template SPECFUN_HOST_DEVICE inline double wright_bessel_integral(double a, double b, double x) { /* 5. Integral representation * * K(a, b, x, r) = exp(-r + x * r^(-a) * cos(pi*a)) * r^(-b) - * * sin(x * r^(-a) * sin(pi*a) + pi * b) - * P(eps, a, b, x, phi) = - * * exp(eps * cos(phi) + x * eps^(-a) * cos(a*phi)) + * * sin(x * r^(-a) * sin(pi*a) + pi * b) + * P(eps, a, b, x, phi) = exp(eps * cos(phi) + x * eps^(-a) * cos(a*phi)) * * cos(eps * sin(phi) - x * eps^(-a) * sin(a*phi) * + (1-b)*phi) * @@ -531,10 +563,14 @@ namespace detail { * * for any eps > 0. * - * Note that P has a misprint in Luchko (2008). + * Note that P has a misprint in Luchko (2008) Eq. 9, the cos(phi(beta-1)) at + * the end of the first line should be removed. * This integral representation introduced the free parameter eps (from the * radius of complex contour integration). We try to choose eps such that - * the integrand behaves smoothly. + * the integrand behaves smoothly. Note that this is quite diffrent from how + * Luchko (2008) deals with eps: he is either looking for the limit eps -> 0 + * or he sets (silently) eps=1. But having the freedom to set eps is much more + * powerful for numerical evaluation. * * As K has a leading exp(-r), we factor this out and apply Gauss-Laguerre * quadrature rule: @@ -581,25 +617,47 @@ namespace detail { eps = std::fmin(eps, 150.); eps = std::fmax(eps, 3.); // 3 seems to be a pretty good choice in general. + // We factor out exp(-exp_term) from wb_Kmod and wb_P to avoid overflow of + // exp(..). + double exp_term = 0; + // From the exponent of K: + double r = wb_x_laguerre[50-1]; // largest value of x used in wb_Kmod + double x_r_a = x * std::pow(r + eps, -a); + exp_term = std::fmax(exp_term, x_r_a * cephes::cospi(a)); + // From the exponent of P: + double x_eps_a = x * std::pow(eps, -a); + // phi = 0 => cos(phi) = cos(a * phi) = 1 + exp_term = std::fmax(exp_term, eps + x_eps_a); + // phi = pi => cos(phi) = -1 + exp_term = std::fmax(exp_term, -eps + x_eps_a * cephes::cospi(a)); + double res1 = 0; double res2 = 0; double y; for (int k = 0; k < 50; k++) { - res1 += wb_w_laguerre[k] * wb_Kmod(eps, a, b, x, wb_x_laguerre[k]); + res1 += wb_w_laguerre[k] * wb_Kmod(-exp_term, eps, a, b, x, wb_x_laguerre[k]); // y = (b-a)*(x+1)/2.0 + a for integration from a=0 to b=pi y = M_PI * (wb_x_legendre[k] + 1) / 2.0; - res2 += wb_w_legendre[k] * wb_P(eps, a, b, x, y); + res2 += wb_w_legendre[k] * wb_P(-exp_term, eps, a, b, x, y); } res1 *= std::exp(-eps); // (b-a)/2.0 * np.sum(w*func(y, *args), axis=-1) res2 *= M_PI / 2.0; res2 *= std::pow(eps, 1 - b); - return 1. / M_PI * (res1 + res2); + + if (!log_wb) { + // Remember the factored out exp_term from wb_Kmod and wb_P + return std::exp(exp_term) / M_PI * (res1 + res2); + } else { + // logarithm of Wright's function + return exp_term + std::log((res1 + res2) / M_PI); + } } } // namespace detail -SPECFUN_HOST_DEVICE inline double wright_bessel(double a, double b, double x) { +template +SPECFUN_HOST_DEVICE inline double wright_bessel_t(double a, double b, double x) { /* Compute Wright's generalized Bessel function for scalar arguments. * * According to [1], it is an entire function defined as @@ -610,27 +668,27 @@ SPECFUN_HOST_DEVICE inline double wright_bessel(double a, double b, double x) { * There are 5 different approaches depending on the ranges of the arguments: * * 1. Taylor series expansion in x=0 [1], for x <= 1. - * Involves gamma funtions in each term. + * Involves gamma funtions in each term. * 2. Taylor series expansion in x=0 [2], for large a. * 3. Taylor series in a=0, for tiny a and not too large x. * 4. Asymptotic expansion for large x [3, 4]. - * Suitable for large x while still small a and b. + * Suitable for large x while still small a and b. * 5. Integral representation [5], in principle for all arguments. * * References * ---------- * [1] https://dlmf.nist.gov/10.46.E1 * [2] P. K. Dunn, G. K. Smyth (2005), Series evaluation of Tweedie exponential - * dispersion model densities. Statistics and Computing 15 (2005): 267-280. + * dispersion model densities. Statistics and Computing 15 (2005): 267-280. * [3] E. M. Wright (1935), The asymptotic expansion of the generalized Bessel - * function. Proc. London Math. Soc. (2) 38, pp. 257-270. - * https://doi.org/10.1112/plms/s2-38.1.257 + * function. Proc. London Math. Soc. (2) 38, pp. 257-270. + * https://doi.org/10.1112/plms/s2-38.1.257 * [4] R. B. Paris (2017), The asymptotics of the generalised Bessel function, - * Mathematica Aeterna, Vol. 7, 2017, no. 4, 381 - 406, - * https://arxiv.org/abs/1711.03006 + * Mathematica Aeterna, Vol. 7, 2017, no. 4, 381 - 406, + * https://arxiv.org/abs/1711.03006 * [5] Y. F. Luchko (2008), Algorithms for Evaluation of the Wright Function for - * the Real Arguments' Values, Fractional Calculus and Applied Analysis 11(1) - * http://sci-gems.math.bas.bg/jspui/bitstream/10525/1298/1/fcaa-vol11-num1-2008-57p-75p.pdf + * the Real Arguments' Values, Fractional Calculus and Applied Analysis 11(1) + * http://sci-gems.math.bas.bg/jspui/bitstream/10525/1298/1/fcaa-vol11-num1-2008-57p-75p.pdf */ if (std::isnan(a) || std::isnan(b) || std::isnan(x)) { return std::numeric_limits::quiet_NaN(); @@ -653,17 +711,28 @@ SPECFUN_HOST_DEVICE inline double wright_bessel(double a, double b, double x) { return std::numeric_limits::quiet_NaN(); } if (x == 0) { - return cephes::rgamma(b); + // return rgamma(b) + if (!log_wb) { + return cephes::rgamma(b); + } else { + // logarithm of Wright's function + return -cephes::lgam(b); + } } if (a == 0) { // return exp(x) * rgamma(b) - return detail::exp_rgamma(x, b); + if (!log_wb) { + return detail::exp_rgamma(x, b); + } else { + // logarithm of Wright's function + return x - cephes::lgam(b); + } } constexpr double exp_inf = 709.78271289338403; int order; if ((a <= 1e-3 && b <= 50 && x <= 9) || (a <= 1e-4 && b <= 70 && x <= 100) || - (a <= 1e-5 and b <= 170 and x < exp_inf)) { + (a <= 1e-5 && b <= 170 && (x < exp_inf || (log_wb && x <= 1e3)))) { /* Taylor Series expansion in a=0 to order=order => precision <= 1e-11 * If beta is also small => precision <= 1e-11. * max order = 5 */ @@ -699,12 +768,14 @@ SPECFUN_HOST_DEVICE inline double wright_bessel(double a, double b, double x) { } } - return detail::wb_small_a(a, b, x, order); + return detail::wb_small_a(a, b, x, order); } if (x <= 1) { // 18 term Taylor Series => error mostly smaller 5e-14 - return detail::wb_series(a, b, x, 0, 18); + double res = detail::wb_series(a, b, x, 0, 18); + if (log_wb) res = std::log(res); + return res; } if (x <= 2) { // 20 term Taylor Series => error mostly smaller 1e-12 to 1e-13 @@ -730,10 +801,7 @@ SPECFUN_HOST_DEVICE inline double wright_bessel(double a, double b, double x) { order = static_cast(std::fmin(6 * std::log10(x) - 36, 100)); } } - return detail::wb_large_a(a, b, x, order); - } - if (0.5 <= a && a <= 1.8 && 100 <= b && 1e5 <= x) { - return std::numeric_limits::quiet_NaN(); + return detail::wb_large_a(a, b, x, order); } if (std::pow(a * x, 1 / (1. + a)) >= 14 + b * b / (2 * (1 + a))) { /* Asymptotic expansion in Z = (a*x)^(1/(1+a)) up to 8th term 1/Z^8. @@ -742,13 +810,31 @@ SPECFUN_HOST_DEVICE inline double wright_bessel(double a, double b, double x) { * domain of good convergence set above. * => precision ~ 1e-11 but can go down to ~1e-8 or 1e-7 * Note: We ensured a <= 5 as this is a bad approximation for large a. */ - return detail::wb_asymptotic(a, b, x); + return detail::wb_asymptotic(a, b, x); + } + if (0.5 <= a && a <= 1.8 && 100 <= b && 1e5 <= x) { + // This is a very hard domain. This condition is placed after wb_asymptotic. + // TODO: Explore ways to cover this domain. + return std::numeric_limits::quiet_NaN(); } - return detail::wright_bessel_integral(a, b, x); + return detail::wright_bessel_integral(a, b, x); +} + + +SPECFUN_HOST_DEVICE inline double wright_bessel(double a, double b, double x) { + return wright_bessel_t(a, b, x); } SPECFUN_HOST_DEVICE inline float wright_bessel(float a, float b, float x) { return wright_bessel(static_cast(a), static_cast(b), static_cast(x)); } +SPECFUN_HOST_DEVICE inline double log_wright_bessel(double a, double b, double x) { + return wright_bessel_t(a, b, x); +} + +SPECFUN_HOST_DEVICE inline float log_wright_bessel(float a, float b, float x) { + return log_wright_bessel(static_cast(a), static_cast(b), static_cast(x)); +} + } // namespace special diff --git a/scipy/special/special_wrappers.cpp b/scipy/special/special_wrappers.cpp index d2f3a7293928..38b32dc15de4 100644 --- a/scipy/special/special_wrappers.cpp +++ b/scipy/special/special_wrappers.cpp @@ -544,6 +544,7 @@ double cephes_p1evl_wrap(double x, const double coef[], int N) { return special: double gammaln_wrap(double x) { return special::gammaln(x); } double special_wright_bessel(double a, double b, double x) { return special::wright_bessel(a, b, x); } +double special_log_wright_bessel(double a, double b, double x) { return special::log_wright_bessel(a, b, x); } double special_scaled_exp1(double x) { return special::scaled_exp1(x); } diff --git a/scipy/special/special_wrappers.h b/scipy/special/special_wrappers.h index e96a6894ad78..b4e87614e4ac 100644 --- a/scipy/special/special_wrappers.h +++ b/scipy/special/special_wrappers.h @@ -216,6 +216,7 @@ double cephes_yn_wrap(int n, double x); double cephes_polevl_wrap(double x, const double coef[], int N); double cephes_p1evl_wrap(double x, const double coef[], int N); double special_wright_bessel(double a, double b, double x); +double special_log_wright_bessel(double a, double b, double x); double special_scaled_exp1(double x); diff --git a/scipy/special/tests/test_cython_special.py b/scipy/special/tests/test_cython_special.py index 058b5a76c548..4d26311d3fe2 100644 --- a/scipy/special/tests/test_cython_special.py +++ b/scipy/special/tests/test_cython_special.py @@ -195,6 +195,7 @@ (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), diff --git a/scipy/special/tests/test_wright_bessel.py b/scipy/special/tests/test_wright_bessel.py index 319db817c1ed..ebf236f77269 100644 --- a/scipy/special/tests/test_wright_bessel.py +++ b/scipy/special/tests/test_wright_bessel.py @@ -16,12 +16,14 @@ # # 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 rgamma, wright_bessel +from scipy.special import log_wright_bessel, loggamma, rgamma, wright_bessel @pytest.mark.parametrize('a', [0, 1e-6, 0.1, 0.5, 1, 10]) @@ -29,6 +31,7 @@ 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]) @@ -113,3 +116,90 @@ def test_wright_data_grid_less_accurate(a, b, x, phi, 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, 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) From 1457f69ee3acf1093731e724132729b23a0fc3d7 Mon Sep 17 00:00:00 2001 From: Ilan Gold Date: Tue, 7 May 2024 20:09:14 +0200 Subject: [PATCH 103/500] BUG: sparse: raise error for array classes, document/test old behavior (#20490) * (feat): raise errory for array classes, document/test old behavior * (fix): lint issues * Apply suggestions from code review Co-authored-by: Dan Schult * (fix): string overrun * (fix): more string overrun issues * Update _bsr.py * (refactor): check for singleton in `_spbase` --------- Co-authored-by: Dan Schult Co-authored-by: CJ Carey --- scipy/sparse/_base.py | 8 +++++-- scipy/sparse/_bsr.py | 8 ++++--- scipy/sparse/_compressed.py | 2 +- scipy/sparse/_coo.py | 2 +- scipy/sparse/_data.py | 4 ++-- scipy/sparse/_dia.py | 2 +- scipy/sparse/_dok.py | 2 +- scipy/sparse/_lil.py | 2 +- scipy/sparse/tests/test_construct.py | 35 +++++++++++++++++++++++++++- 9 files changed, 52 insertions(+), 13 deletions(-) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 22b9a2454c8d..1a297ff498ab 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -110,11 +110,15 @@ def _lil_container(self): from ._lil import lil_array return lil_array - def __init__(self, maxprint=MAXPRINT): + def __init__(self, arg1, maxprint=MAXPRINT): 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 @property @@ -1319,7 +1323,7 @@ def _get_index_dtype(self, arrays=(), maxval=None, check_contents=False): class sparray: """A namespace class to separate sparray from spmatrix""" - pass + sparray.__doc__ = _spbase.__doc__ diff --git a/scipy/sparse/_bsr.py b/scipy/sparse/_bsr.py index 8702fcdc9b45..7f8dc5d317f8 100644 --- a/scipy/sparse/_bsr.py +++ b/scipy/sparse/_bsr.py @@ -25,7 +25,7 @@ class _bsr_base(_cs_matrix, _minmax_mixin): _format = 'bsr' def __init__(self, arg1, shape=None, dtype=None, copy=False, blocksize=None): - _data_matrix.__init__(self) + _data_matrix.__init__(self, arg1) if issparse(arg1): if arg1.format == self.format and copy: @@ -94,8 +94,10 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False, blocksize=None): if not isshape(blocksize): raise ValueError(f'invalid blocksize={blocksize}') if tuple(blocksize) != self.data.shape[1:]: - raise ValueError('mismatching blocksize={} vs {}'.format( - 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: diff --git a/scipy/sparse/_compressed.py b/scipy/sparse/_compressed.py index 2a5a0fe23bf8..302a65628755 100644 --- a/scipy/sparse/_compressed.py +++ b/scipy/sparse/_compressed.py @@ -25,7 +25,7 @@ class _cs_matrix(_data_matrix, _minmax_mixin, IndexMixin): """ def __init__(self, arg1, shape=None, dtype=None, copy=False): - _data_matrix.__init__(self) + _data_matrix.__init__(self, arg1) is_array = isinstance(self, sparray) if issparse(arg1): diff --git a/scipy/sparse/_coo.py b/scipy/sparse/_coo.py index b52bc7af5760..bf9c7ad9a41b 100644 --- a/scipy/sparse/_coo.py +++ b/scipy/sparse/_coo.py @@ -25,7 +25,7 @@ class _coo_base(_data_matrix, _minmax_mixin): _format = 'coo' def __init__(self, arg1, shape=None, dtype=None, copy=False): - _data_matrix.__init__(self) + _data_matrix.__init__(self, arg1) is_array = isinstance(self, sparray) if not copy: copy = copy_if_needed diff --git a/scipy/sparse/_data.py b/scipy/sparse/_data.py index 664f4d5a32b5..b619eeb90bbb 100644 --- a/scipy/sparse/_data.py +++ b/scipy/sparse/_data.py @@ -18,8 +18,8 @@ # TODO implement all relevant operations # use .data.__methods__() instead of /=, *=, etc. class _data_matrix(_spbase): - def __init__(self): - _spbase.__init__(self) + def __init__(self, arg1): + _spbase.__init__(self, arg1) @property def dtype(self): diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index 18fe20607565..5a947f530089 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -20,7 +20,7 @@ class _dia_base(_data_matrix): _format = 'dia' def __init__(self, arg1, shape=None, dtype=None, copy=False): - _data_matrix.__init__(self) + _data_matrix.__init__(self, arg1) if issparse(arg1): if arg1.format == "dia": diff --git a/scipy/sparse/_dok.py b/scipy/sparse/_dok.py index cfe95466077c..08a039136ff3 100644 --- a/scipy/sparse/_dok.py +++ b/scipy/sparse/_dok.py @@ -19,7 +19,7 @@ class _dok_base(_spbase, IndexMixin, dict): _format = 'dok' def __init__(self, arg1, shape=None, dtype=None, copy=False): - _spbase.__init__(self) + _spbase.__init__(self, arg1) is_array = isinstance(self, sparray) if isinstance(arg1, tuple) and isshape(arg1, allow_1d=is_array): diff --git a/scipy/sparse/_lil.py b/scipy/sparse/_lil.py index b55900103861..f64598481a68 100644 --- a/scipy/sparse/_lil.py +++ b/scipy/sparse/_lil.py @@ -21,7 +21,7 @@ class _lil_base(_spbase, IndexMixin): _format = 'lil' def __init__(self, arg1, shape=None, dtype=None, copy=False): - _spbase.__init__(self) + _spbase.__init__(self, arg1) self.dtype = getdtype(dtype, arg1, default=float) # First get the shape diff --git a/scipy/sparse/tests/test_construct.py b/scipy/sparse/tests/test_construct.py index ff174158cb99..e539ed8a8cf6 100644 --- a/scipy/sparse/tests/test_construct.py +++ b/scipy/sparse/tests/test_construct.py @@ -11,7 +11,11 @@ from scipy.sparse import (csr_matrix, coo_matrix, csr_array, coo_array, - sparray, spmatrix, + 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 @@ -37,6 +41,35 @@ def _sprandn_array(m, n, density=0.01, format="coo", dtype=None, random_state=No 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], From bdc41eebd8e90e6779e9bb42bd94b11c36ff1e1c Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 7 May 2024 19:05:56 -0700 Subject: [PATCH 104/500] TST: stats.kruskal: test behavior with no input - fails w/ SCIPY_ARRAY_API=1 --- scipy/stats/tests/test_stats.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 676ef93d82e2..15227b57701a 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -7638,6 +7638,11 @@ def test_large_no_samples(self): expected = 0 assert_approx_equal(p, expected) + def test_no_args_gh20661(self): + message = r"Need at least two groups in stats.kruskal\(\)" + with pytest.raises(ValueError, match=message): + stats.kruskal() + class TestCombinePvalues: From 8fde6e2aca6ad5adc08c8d8ef73caea52cfa42b6 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 7 May 2024 19:09:36 -0700 Subject: [PATCH 105/500] MAINT: stats.kruskal: fix no-arg behavior --- scipy/stats/_axis_nan_policy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scipy/stats/_axis_nan_policy.py b/scipy/stats/_axis_nan_policy.py index d504e426b155..24e36bfa7d14 100644 --- a/scipy/stats/_axis_nan_policy.py +++ b/scipy/stats/_axis_nan_policy.py @@ -16,6 +16,8 @@ def _broadcast_arrays(arrays, axis=None, xp=None): """ Broadcast shapes of arrays, ignoring incompatibility of specified axes """ + 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] From b8f5297728989810d041a52b00aa7b06da145889 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Wed, 8 May 2024 12:40:36 +1000 Subject: [PATCH 106/500] MAINT: trust-constr make origin of error message clearer when there are more equality constraints than independent variables --- .../equality_constrained_sqp.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py b/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py index d50e1e792bef..233b0a380934 100644 --- a/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py +++ b/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py @@ -77,7 +77,21 @@ def equality_constrained_sqp(fun_and_constr, grad_and_jac, lagr_hess, A = jac0 S = scaling(x) # Get projections - Z, LS, Y = projections(A, factorization_method) + try: + Z, LS, Y = projections(A, factorization_method) + except ValueError as e: + if str(e) == "expected square matrix": + # can be the case if there are more equality + # constraints than independent variables + raise ValueError( + "The 'expected square matrix' error can occur if there are" + " more equality constraints than independent variables." + " Consider how your constraints are setup, or use" + " factorization_method='SVDFactorization'." + ) from e + else: + raise e + # Compute least-square lagrange multipliers v = -LS.dot(c) # Compute Hessian From a5f357bb596e1c38f3816c674da6b4cb723162bf Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 7 May 2024 21:54:31 -0700 Subject: [PATCH 107/500] TST: optimize.minimize: test improved error message --- .../equality_constrained_sqp.py | 2 +- .../tests/test_minimize_constrained.py | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py b/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py index 233b0a380934..fb4c05dcdd03 100644 --- a/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py +++ b/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py @@ -86,7 +86,7 @@ def equality_constrained_sqp(fun_and_constr, grad_and_jac, lagr_hess, raise ValueError( "The 'expected square matrix' error can occur if there are" " more equality constraints than independent variables." - " Consider how your constraints are setup, or use" + " Consider how your constraints are set up, or use" " factorization_method='SVDFactorization'." ) from e else: diff --git a/scipy/optimize/tests/test_minimize_constrained.py b/scipy/optimize/tests/test_minimize_constrained.py index 1f68bfafae66..ee700ec6e959 100644 --- a/scipy/optimize/tests/test_minimize_constrained.py +++ b/scipy/optimize/tests/test_minimize_constrained.py @@ -10,7 +10,8 @@ Bounds, minimize, BFGS, - SR1) + SR1, + rosen) class Maratos: @@ -747,6 +748,22 @@ def nci(x): assert_allclose(res.fun, ref.fun) +def test_gh20665_too_many_constraints(): + # gh-20665 reports a confusing error message when there are more equality + # constraints than variables. Check that the error message is improved. + message = "...more equality constraints than independent variables..." + with pytest.raises(ValueError, match=message): + x0 = np.ones((2,)) + A_eq, b_eq = np.arange(6).reshape((3, 2)), np.ones((3,)) + g = NonlinearConstraint(lambda x: A_eq @ x, lb=b_eq, ub=b_eq) + minimize(rosen, x0, method='trust-constr', constraints=[g]) + # no error with `SVDFactorization` + with np.testing.suppress_warnings() as sup: + sup.filter(UserWarning) + minimize(rosen, x0, method='trust-constr', constraints=[g], + options={'factorization_method': 'SVDFactorization'}) + + class TestBoundedNelderMead: @pytest.mark.parametrize('bounds, x_opt', From 5b7e5a0fb34dfa869ce5801bce2bd5992f0c2926 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Wed, 8 May 2024 06:24:58 +0100 Subject: [PATCH 108/500] ENH: stats: add array-API support to kstat/kstatvar (#20634) * ENH: stats: add array-API support to kstat/kstatvar --- scipy/stats/_morestats.py | 12 ++-- scipy/stats/_stats_py.py | 3 + scipy/stats/tests/test_morestats.py | 89 ++++++++++++++++------------- 3 files changed, 60 insertions(+), 44 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 9b26e4e19fea..dfaa0faeda8e 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -288,12 +288,13 @@ def kstat(data, n=2, *, axis=None): 0.00166 0.00166 -4.99e-09 -2.88e-06 -2.88e-06 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) - data = np.asarray(data) if axis is None: - data = ravel(data) + data = xp.reshape(data, (-1,)) axis = 0 N = data.shape[axis] @@ -302,7 +303,7 @@ def kstat(data, n=2, *, axis=None): if N == 0: raise ValueError("Data input must not be empty") - S = [None] + [np.sum(data**k, axis=axis) for k in range(1, n + 1)] + 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: @@ -365,9 +366,10 @@ def kstatvar(data, n=2, *, axis=None): \frac{144 n \kappa_{2} \kappa^2_{3}}{(n - 1) (n - 2)} + \frac{24 (n + 1) n \kappa^4_{2}}{(n - 1) (n - 2) (n - 3)} """ # noqa: E501 - data = np.asarray(data) + xp = array_namespace(data) + data = xp.asarray(data) if axis is None: - data = ravel(data) + data = xp.reshape(data, (-1,)) axis = 0 N = data.shape[axis] diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 0a8a949bd980..888bfe8281fa 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -2829,6 +2829,9 @@ def sem(a, axis=0, ddof=1, nan_policy='propagate'): """ xp = array_namespace(a) + if axis is None: + a = xp.reshape(a, (-1,)) + axis = 0 a = atleast_nd(a, ndim=1, xp=xp) n = a.shape[axis] s = xp.std(a, axis=axis, correction=ddof) / n**0.5 diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 2d94353bdae0..04e3c35bab8a 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -21,10 +21,14 @@ from .._hypotests import _get_wilcoxon_distr, _get_wilcoxon_distr2 from scipy.stats._binomtest import _binary_search_for_binom_tst from scipy.stats._distr_params import distcont + from scipy.conftest import array_api_compatible from scipy._lib._array_api import (array_namespace, xp_assert_close, xp_assert_less, SCIPY_ARRAY_API, xp_assert_equal) + +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 @@ -1669,36 +1673,38 @@ def test_permutation_method(self, size): 12.10, 15.02, 16.83, 16.98, 19.92, 9.47, 11.68, 13.41, 15.35, 19.11] +@array_api_compatible class TestKstat: - def test_moments_normal_distribution(self): + def test_moments_normal_distribution(self, xp): np.random.seed(32149) - data = np.random.randn(12345) - moments = [stats.kstat(data, n) for n in [1, 2, 3, 4]] + data = xp.asarray(np.random.randn(12345), dtype=xp.float64) + moments = xp.asarray([stats.kstat(data, n) for n in [1, 2, 3, 4]]) - expected = [0.011315, 1.017931, 0.05811052, 0.0754134] - assert_allclose(moments, expected, rtol=1e-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) - assert_allclose((m1, m2, m3), expected[:-1], atol=0.02, rtol=1e-2) + xp_assert_close(xp.asarray((m1, m2, m3)), expected[:-1], atol=0.02, rtol=1e-2) - def test_empty_input(self): + def test_empty_input(self, xp): message = 'Data input must not be empty' with pytest.raises(ValueError, match=message): - stats.kstat([]) + stats.kstat(xp.asarray([])) - def test_nan_input(self): - data = np.arange(10.) - data[6] = np.nan + def test_nan_input(self, xp): + data = xp.arange(10.) + data[6] = xp.nan - assert_equal(stats.kstat(data), np.nan) + xp_assert_equal(stats.kstat(data), xp.asarray(xp.nan)) @pytest.mark.parametrize('n', [0, 4.001]) - def test_kstat_bad_arg(self, n): + def test_kstat_bad_arg(self, n, xp): # Raise ValueError if n > 4 or n < 1. - data = np.arange(10) + data = xp.arange(10) message = 'k-statistics only supported for 1<=n<=4' with pytest.raises(ValueError, match=message): stats.kstat(data, n=n) @@ -1707,7 +1713,7 @@ def test_kstat_bad_arg(self, n): (2, 12.65006954022974), (3, -1.447059503280798), (4, -141.6682291883626)]) - def test_against_R(self, case): + def test_against_R(self, case, xp): # Test against reference values computed with R kStatistics, e.g. # options(digits=16) # library(kStatistics) @@ -1717,24 +1723,27 @@ def test_against_R(self, case): # 19.92, 9.47, 11.68, 13.41, 15.35, 19.11) # nKS(4, data) n, ref = case - res = stats.kstat(x_kstat, n) - assert_allclose(res, ref) + res = stats.kstat(xp.asarray(x_kstat), n) + xp_assert_close(res, xp.asarray(ref)) +@array_api_compatible class TestKstatVar: - def test_empty_input(self): + def test_empty_input(self, xp): message = 'Data input must not be empty' with pytest.raises(ValueError, match=message): - stats.kstatvar([]) + stats.kstatvar(xp.asarray([])) - def test_nan_input(self): - data = np.arange(10.) - data[6] = np.nan + def test_nan_input(self, xp): + data = xp.arange(10.) + data[6] = xp.nan - assert_equal(stats.kstat(data), np.nan) + xp_assert_equal(stats.kstat(data), xp.asarray(xp.nan)) - def test_bad_arg(self): + @skip_xp_backends(np_only=True, + reasons=['input validation of `n` does not depend on backend']) + def test_bad_arg(self, xp): # Raise ValueError is n is not 1 or 2. data = [1] n = 10 @@ -1742,7 +1751,7 @@ def test_bad_arg(self): with pytest.raises(ValueError, match=message): stats.kstatvar(data, n=n) - def test_against_R_mathworld(self): + 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, @@ -1751,14 +1760,14 @@ def test_against_R_mathworld(self): k2 = 12.65006954022974 # see source code in TestKstat k4 = -141.6682291883626 - res = stats.kstatvar(x_kstat, 1) + res = stats.kstatvar(xp.asarray(x_kstat), 1) ref = k2 / n - assert_allclose(res, ref) + xp_assert_close(res, xp.asarray(ref)) - res = stats.kstatvar(x_kstat, 2) + res = stats.kstatvar(xp.asarray(x_kstat), 2) # *unbiased estimator* for var(k2) ref = (2*k2**2*n + (n-1)*k4) / (n * (n+1)) - assert_allclose(res, ref) + xp_assert_close(res, xp.asarray(ref)) class TestPpccPlot: @@ -3011,29 +3020,31 @@ def test_edge_cases(self): assert_array_equal(stats.false_discovery_control([]), []) +@array_api_compatible class TestCommonAxis: # More thorough testing of `axis` in `test_axis_nan_policy`, - # but those testa aren't run with array API yet. This class + # 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})]) - def test_axis(self, case): + (stats.kstat, {'n': 2}), + (stats.variation, {})]) + def test_axis(self, case, xp): fun, kwargs = case rng = np.random.default_rng(24598245982345) - x = rng.random((6, 7)) + x = xp.asarray(rng.random((6, 7))) res = fun(x, **kwargs, axis=0) - ref = [fun(x[:, i], **kwargs) for i in range(x.shape[1])] - assert_allclose(res, ref) + ref = xp.asarray([fun(x[:, i], **kwargs) for i in range(x.shape[1])]) + xp_assert_close(res, ref) res = fun(x, **kwargs, axis=1) - ref = [fun(x[i, :], **kwargs) for i in range(x.shape[0])] - assert_allclose(res, ref) + ref = xp.asarray([fun(x[i, :], **kwargs) for i in range(x.shape[0])]) + xp_assert_close(res, ref) res = fun(x, **kwargs, axis=None) - ref = fun(x.ravel(), **kwargs) - assert_allclose(res, ref) + ref = fun(xp.reshape(x, (-1,)), **kwargs) + xp_assert_close(res, ref) From 0ca4d77e552d576da20e96d936fa1346818085d8 Mon Sep 17 00:00:00 2001 From: Sparrow Date: Tue, 7 May 2024 20:50:45 -1000 Subject: [PATCH 109/500] ENH: interpolate: allow Akima extrapolation (#20304) Reviewed at https://github.com/scipy/scipy/pull/20304 --- scipy/interpolate/_cubic.py | 18 ++++++++++++++---- scipy/interpolate/tests/test_interpolate.py | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/scipy/interpolate/_cubic.py b/scipy/interpolate/_cubic.py index 997776150afa..3277be0d0944 100644 --- a/scipy/interpolate/_cubic.py +++ b/scipy/interpolate/_cubic.py @@ -424,6 +424,11 @@ class Akima1DInterpolator(CubicHermiteSpline): .. versionadded:: 1.13.0 + extrapolate : {bool, None}, optional + If bool, determines whether to extrapolate to out-of-bounds points + based on first and last intervals, or to return NaNs. If None, + ``extrapolate`` is set to False. + Methods ------- __call__ @@ -505,7 +510,8 @@ class Akima1DInterpolator(CubicHermiteSpline): """ - def __init__(self, x, y, axis=0, *, method: Literal["akima", "makima"]="akima"): + def __init__(self, x, y, axis=0, *, method: Literal["akima", "makima"]="akima", + extrapolate:bool | None = None): if method not in {"akima", "makima"}: raise NotImplementedError(f"`method`={method} is unsupported.") # Original implementation in MATLAB by N. Shamsundar (BSD licensed), see @@ -520,6 +526,9 @@ def __init__(self, x, y, axis=0, *, method: Literal["akima", "makima"]="akima"): "the array before passing to `Akima1DInterpolator`.") warnings.warn(msg, DeprecationWarning, stacklevel=2) + # Akima extrapolation historically False; parent class defaults to True. + extrapolate = False if extrapolate is None else extrapolate + # determine slopes between breakpoints m = np.empty((x.size + 3, ) + y.shape[1:]) dx = dx[(slice(None), ) + (None, ) * (y.ndim - 1)] @@ -552,7 +561,7 @@ def __init__(self, x, y, axis=0, *, method: Literal["akima", "makima"]="akima"): t[ind] = (f1[ind] * m[(x_ind + 1,) + y_ind] + f2[ind] * m[(x_ind + 2,) + y_ind]) / f12[ind] - super().__init__(x, y, t, axis=0, extrapolate=False) + super().__init__(x, y, t, axis=0, extrapolate=extrapolate) self.axis = axis def extend(self, c, x, right=True): @@ -959,8 +968,9 @@ def _validate_bc(bc_type, y, expected_deriv_shape, axis): deriv_value = np.asarray(deriv_value) if deriv_value.shape != expected_deriv_shape: raise ValueError( - "`deriv_value` shape {} is not the expected one {}." - .format(deriv_value.shape, expected_deriv_shape)) + f"`deriv_value` shape {deriv_value.shape} is not " + f"the expected one {expected_deriv_shape}." + ) if np.issubdtype(deriv_value.dtype, np.complexfloating): y = y.astype(complex, copy=False) diff --git a/scipy/interpolate/tests/test_interpolate.py b/scipy/interpolate/tests/test_interpolate.py index e5a1dd600c16..ed1ca8b46e68 100644 --- a/scipy/interpolate/tests/test_interpolate.py +++ b/scipy/interpolate/tests/test_interpolate.py @@ -1015,6 +1015,25 @@ def test_mod_invalid_method(self): with pytest.raises(NotImplementedError, match=match): Akima1DInterpolator(x, y, method="invalid") # type: ignore + def test_extrapolate_attr(self): + # + x = np.linspace(-5, 5, 11) + y = x**2 + x_ext = np.linspace(-10, 10, 17) + y_ext = x_ext**2 + # Testing all extrapolate cases. + ak_true = Akima1DInterpolator(x, y, extrapolate=True) + ak_false = Akima1DInterpolator(x, y, extrapolate=False) + ak_none = Akima1DInterpolator(x, y, extrapolate=None) + # None should default to False; extrapolated points are NaN. + assert_allclose(ak_false(x_ext), ak_none(x_ext), equal_nan=True, atol=1e-15) + assert_equal(ak_false(x_ext)[0:4], np.full(4, np.nan)) + assert_equal(ak_false(x_ext)[-4:-1], np.full(3, np.nan)) + # Extrapolation on call and attribute should be equal. + assert_allclose(ak_false(x_ext, extrapolate=True), ak_true(x_ext), atol=1e-15) + # Testing extrapoation to actual function. + assert_allclose(y_ext, ak_true(x_ext), atol=1e-15) + def test_complex(self): # Complex-valued data deprecated x = np.arange(0., 11.) From 8ed3941b0fb7af5496c8a1989abc72921c3669a7 Mon Sep 17 00:00:00 2001 From: Budjen Jovan <92484860+budjen-jovan@users.noreply.github.com> Date: Wed, 8 May 2024 09:57:21 +0200 Subject: [PATCH 110/500] MAINT: signal: Fix typo in error raised by cont2discrete (#20664) As per #20662, fixed the missing blankspace. --- scipy/signal/_lti_conversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/signal/_lti_conversion.py b/scipy/signal/_lti_conversion.py index 5302f0d54d6b..3402d294b717 100644 --- a/scipy/signal/_lti_conversion.py +++ b/scipy/signal/_lti_conversion.py @@ -519,7 +519,7 @@ def cont2discrete(system, dt, method="zoh", alpha=None): elif method == 'impulse': if not np.allclose(d, 0): - raise ValueError("Impulse method is only applicable" + raise ValueError("Impulse method is only applicable " "to strictly proper systems") ad = linalg.expm(a * dt) From eb6156b5e898f8b459a105e6bc0e1e5221a2b894 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 8 May 2024 01:10:47 -0700 Subject: [PATCH 111/500] ENH: stats.kurtosis: add array API support (#20658) * ENH: stats.kurtosis: add array API support * TST: stats.kurtosis: strengthen tests * MAINT: stats.kurtosis: accommodate for array_api_compat bug --- scipy/stats/_stats_py.py | 38 ++++++++-------- scipy/stats/tests/test_stats.py | 81 +++++++++++++++++++++++++-------- 2 files changed, 80 insertions(+), 39 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 888bfe8281fa..d2072e6b03e4 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -1204,8 +1204,8 @@ def skew(a, axis=0, bias=True, nan_policy='propagate'): mean = xp.mean(a, axis=axis, keepdims=True) mean_reduced = xp.squeeze(mean, axis=axis) # needed later - m2 = _moment(a, 2, axis, mean=mean) - m3 = _moment(a, 3, axis, mean=mean) + m2 = _moment(a, 2, axis, mean=mean, xp=xp) + m3 = _moment(a, 3, axis, mean=mean, xp=xp) with np.errstate(all='ignore'): eps = xp.finfo(m2.dtype).eps zero = m2 <= (eps * mean_reduced)**2 @@ -1224,6 +1224,8 @@ def skew(a, axis=0, bias=True, nan_policy='propagate'): @_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. @@ -1305,31 +1307,29 @@ def kurtosis(a, axis=0, fisher=True, bias=True, nan_policy='propagate'): tail. """ - a, axis = _chk_asarray(a, axis) - - contains_nan, nan_policy = _contains_nan(a, nan_policy) - - if contains_nan and nan_policy == 'omit': - a = ma.masked_invalid(a) - return mstats_basic.kurtosis(a, axis, fisher, bias) + xp = array_namespace(a) + a, axis = _chk_asarray(a, axis, xp=xp) n = a.shape[axis] - mean = a.mean(axis, keepdims=True) - m2 = _moment(a, 2, axis, mean=mean) - m4 = _moment(a, 4, axis, mean=mean) + mean = xp.mean(a, axis=axis, keepdims=True) + mean_reduced = xp.squeeze(mean, axis=axis) # needed later + m2 = _moment(a, 2, axis, mean=mean, xp=xp) + m4 = _moment(a, 4, axis, mean=mean, xp=xp) with np.errstate(all='ignore'): - zero = (m2 <= (np.finfo(m2.dtype).resolution * mean.squeeze(axis))**2) - vals = np.where(zero, np.nan, m4 / m2**2.0) + zero = m2 <= (xp.finfo(m2.dtype).eps * mean_reduced)**2 + NaN = _get_nan(m4, xp=xp) + vals = xp.where(zero, NaN, m4 / m2**2.0) if not bias: can_correct = ~zero & (n > 3) - if can_correct.any(): - m2 = np.extract(can_correct, m2) - m4 = np.extract(can_correct, m4) + if xp.any(can_correct): + m2 = m2[can_correct] + m4 = m4[can_correct] nval = 1.0/(n-2)/(n-3) * ((n**2-1.0)*m4/m2**2.0 - 3*(n-1)**2.0) - np.place(vals, can_correct, nval + 3.0) + vals[can_correct] = nval + 3.0 - return vals[()] - 3 if fisher else vals[()] + vals = vals - 3 if fisher else vals + return vals[()] if vals.ndim == 0 else vals DescribeResult = namedtuple('DescribeResult', diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 676ef93d82e2..12118dbaf24f 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -3444,8 +3444,7 @@ def test_skew_propagate_nan(self, xp): @array_api_compatible def test_skew_constant_value(self, xp): - # Skewness of a constant input should be zero even when the mean is not - # exact (gh-13245) + # Skewness of a constant input should be NaN (gh-16061) with pytest.warns(RuntimeWarning, match="Precision loss occurred"): a = xp.asarray([-0.27829495]*10) # xp.repeat not currently available xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) @@ -3500,10 +3499,11 @@ def skewness(a, axis, bias): class TestKurtosis(SkewKurtosisTest): - def test_kurtosis(self): + @array_api_compatible + def test_kurtosis(self, xp): # Scalar test case - y = stats.kurtosis(self.scalar_testcase) - assert np.isnan(y) + 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) @@ -3515,29 +3515,36 @@ def test_kurtosis(self): # # Set flags for axis = 0 and # fisher=0 (Pearson's defn of kurtosis for compatibility with Matlab) - y = stats.kurtosis(self.testmathworks, 0, fisher=0, bias=1) - assert_approx_equal(y, 2.1658856802973, 10) + 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(self.testmathworks, fisher=0, bias=0) - assert_approx_equal(y, 3.663542721189047, 10) - y = stats.kurtosis(self.testcase, 0, 0) - assert_approx_equal(y, 1.64) + 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.asarray(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_equal(stats.kurtosis(x), 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) @@ -3545,15 +3552,49 @@ def test_kurtosis_propagate_nan(self): 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): - # Kurtosis of a constant input should be zero, even when the mean is not - # exact (gh-13245) - a = np.repeat(-0.27829495, 10) + @array_api_compatible + def test_kurtosis_constant_value(self, xp): + # Kurtosis of a constant input should be NaN (gh-16061) + a = xp.asarray([-0.27829495]*10) with pytest.warns(RuntimeWarning, match="Precision loss occurred"): - assert np.isnan(stats.kurtosis(a, fisher=False)) - assert np.isnan(stats.kurtosis(a * float(2**50), fisher=False)) - assert np.isnan(stats.kurtosis(a / float(2**50), fisher=False)) - assert np.isnan(stats.kurtosis(a, fisher=False, bias=False)) + 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)) + + @array_api_compatible + @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 + xp_test = array_namespace(a) # plain torch ddof=1 by default + mean = xp_test.mean(a, axis=axis, keepdims=True) + mu4 = xp_test.mean((a - mean)**4, axis=axis) + mu2 = xp_test.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 From bf645b7414dc1a16e99dd3ca22113687eb3cc31a Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 8 May 2024 15:50:45 -0700 Subject: [PATCH 112/500] ENH: optimize._jacobian: use _differentiate to compute accurate Jacobian (#20630) * ENH: optimize._jacobian: use _differentiate to compute accurate Jacobian * TST: optimize._jacobian: add small atol * MAINT: optimize._jacobian: minor doc tweak and mypy fix [skip circle] [skip cirrus] --- scipy/optimize/_differentiate.py | 189 ++++++++++++++++++++- scipy/optimize/tests/test_differentiate.py | 120 ++++++++++++- 2 files changed, 306 insertions(+), 3 deletions(-) diff --git a/scipy/optimize/_differentiate.py b/scipy/optimize/_differentiate.py index 0b59a4fdd992..eca51554bf9f 100644 --- a/scipy/optimize/_differentiate.py +++ b/scipy/optimize/_differentiate.py @@ -68,7 +68,7 @@ def _differentiate(func, x, *, args=(), atol=None, rtol=None, maxiter=10, func(x: ndarray, *fargs) -> ndarray - where each element of ``x`` is a finite real and ``fargs`` is a tuple, + where each element of ``x`` is a finite real number and ``fargs`` is a tuple, which may contain an arbitrary number of arrays that are broadcastable with `x`. ``func`` must be an elementwise function: each element ``func(x)[i]`` must equal ``func(x[i])`` for all indices ``i``. @@ -667,3 +667,190 @@ def _differentiate_weights(work, n): _differentiate_weights.central = [] _differentiate_weights.right = [] _differentiate_weights.fac = None + + +def _jacobian(func, x, *, atol=None, rtol=None, maxiter=10, + order=8, initial_step=0.5, step_factor=2.0): + r"""Evaluate the Jacobian of a function numerically. + + Parameters + ---------- + func : callable + The function whose Jacobian is desired. The signature must be:: + + func(x: ndarray) -> ndarray + + where each element of ``x`` is a finite real. If the function to be + differentiated accepts additional, arguments wrap it (e.g. using + `functools.partial` or ``lambda``) and pass the wrapped callable + into `_jacobian`. See Notes regarding vectorization and the dimensionality + of the input and output. + x : array_like + Points at which to evaluate the Jacobian. Must have at least one dimension. + See Notes regarding the dimensionality and vectorization. + atol, rtol : float, optional + Absolute and relative tolerances for the stopping condition: iteration + will stop for each element of the Jacobian when + ``res.error < atol + rtol * abs(res.df)``. The default `atol` is the + smallest normal number of the appropriate dtype, and the default `rtol` + is the square root of the precision of the appropriate dtype. + order : int, default: 8 + The (positive integer) order of the finite difference formula to be + used. Odd integers will be rounded up to the next even integer. + initial_step : float, default: 0.5 + The (absolute) initial step size for the finite difference derivative + approximation. + step_factor : float, default: 2.0 + The factor by which the step size is *reduced* in each iteration; i.e. + the step size in iteration 1 is ``initial_step/step_factor``. If + ``step_factor < 1``, subsequent steps will be greater than the initial + step; this may be useful if steps smaller than some threshold are + undesirable (e.g. due to subtractive cancellation error). + maxiter : int, default: 10 + The maximum number of iterations of the algorithm to perform. + + Returns + ------- + res : _RichResult + An instance of `scipy._lib._util._RichResult` with the following + attributes. + + success : bool array + ``True`` when the algorithm terminated successfully (status ``0``). + status : int array + An integer representing the exit status of the algorithm. + ``0`` : The algorithm converged to the specified tolerances. + ``-1`` : The error estimate increased, so iteration was terminated. + ``-2`` : The maximum number of iterations was reached. + ``-3`` : A non-finite value was encountered. + ``-4`` : Iteration was terminated by `callback`. + ``1`` : The algorithm is proceeding normally (in `callback` only). + df : float array + The Jacobian of `func` at `x`, if the algorithm terminated + successfully. + error : float array + An estimate of the error: the magnitude of the difference between + the current estimate of the derivative and the estimate in the + previous iteration. + nit : int array + The number of iterations performed. + nfev : int array + The number of points at which `func` was evaluated. + x : float array + The value at which the derivative of `func` was evaluated. + + See Also + -------- + _differentiate + + Notes + ----- + Suppose we wish to evaluate the Jacobian of a function + :math:`f: \mathbf{R^m} \rightarrow \mathbf{R^n}`, and assign to variables + ``m`` and ``n`` the positive integer values of :math:`m` and :math:`n`, + respectively. If we wish to evaluate the Jacobian at a single point, + then: + + - argument `x` must be an array of shape ``(m,)`` + - argument `func` must be vectorized to accept an array of shape ``(m, p)``. + The first axis represents the :math:`m` inputs of :math:`f`; the second + is for evaluating the function at multiple points in a single call. + - argument `func` must return an array of shape ``(n, p)``. The first + axis represents the :math:`n` outputs of :math:`f`; the second + is for the result of evaluating the function at multiple points. + - attribute ``df`` of the result object will be an array of shape ``(n, m)``, + the Jacobian. + + This function is also vectorized in the sense that the Jacobian can be + evaluated at ``k`` points in a single call. In this case, `x` would be an + array of shape ``(m, k)``, `func` would accept an array of shape + ``(m, k, p)`` and return an array of shape ``(n, k, p)``, and the ``df`` + attribute of the result would have shape ``(n, m, k)``. + + References + ---------- + .. [1] Jacobian matrix and determinant, *Wikipedia*, + https://en.wikipedia.org/wiki/Jacobian_matrix_and_determinant + + Examples + -------- + The Rosenbrock function maps from :math:`\mathbf{R}^m \righarrow \mathbf{R}`; + the SciPy implementation `scipy.optimize.rosen` is vectorized to accept an + array of shape ``(m, p)`` and return an array of shape ``m``. Suppose we wish + to evaluate the Jacobian (AKA the gradient because the function returns a scalar) + at ``[0.5, 0.5, 0.5]``. + + >>> import numpy as np + >>> from scipy.optimize._differentiate import _jacobian as jacobian + >>> from scipy.optimize import rosen, rosen_der + >>> m = 3 + >>> x = np.full(m, 0.5) + >>> res = jacobian(rosen, x) + >>> ref = rosen_der(x) # reference value of the gradient + >>> res.df, ref + (array([-51., -1., 50.]), array([-51., -1., 50.])) + + As an example of a function with multiple outputs, consider Example 4 + from [1]_. + + >>> def f(x): + ... x1, x2, x3 = x ... + ... return [x1, 5*x3, 4*x2**2 - 2*x3, x3*np.sin(x1)] + + The true Jacobian is given by: + + >>> def df(x): + ... x1, x2, x3 = x + ... one = np.ones_like(x1) + ... return [[one, 0*one, 0*one], + ... [0*one, 0*one, 5*one], + ... [0*one, 8*x2, -2*one], + ... [x3*np.cos(x1), 0*one, np.sin(x1)]] + + Evaluate the Jacobian at an arbitrary point. + + >>> rng = np.random.default_rng(389252938452) + >>> x = rng.random(size=3) + >>> res = jacobian(f, x) + >>> ref = df(x) + >>> res.df.shape == (4, 3) + True + >>> np.allclose(res.df, ref) + True + + Evaluate the Jacobian at 10 arbitrary points in a single call. + + >>> x = rng.random(size=(3, 10)) + >>> res = jacobian(f, x) + >>> ref = df(x) + >>> res.df.shape == (4, 3, 10) + True + >>> np.allclose(res.df, ref) + True + + """ + x = np.asarray(x) + int_dtype = np.issubdtype(x.dtype, np.integer) + x0 = np.asarray(x, dtype=float) if int_dtype else x + + if x0.ndim < 1: + message = "Argument `x` must be at least 1-D." + raise ValueError(message) + + m = x0.shape[0] + i = np.arange(m) + + def wrapped(x): + p = () if x.ndim == x0.ndim else (x.shape[-1],) # number of abscissae + new_dims = (1,) if x.ndim == x0.ndim else (1, -1) + new_shape = (m, m) + x0.shape[1:] + p + xph = np.expand_dims(x0, new_dims) + xph = np.broadcast_to(xph, new_shape).copy() + xph[i, i] = x + return func(xph) + + res = _differentiate(wrapped, x, atol=atol, rtol=rtol, + maxiter=maxiter, order=order, initial_step=initial_step, + step_factor=step_factor, preserve_shape=True) + del res.x # the user knows `x`, and the way it gets broadcasted is meaningless here + return res diff --git a/scipy/optimize/tests/test_differentiate.py b/scipy/optimize/tests/test_differentiate.py index 5e010dabf4d0..ad9407675b46 100644 --- a/scipy/optimize/tests/test_differentiate.py +++ b/scipy/optimize/tests/test_differentiate.py @@ -4,9 +4,9 @@ from numpy.testing import assert_array_less, assert_allclose, assert_equal import scipy._lib._elementwise_iterative_method as eim -from scipy import stats +from scipy import stats, optimize from scipy.optimize._differentiate import (_differentiate as differentiate, - _EERRORINCREASE) + _jacobian as jacobian, _EERRORINCREASE) class TestDifferentiate: @@ -394,3 +394,119 @@ def test_saddle_gh18811(self, case): res = differentiate(*case, step_direction=[-1, 0, 1], atol=atol) assert np.all(res.success) assert_allclose(res.df, 0, atol=atol) + + +class TestJacobian: + + # Example functions and Jacobians from Wikipedia: + # https://en.wikipedia.org/wiki/Jacobian_matrix_and_determinant#Examples + + def f1(z): + x, y = z + return [x ** 2 * y, 5 * x + np.sin(y)] + + def df1(z): + x, y = z + return [[2 * x * y, x ** 2], [np.full_like(x, 5), np.cos(y)]] + + f1.mn = 2, 2 # type: ignore[attr-defined] + f1.ref = df1 # type: ignore[attr-defined] + + def f2(z): + r, phi = z + return [r * np.cos(phi), r * np.sin(phi)] + + def df2(z): + r, phi = z + return [[np.cos(phi), -r * np.sin(phi)], + [np.sin(phi), r * np.cos(phi)]] + + f2.mn = 2, 2 # type: ignore[attr-defined] + f2.ref = df2 # type: ignore[attr-defined] + + def f3(z): + r, phi, th = z + return [r * np.sin(phi) * np.cos(th), r * np.sin(phi) * np.sin(th), + r * np.cos(phi)] + + def df3(z): + r, phi, th = z + return [[np.sin(phi) * np.cos(th), r * np.cos(phi) * np.cos(th), + -r * np.sin(phi) * np.sin(th)], + [np.sin(phi) * np.sin(th), r * np.cos(phi) * np.sin(th), + r * np.sin(phi) * np.cos(th)], + [np.cos(phi), -r * np.sin(phi), np.zeros_like(r)]] + + f3.mn = 3, 3 # type: ignore[attr-defined] + f3.ref = df3 # type: ignore[attr-defined] + + def f4(x): + x1, x2, x3 = x + return [x1, 5 * x3, 4 * x2 ** 2 - 2 * x3, x3 * np.sin(x1)] + + def df4(x): + x1, x2, x3 = x + one = np.ones_like(x1) + return [[one, 0 * one, 0 * one], + [0 * one, 0 * one, 5 * one], + [0 * one, 8 * x2, -2 * one], + [x3 * np.cos(x1), 0 * one, np.sin(x1)]] + + f4.mn = 3, 4 # type: ignore[attr-defined] + f4.ref = df4 # type: ignore[attr-defined] + + def f5(x): + x1, x2, x3 = x + return [5 * x2, 4 * x1 ** 2 - 2 * np.sin(x2 * x3), x2 * x3] + + def df5(x): + x1, x2, x3 = x + one = np.ones_like(x1) + return [[0 * one, 5 * one, 0 * one], + [8 * x1, -2 * x3 * np.cos(x2 * x3), -2 * x2 * np.cos(x2 * x3)], + [0 * one, x3, x2]] + + f5.mn = 3, 3 # type: ignore[attr-defined] + f5.ref = df5 # type: ignore[attr-defined] + + rosen = optimize.rosen + rosen.mn = 5, 1 # type: ignore[attr-defined] + rosen.ref = optimize.rosen_der # type: ignore[attr-defined] + + @pytest.mark.parametrize('size', [(), (6,), (2, 3)]) + @pytest.mark.parametrize('func', [f1, f2, f3, f4, f5, rosen]) + def test_examples(self, size, func): + rng = np.random.default_rng(458912319542) + m, n = func.mn + x = rng.random(size=(m,) + size) + res = jacobian(func, x).df + ref = func.ref(x) + np.testing.assert_allclose(res, ref, atol=1e-10) + + def test_iv(self): + # Test input validation + message = "Argument `x` must be at least 1-D." + with pytest.raises(ValueError, match=message): + jacobian(np.sin, 1, atol=-1) + + # Confirm that other parameters are being passed to `_derivative`, + # which raises an appropriate error message. + x = np.ones(3) + func = optimize.rosen + message = 'Tolerances and step parameters must be non-negative scalars.' + with pytest.raises(ValueError, match=message): + jacobian(func, x, atol=-1) + with pytest.raises(ValueError, match=message): + jacobian(func, x, rtol=-1) + with pytest.raises(ValueError, match=message): + jacobian(func, x, initial_step=-1) + with pytest.raises(ValueError, match=message): + jacobian(func, x, step_factor=-1) + + message = '`order` must be a positive integer.' + with pytest.raises(ValueError, match=message): + jacobian(func, x, order=-1) + + message = '`maxiter` must be a positive integer.' + with pytest.raises(ValueError, match=message): + jacobian(func, x, maxiter=-1) From de0e3e3ba452032a1e6543c6850bc67221a95e9b Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Wed, 8 May 2024 22:01:33 -0400 Subject: [PATCH 113/500] BUG: sparse: Clean up 1D input handling to sparse array/matrix and add tests (#20444) * test 1d input to sparse. Add FutureWarnings and ValueErrors * remove matrix changes. Let them create 2D * correct imports * rebase on main and update to support 1D CSR input --- scipy/sparse/_bsr.py | 6 +++--- scipy/sparse/_compressed.py | 4 ++++ scipy/sparse/_coo.py | 2 +- scipy/sparse/_dia.py | 2 ++ scipy/sparse/_lil.py | 13 +++++++------ scipy/sparse/tests/test_base.py | 16 +++++++--------- scipy/sparse/tests/test_common1d.py | 18 +++++++++++++++++- 7 files changed, 41 insertions(+), 20 deletions(-) diff --git a/scipy/sparse/_bsr.py b/scipy/sparse/_bsr.py index 7f8dc5d317f8..0d3fe31f0419 100644 --- a/scipy/sparse/_bsr.py +++ b/scipy/sparse/_bsr.py @@ -107,9 +107,9 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False, blocksize=None): except Exception as e: raise ValueError("unrecognized form for" " %s_matrix constructor" % self.format) from e - arg1 = self._coo_container( - arg1, dtype=dtype - ).tobsr(blocksize=blocksize) + 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 ) diff --git a/scipy/sparse/_compressed.py b/scipy/sparse/_compressed.py index 302a65628755..fda5e2eb1596 100644 --- a/scipy/sparse/_compressed.py +++ b/scipy/sparse/_compressed.py @@ -84,6 +84,10 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): 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" + ) coo = self._coo_container(arg1, dtype=dtype) arrays = coo._coo_to_compressed(self._swap) self.indptr, self.indices, self.data, self._shape = arrays diff --git a/scipy/sparse/_coo.py b/scipy/sparse/_coo.py index bf9c7ad9a41b..2f4580b3f004 100644 --- a/scipy/sparse/_coo.py +++ b/scipy/sparse/_coo.py @@ -79,7 +79,7 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): if not is_array: M = np.atleast_2d(M) if M.ndim != 2: - raise TypeError('expected dimension <= 2 array or matrix') + raise TypeError(f'expected 2D array or matrix, not {M.ndim}D') self._shape = check_shape(M.shape, allow_1d=is_array) if shape is not None: diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index 5a947f530089..086f9bf6d617 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -70,6 +70,8 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): except Exception as e: raise ValueError("unrecognized form for" " %s_matrix constructor" % self.format) 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 diff --git a/scipy/sparse/_lil.py b/scipy/sparse/_lil.py index f64598481a68..4b9057d53b5b 100644 --- a/scipy/sparse/_lil.py +++ b/scipy/sparse/_lil.py @@ -57,13 +57,14 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): A = self._ascontainer(arg1) except TypeError as e: raise TypeError('unsupported matrix type') from e - else: - A = self._csr_container(A, dtype=dtype).tolil() + 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 = A.dtype - self.rows = A.rows - self.data = A.data + self._shape = check_shape(A.shape) + self.dtype = A.dtype + self.rows = A.rows + self.data = A.data def __iadd__(self,other): self[:,:] = self + other diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index e5d1170f0495..da381f1c3eea 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -292,8 +292,8 @@ def check(dtype): datsp = self.datsp_dtypes[dtype] assert_raises(ValueError, bool, datsp) - assert_(self.spcreator([1])) - assert_(not self.spcreator([0])) + assert_(self.spcreator([[1]])) + assert_(not self.spcreator([[0]])) if isinstance(self, TestDOK): pytest.skip("Cannot create a rank <= 2 DOK matrix.") @@ -3456,7 +3456,7 @@ def test_minmax(self): assert_equal(X.max(), -1) # and a fully sparse matrix - Z = self.spcreator(np.zeros(1)) + Z = self.spcreator(np.zeros((1, 1))) assert_equal(Z.min(), 0) assert_equal(Z.max(), 0) assert_equal(Z.max().dtype, Z.dtype) @@ -3736,10 +3736,8 @@ def sparse_test_class(getset=True, slicing=True, slicing_assign=True, continue old_cls = names.get(name) if old_cls is not None: - raise ValueError( - f"Test class {cls.__name__} overloads" - f" test {name} defined in {old_cls.__name__}" - ) + raise ValueError(f"Test class {cls.__name__} overloads test " + f"{name} defined in {old_cls.__name__}") names[name] = cls return type("TestBase", bases, {}) @@ -3992,8 +3990,8 @@ def test_scalar_idx_dtype(self): def test_binop_explicit_zeros(self): # Check that binary ops don't introduce spurious explicit zeros. # See gh-9619 for context. - a = csr_matrix([0, 1, 0]) - b = csr_matrix([1, 1, 0]) + a = csr_matrix([[0, 1, 0]]) + b = csr_matrix([[1, 1, 0]]) assert (a + b).nnz == 2 assert a.multiply(b).nnz == 1 diff --git a/scipy/sparse/tests/test_common1d.py b/scipy/sparse/tests/test_common1d.py index da2d010a1183..1d6ca6041858 100644 --- a/scipy/sparse/tests/test_common1d.py +++ b/scipy/sparse/tests/test_common1d.py @@ -5,7 +5,10 @@ import numpy as np from numpy.testing import assert_equal, assert_allclose -from scipy.sparse import coo_array, csr_array, dok_array, SparseEfficiencyWarning +from scipy.sparse import ( + bsr_array, csc_array, dia_array, lil_array, + coo_array, csr_array, dok_array, SparseEfficiencyWarning, + ) from scipy.sparse._sputils import supported_dtypes, matrix from scipy._lib._util import ComplexWarning @@ -32,6 +35,15 @@ def datsp_math_dtypes(dat1d): } +# 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]) + + +# Main tests class @pytest.mark.parametrize("spcreator", spcreators) class TestCommon1D: """test common functionality shared by 1D sparse formats""" @@ -55,6 +67,10 @@ 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]) From 1cd82744323a865a324e2a4d218d2e929da9e405 Mon Sep 17 00:00:00 2001 From: Budjen Jovan <92484860+budjen-jovan@users.noreply.github.com> Date: Thu, 9 May 2024 08:49:43 +0200 Subject: [PATCH 114/500] DOC: Fixed typo in signal.ellipap (#20675) As it says on the tin -- added the missing letter to prof. Lutovac's last name in the docstring of signal.ellipap, as mentioned in issue #20674 --- scipy/signal/_filter_design.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index 02e079ee6d92..d93f810c7f43 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -4562,7 +4562,7 @@ def ellipap(N, rp, rs): References ---------- - .. [1] Lutova, Tosic, and Evans, "Filter Design for Signal Processing", + .. [1] Lutovac, Tosic, and Evans, "Filter Design for Signal Processing", Chapters 5 and 12. .. [2] Orfanidis, "Lecture Notes on Elliptic Filter Design", From 6c6d37958ccdee663dc592edeb267dd9da2f002f Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Thu, 9 May 2024 18:44:28 +1000 Subject: [PATCH 115/500] TST: test__differential_evolution tweaks for speed --- .../tests/test__differential_evolution.py | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/scipy/optimize/tests/test__differential_evolution.py b/scipy/optimize/tests/test__differential_evolution.py index 8f83c1601648..02b08f197e46 100644 --- a/scipy/optimize/tests/test__differential_evolution.py +++ b/scipy/optimize/tests/test__differential_evolution.py @@ -2,6 +2,7 @@ Unit tests for the differential global minimization algorithm. """ import multiprocessing +from multiprocessing.dummy import Pool as ThreadPool import platform from scipy.optimize._differentialevolution import (DifferentialEvolutionSolver, @@ -714,16 +715,19 @@ def test_immediate_updating(self): def test_parallel(self): # smoke test for parallelization with deferred updating bounds = [(0., 2.), (0., 2.)] - with multiprocessing.Pool(2) as p, DifferentialEvolutionSolver( - rosen, bounds, updating='deferred', workers=p.map) as solver: - assert_(solver._mapwrapper.pool is not None) - assert_(solver._updating == 'deferred') + # use threads instead of Process to speed things up for this simple example + with ThreadPool(2) as p, DifferentialEvolutionSolver( + rosen, bounds, updating='deferred', workers=p.map, tol=0.1, popsize=3 + ) as solver: + assert solver._mapwrapper.pool is not None + assert solver._updating == 'deferred' solver.solve() - with DifferentialEvolutionSolver(rosen, bounds, updating='deferred', - workers=2) as solver: - assert_(solver._mapwrapper.pool is not None) - assert_(solver._updating == 'deferred') + with DifferentialEvolutionSolver( + rosen, bounds, updating='deferred', workers=2, popsize=3, tol=0.1 + ) as solver: + assert solver._mapwrapper.pool is not None + assert solver._updating == 'deferred' solver.solve() def test_converged(self): @@ -741,7 +745,7 @@ def constr_f2(x): nlc = NonlinearConstraint(constr_f, -np.inf, 1.9) solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)], - constraints=(nlc)) + constraints=(nlc,)) cv = solver._constraint_violation_fn(np.array([1.0, 1.0])) assert_almost_equal(cv, 0.1) @@ -798,7 +802,7 @@ def constr_f2(x): nlc = NonlinearConstraint(constr_f, -np.inf, 1.9) solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)], - constraints=(nlc)) + constraints=(nlc,)) # are population feasibilities correct # [0.5, 0.5] corresponds to scaled values of [1., 1.] @@ -840,7 +844,7 @@ def constr_f(x): nlc = NonlinearConstraint(constr_f, -np.inf, 1.9) solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)], - constraints=(nlc)) + constraints=(nlc,)) # trust-constr warns if the constraint function is linear with warns(UserWarning): @@ -855,9 +859,9 @@ def constr_f(x): nlc = NonlinearConstraint(constr_f, -np.inf, -1) - solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)], - constraints=(nlc), popsize=3, - seed=1) + solver = DifferentialEvolutionSolver( + rosen, [(0, 2), (0, 2)], constraints=(nlc,), popsize=1, seed=1, maxiter=100 + ) # a UserWarning is issued because the 'trust-constr' polishing is # attempted on the least infeasible solution found. @@ -870,8 +874,8 @@ def constr_f(x): # test _promote_lowest_energy works when none of the population is # feasible. In this case, the solution with the lowest constraint # violation should be promoted. - solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)], - constraints=(nlc), polish=False) + solver = DifferentialEvolutionSolver( + rosen, [(0, 2), (0, 2)], constraints=(nlc,), polish=False) next(solver) assert not solver.feasible.all() assert not np.isfinite(solver.population_energies).all() @@ -895,7 +899,7 @@ def constr_f(x): return [x[0] + x[1]] nlc = NonlinearConstraint(constr_f, -np.inf, 1.9) solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)], - constraints=(nlc)) + constraints=(nlc,)) fn = solver._accept_trial # both solutions are feasible, select lower energy assert fn(0.1, True, np.array([0.]), 1.0, True, np.array([0.])) @@ -1033,8 +1037,10 @@ def f(x): bounds = [(0, 1)]*9 + [(0, 100)]*3 + [(0, 1)] # using a lower popsize to speed the test up - res = differential_evolution(f, bounds, strategy='best1bin', seed=1234, - constraints=(L), popsize=2) + res = differential_evolution( + f, bounds, strategy='best1bin', seed=1234, constraints=(L,), + popsize=2, tol=0.05 + ) x_opt = (1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 1) f_opt = -15 @@ -1054,8 +1060,10 @@ def f(x): L = LinearConstraint(csr_matrix(A), -np.inf, b) # using a lower popsize to speed the test up - res = differential_evolution(f, bounds, strategy='best1bin', seed=1234, - constraints=(L), popsize=2) + res = differential_evolution( + f, bounds, strategy='best1bin', seed=1234, constraints=(L,), + popsize=2, tol=0.05 + ) assert_allclose(f(x_opt), f_opt) assert res.success @@ -1085,9 +1093,10 @@ def c2(x): with suppress_warnings() as sup: sup.filter(UserWarning) - res = differential_evolution(f, bounds, strategy='rand1bin', - seed=1234, constraints=constraints, - popsize=2) + res = differential_evolution( + f, bounds, strategy='best1bin', seed=1234, + constraints=constraints, popsize=2, tol=0.05 + ) assert_allclose(res.x, x_opt, atol=6e-4) assert_allclose(res.fun, f_opt, atol=5e-3) @@ -1119,7 +1128,7 @@ def c1(x): with suppress_warnings() as sup: sup.filter(UserWarning) - res = differential_evolution(f, bounds, strategy='rand1bin', + res = differential_evolution(f, bounds, strategy='best1bin', seed=1234, constraints=constraints) f_opt = 680.6300599487869 @@ -1210,9 +1219,10 @@ def c1(x): with suppress_warnings() as sup: sup.filter(UserWarning) - res = differential_evolution(f, bounds, strategy='rand1bin', - seed=1234, constraints=constraints, - popsize=3) + res = differential_evolution( + f, bounds, strategy='best1bin', seed=1234, + constraints=constraints, popsize=3, tol=0.05 + ) f_opt = 7049.248 From 3bd289f73953494e06e9b839ec5e16b68f37ea25 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Thu, 9 May 2024 22:13:45 +1000 Subject: [PATCH 116/500] MAINT: clarify dual_annealing-minimizer_kwargs docstring. Closes #20614 (#20676) Co-authored-by: Matt Haberland --- scipy/optimize/_dual_annealing.py | 33 +++++++++++++++----- scipy/optimize/tests/test__dual_annealing.py | 22 ++++++++++++- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/scipy/optimize/_dual_annealing.py b/scipy/optimize/_dual_annealing.py index 0dd9eed9dbf9..9645c7967b96 100644 --- a/scipy/optimize/_dual_annealing.py +++ b/scipy/optimize/_dual_annealing.py @@ -395,6 +395,9 @@ def __init__(self, search_bounds, func_wrapper, *args, **kwargs): self.func_wrapper = func_wrapper self.kwargs = kwargs self.jac = self.kwargs.get('jac', None) + self.hess = self.kwargs.get('hess', None) + self.hessp = self.kwargs.get('hessp', None) + self.kwargs.pop("args", None) self.minimizer = minimize bounds_list = list(zip(*search_bounds)) self.lower = np.array(bounds_list[0]) @@ -411,10 +414,19 @@ def __init__(self, search_bounds, func_wrapper, *args, **kwargs): 'maxiter': ls_max_iter, } self.kwargs['bounds'] = list(zip(self.lower, self.upper)) - elif callable(self.jac): - def wrapped_jac(x): - return self.jac(x, *args) - self.kwargs['jac'] = wrapped_jac + else: + if callable(self.jac): + def wrapped_jac(x): + return self.jac(x, *args) + self.kwargs['jac'] = wrapped_jac + if callable(self.hess): + def wrapped_hess(x): + return self.hess(x, *args) + self.kwargs['hess'] = wrapped_hess + if callable(self.hessp): + def wrapped_hessp(x, p): + return self.hessp(x, p, *args) + self.kwargs['hessp'] = wrapped_hessp def local_search(self, x, e): # Run local search from the given x location where energy value is e @@ -464,10 +476,15 @@ def dual_annealing(func, bounds, args=(), maxiter=1000, maxiter : int, optional The maximum number of global search iterations. Default value is 1000. minimizer_kwargs : dict, optional - Extra keyword arguments to be passed to the local minimizer - (`minimize`). Some important options could be: - ``method`` for the minimizer method to use and ``args`` for - objective function additional arguments. + Keyword arguments to be passed to the local minimizer + (`minimize`). An important option could be ``method`` for the minimizer + method to use. + If no keyword arguments are provided, the local minimizer defaults to + 'L-BFGS-B' and uses the already supplied bounds. If `minimizer_kwargs` + is specified, then the dict must contain all parameters required to + control the local minimization. `args` is ignored in this dict, as it is + passed automatically. `bounds` is not automatically passed on to the + local minimizer as the method may not support them. initial_temp : float, optional The initial temperature, use higher values to facilitates a wider search of the energy landscape, allowing dual_annealing to escape diff --git a/scipy/optimize/tests/test__dual_annealing.py b/scipy/optimize/tests/test__dual_annealing.py index 9a44333d0a81..7caa88bb0b63 100644 --- a/scipy/optimize/tests/test__dual_annealing.py +++ b/scipy/optimize/tests/test__dual_annealing.py @@ -361,9 +361,11 @@ def func(x): assert_allclose(ret_bounds_list.fun, ret_bounds_class.fun, atol=1e-9) assert ret_bounds_list.nfev == ret_bounds_class.nfev - def test_callable_jac_with_args_gh11052(self): + def test_callable_jac_hess_with_args_gh11052(self): # dual_annealing used to fail when `jac` was callable and `args` were # used; check that this is resolved. Example is from gh-11052. + + # extended to hess as part of closing gh20614 rng = np.random.default_rng(94253637693657847462) def f(x, power): return np.sum(np.exp(x ** power)) @@ -371,9 +373,27 @@ def f(x, power): def jac(x, power): return np.exp(x ** power) * power * x ** (power - 1) + def hess(x, power): + # calculated using WolframAlpha as d^2/dx^2 e^(x^p) + return np.diag( + power * np.exp(x ** power) * x ** (power - 2) * + (power * x ** power + power - 1) + ) + + def hessp(x, p, power): + return hess(x, power) @ p + res1 = dual_annealing(f, args=(2, ), bounds=[[0, 1], [0, 1]], seed=rng, minimizer_kwargs=dict(method='L-BFGS-B')) res2 = dual_annealing(f, args=(2, ), bounds=[[0, 1], [0, 1]], seed=rng, minimizer_kwargs=dict(method='L-BFGS-B', jac=jac)) + res3 = dual_annealing(f, args=(2, ), bounds=[[0, 1], [0, 1]], seed=rng, + minimizer_kwargs=dict(method='newton-cg', + jac=jac, hess=hess)) + res4 = dual_annealing(f, args=(2, ), bounds=[[0, 1], [0, 1]], seed=rng, + minimizer_kwargs=dict(method='newton-cg', + jac=jac, hessp=hessp)) assert_allclose(res1.fun, res2.fun, rtol=1e-6) + assert_allclose(res3.fun, res2.fun, rtol=1e-6) + assert_allclose(res4.fun, res2.fun, rtol=1e-6) From 00ac018d0e555ceb95ac1e5f46814c30a09f5e28 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 9 May 2024 11:30:46 -0700 Subject: [PATCH 117/500] ENH: _lib._elementwise_iterative_method: add array API support --- scipy/_lib/_elementwise_iterative_method.py | 131 ++++++++++++-------- scipy/integrate/_tanhsinh.py | 6 +- scipy/optimize/_bracket.py | 10 +- scipy/optimize/_chandrupatla.py | 10 +- scipy/optimize/_differentiate.py | 4 +- 5 files changed, 95 insertions(+), 66 deletions(-) diff --git a/scipy/_lib/_elementwise_iterative_method.py b/scipy/_lib/_elementwise_iterative_method.py index 1c7820bbfdff..28e37efcc003 100644 --- a/scipy/_lib/_elementwise_iterative_method.py +++ b/scipy/_lib/_elementwise_iterative_method.py @@ -11,8 +11,10 @@ # `scipy.optimize._bracket._bracket_minimize for finding minimization brackets, # `scipy.integrate._tanhsinh._tanhsinh` for numerical quadrature. +import math import numpy as np from ._util import _RichResult, _call_callback_maybe_halt +from ._array_api import array_namespace, size as xp_size _ESIGNERR = -1 _ECONVERR = -2 @@ -69,18 +71,19 @@ def _initialize(func, xs, args, complex_ok=False, preserve_shape=None): `scipy.optimize._chandrupatla`. """ nx = len(xs) + xp = array_namespace(*xs) # Try to preserve `dtype`, but we need to ensure that the arguments are at # least floats before passing them into the function; integers can overflow # and cause failure. # There might be benefit to combining the `xs` into a single array and # calling `func` once on the combined array. For now, keep them separate. - xas = np.broadcast_arrays(*xs, *args) # broadcast and rename - xat = np.result_type(*[xa.dtype for xa in xas]) - xat = np.float64 if np.issubdtype(xat, np.integer) else xat + xas = xp.broadcast_arrays(*xs, *args) # broadcast and rename + xat = xp.result_type(*[xa.dtype for xa in xas]) + xat = xp.asarray(1.).dtype if xp.isdtype(xat, "integral") else xat xs, args = xas[:nx], xas[nx:] - xs = [x.astype(xat, copy=False)[()] for x in xs] - fs = [np.asarray(func(x, *args)) for x in xs] + xs = [xp.asarray(x, dtype=xat)[()] for x in xs] # use copy=False when implemented + fs = [xp.asarray(func(x, *args)) for x in xs] shape = xs[0].shape fshape = fs[0].shape @@ -89,38 +92,38 @@ def _initialize(func, xs, args, complex_ok=False, preserve_shape=None): def func(x, *args, shape=shape, func=func, **kwargs): i = (0,)*(len(fshape) - len(shape)) return func(x[i], *args, **kwargs) - shape = np.broadcast_shapes(fshape, shape) - xs = [np.broadcast_to(x, shape) for x in xs] - args = [np.broadcast_to(arg, shape) for arg in args] + shape = np.broadcast_shapes(fshape, shape) # just shapes; use of NumPy OK + xs = [xp.broadcast_to(x, shape) for x in xs] + args = [xp.broadcast_to(arg, shape) for arg in args] message = ("The shape of the array returned by `func` must be the same as " "the broadcasted shape of `x` and all other `args`.") if preserve_shape is not None: # only in tanhsinh for now message = f"When `preserve_shape=False`, {message.lower()}" shapes_equal = [f.shape == shape for f in fs] - if not np.all(shapes_equal): + if not all(shapes_equal): # use Python all to reduce overhead raise ValueError(message) # These algorithms tend to mix the dtypes of the abscissae and function # values, so figure out what the result will be and convert them all to # that type from the outset. - xfat = np.result_type(*([f.dtype for f in fs] + [xat])) - if not complex_ok and not np.issubdtype(xfat, np.floating): + xfat = xp.result_type(*([f.dtype for f in fs] + [xat])) + if not complex_ok and not xp.isdtype(xfat, "real floating"): raise ValueError("Abscissae and function output must be real numbers.") - xs = [x.astype(xfat, copy=True)[()] for x in xs] - fs = [f.astype(xfat, copy=True)[()] for f in fs] + xs = [xp.asarray(x, dtype=xfat, copy=True)[()] for x in xs] + fs = [xp.asarray(f, dtype=xfat, copy=True)[()] for f in fs] # To ensure that we can do indexing, we'll work with at least 1d arrays, # but remember the appropriate shape of the output. - xs = [x.ravel() for x in xs] - fs = [f.ravel() for f in fs] - args = [arg.flatten() for arg in args] - return func, xs, fs, args, shape, xfat + xs = [xp.reshape(x, (-1,)) for x in xs] + fs = [xp.reshape(f, (-1,)) for f in fs] + args = [xp.reshape(xp.asarray(arg, copy=True), (-1,)) for arg in args] + return func, xs, fs, args, shape, xfat, xp def _loop(work, callback, shape, maxiter, func, args, dtype, pre_func_eval, post_func_eval, check_termination, post_termination_check, - customize_result, res_work_pairs, preserve_shape=False): + customize_result, res_work_pairs, xp, preserve_shape=False): """Main loop of a vectorized scalar optimization algorithm Parameters @@ -185,82 +188,88 @@ def _loop(work, callback, shape, maxiter, func, args, dtype, pre_func_eval, computation on elements that have already converged. """ + if xp is None: + raise NotImplementedError("Must provide xp.") + cb_terminate = False # Initialize the result object and active element index array - n_elements = int(np.prod(shape)) - active = np.arange(n_elements) # in-progress element indices - res_dict = {i: np.zeros(n_elements, dtype=dtype) for i, j in res_work_pairs} - res_dict['success'] = np.zeros(n_elements, dtype=bool) - res_dict['status'] = np.full(n_elements, _EINPROGRESS) - res_dict['nit'] = np.zeros(n_elements, dtype=int) - res_dict['nfev'] = np.zeros(n_elements, dtype=int) + n_elements = math.prod(shape) + active = xp.arange(n_elements) # in-progress element indices + res_dict = {i: xp.zeros(n_elements, dtype=dtype) for i, j in res_work_pairs} + res_dict['success'] = xp.zeros(n_elements, dtype=xp.bool) + res_dict['status'] = xp.full(n_elements, _EINPROGRESS) + res_dict['nit'] = xp.zeros(n_elements, dtype=xp.int32) + res_dict['nfev'] = xp.zeros(n_elements, dtype=xp.int32) res = _RichResult(res_dict) work.args = args active = _check_termination(work, res, res_work_pairs, active, - check_termination, preserve_shape) + check_termination, preserve_shape, xp) if callback is not None: temp = _prepare_result(work, res, res_work_pairs, active, shape, - customize_result, preserve_shape) + customize_result, preserve_shape, xp) if _call_callback_maybe_halt(callback, temp): cb_terminate = True - while work.nit < maxiter and active.size and not cb_terminate and n_elements: + while work.nit < maxiter and xp_size(active) and not cb_terminate and n_elements: x = pre_func_eval(work) if work.args and work.args[0].ndim != x.ndim: # `x` always starts as 1D. If the SciPy function that uses # _loop added dimensions to `x`, we need to # add them to the elements of `args`. - dims = np.arange(x.ndim, dtype=np.int64) - work.args = [np.expand_dims(arg, tuple(dims[arg.ndim:])) - for arg in work.args] + args = [] + for arg in work.args: + n_new_dims = x.ndim - arg.ndim + new_shape = arg.shape + (1,)*n_new_dims + args.append(xp.reshape(arg, new_shape)) + work.args = args x_shape = x.shape if preserve_shape: - x = x.reshape(shape + (-1,)) + x = xp.reshape(x, (shape + (-1,))) f = func(x, *work.args) - f = np.asarray(f, dtype=dtype) + f = xp.asarray(f, dtype=dtype) if preserve_shape: - x = x.reshape(x_shape) - f = f.reshape(x_shape) + x = xp.reshape(x, x_shape) + f = xp.reshape(f, x_shape) work.nfev += 1 if x.ndim == 1 else x.shape[-1] post_func_eval(x, f, work) work.nit += 1 active = _check_termination(work, res, res_work_pairs, active, - check_termination, preserve_shape) + check_termination, preserve_shape, xp) if callback is not None: temp = _prepare_result(work, res, res_work_pairs, active, shape, - customize_result, preserve_shape) + customize_result, preserve_shape, xp) if _call_callback_maybe_halt(callback, temp): cb_terminate = True break - if active.size == 0: + if xp_size(active) == 0: break post_termination_check(work) work.status[:] = _ECALLBACK if cb_terminate else _ECONVERR return _prepare_result(work, res, res_work_pairs, active, shape, - customize_result, preserve_shape) + customize_result, preserve_shape, xp) def _check_termination(work, res, res_work_pairs, active, check_termination, - preserve_shape): + preserve_shape, xp): # Checks termination conditions, updates elements of `res` with # corresponding elements of `work`, and compresses `work`. stop = check_termination(work) - if np.any(stop): + if xp.any(stop): # update the active elements of the result object with the active # elements for which a termination condition has been met - _update_active(work, res, res_work_pairs, active, stop, preserve_shape) + _update_active(work, res, res_work_pairs, active, stop, preserve_shape, xp) if preserve_shape: stop = stop[active] @@ -271,13 +280,18 @@ def _check_termination(work, res, res_work_pairs, active, check_termination, if not preserve_shape: # compress the arrays to avoid unnecessary computation for key, val in work.items(): - work[key] = val[proceed] if isinstance(val, np.ndarray) else val + # Need to find a better way than these try/excepts + # Somehow need to keep compressible numerical args separate + try: + work[key] = val[proceed] + except (IndexError, TypeError, KeyError): # not a compressible array + work[key] = val work.args = [arg[proceed] for arg in work.args] return active -def _update_active(work, res, res_work_pairs, active, mask, preserve_shape): +def _update_active(work, res, res_work_pairs, active, mask, preserve_shape, xp): # Update `active` indices of the arrays in result object `res` with the # contents of the scalars and arrays in `update_dict`. When provided, # `mask` is a boolean array applied both to the arrays in `update_dict` @@ -287,34 +301,45 @@ def _update_active(work, res, res_work_pairs, active, mask, preserve_shape): if mask is not None: if preserve_shape: - active_mask = np.zeros_like(mask) + active_mask = xp.zeros_like(mask) active_mask[active] = 1 active_mask = active_mask & mask for key, val in update_dict.items(): - res[key][active_mask] = (val[active_mask] if np.size(val) > 1 - else val) + try: + res[key][active_mask] = val[active_mask] + except (IndexError, TypeError, KeyError): + res[key][active_mask] = val else: active_mask = active[mask] for key, val in update_dict.items(): - res[key][active_mask] = val[mask] if np.size(val) > 1 else val + try: + res[key][active_mask] = val[mask] + except (IndexError, TypeError, KeyError): + res[key][active_mask] = val else: for key, val in update_dict.items(): - if preserve_shape and not np.isscalar(val): - val = val[active] + if preserve_shape: + try: + val = val[active] + except (IndexError, TypeError, KeyError): + pass res[key][active] = val def _prepare_result(work, res, res_work_pairs, active, shape, customize_result, - preserve_shape): + preserve_shape, xp): # Prepare the result object `res` by creating a copy, copying the latest # data from work, running the provided result customization function, # and reshaping the data to the original shapes. res = res.copy() - _update_active(work, res, res_work_pairs, active, None, preserve_shape) + _update_active(work, res, res_work_pairs, active, None, preserve_shape, xp) shape = customize_result(res, shape) for key, val in res.items(): - res[key] = np.reshape(val, shape)[()] + # this looks like it won't work for xp != np if val is not numeric + temp = xp.reshape(val, shape) + res[key] = temp[()] if temp.ndim == 0 else temp + res['_order_keys'] = ['success'] + [i for i, j in res_work_pairs] return _RichResult(**res) diff --git a/scipy/integrate/_tanhsinh.py b/scipy/integrate/_tanhsinh.py index 213b7de4b3c3..28f17cc65bc5 100644 --- a/scipy/integrate/_tanhsinh.py +++ b/scipy/integrate/_tanhsinh.py @@ -322,7 +322,7 @@ def _tanhsinh(f, a, b, *, args=(), log=False, maxfun=None, maxlevel=None, c[inf_a & inf_b] = 0 # takes care of infinite a and b temp = eim._initialize(f, (c,), args, complex_ok=True, preserve_shape=preserve_shape) - f, xs, fs, args, shape, dtype = temp + f, xs, fs, args, shape, dtype, xp = temp a = np.broadcast_to(a, shape).astype(dtype).ravel() b = np.broadcast_to(b, shape).astype(dtype).ravel() @@ -461,7 +461,7 @@ def customize_result(res, shape): with np.errstate(over='ignore', invalid='ignore', divide='ignore'): res = eim._loop(work, callback, shape, maxiter, f, args, dtype, pre_func_eval, post_func_eval, check_termination, post_termination_check, - customize_result, res_work_pairs, preserve_shape) + customize_result, res_work_pairs, xp, preserve_shape) return res @@ -1068,7 +1068,7 @@ def _nsum(f, a, b, step=1, args=(), log=False, maxterms=int(2**20), atol=None, # Additional elementwise algorithm input validation / standardization tmp = eim._initialize(f, (a,), args, complex_ok=False) - f, xs, fs, args, shape, dtype = tmp + f, xs, fs, args, shape, dtype, xp = tmp # Finish preparing `a`, `b`, and `step` arrays a = xs[0] diff --git a/scipy/optimize/_bracket.py b/scipy/optimize/_bracket.py index 2210295ea820..68650ed163a4 100644 --- a/scipy/optimize/_bracket.py +++ b/scipy/optimize/_bracket.py @@ -158,7 +158,7 @@ def _bracket_root(func, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, xs = (xl0, xr0) temp = eim._initialize(func, xs, args) - func, xs, fs, args, shape, dtype = temp # line split for PEP8 + func, xs, fs, args, shape, dtype, xp = temp # line split for PEP8 # The approach is to treat the left and right searches as though they were # (almost) totally independent one-sided bracket searches. (The interaction @@ -379,7 +379,8 @@ def customize_result(res, 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) + post_termination_check, customize_result, res_work_pairs, + xp) def _bracket_minimum_iv(func, xm0, xl0, xr0, xmin, xmax, factor, args, maxiter): @@ -562,7 +563,8 @@ def _bracket_minimum(func, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, func, xm0, xl0, xr0, xmin, xmax, factor, args, maxiter = temp xs = (xl0, xm0, xr0) - func, xs, fs, args, shape, dtype = eim._initialize(func, xs, args) + temp = eim._initialize(func, xs, args) + func, xs, fs, args, shape, dtype, xp = temp xl0, xm0, xr0 = xs fl0, fm0, fr0 = fs @@ -655,4 +657,4 @@ def customize_result(res, shape): maxiter, func, args, dtype, pre_func_eval, post_func_eval, check_termination, post_termination_check, - customize_result, res_work_pairs) + customize_result, res_work_pairs, xp) diff --git a/scipy/optimize/_chandrupatla.py b/scipy/optimize/_chandrupatla.py index 6ce98230af68..19154e3e86af 100644 --- a/scipy/optimize/_chandrupatla.py +++ b/scipy/optimize/_chandrupatla.py @@ -125,7 +125,7 @@ def _chandrupatla(func, a, b, *, args=(), xatol=None, xrtol=_rtol, # Initialization temp = eim._initialize(func, (a, b), args) - func, xs, fs, args, shape, dtype = temp + func, xs, fs, args, shape, dtype, xp = temp x1, x2 = xs f1, f2 = fs status = np.full_like(x1, eim._EINPROGRESS, dtype=int) # in progress @@ -227,7 +227,8 @@ def customize_result(res, 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) + post_termination_check, customize_result, res_work_pairs, + xp=xp) def _chandrupatla_iv(func, args, xatol, xrtol, @@ -383,7 +384,7 @@ def _chandrupatla_minimize(func, x1, x2, x3, *, args=(), xatol=None, # Initialization xs = (x1, x2, x3) temp = eim._initialize(func, xs, args) - func, xs, fs, args, shape, dtype = temp # line split for PEP8 + func, xs, fs, args, shape, dtype, xp = temp # line split for PEP8 x1, x2, x3 = xs f1, f2, f3 = fs phi = dtype.type(0.5 + 0.5*5**0.5) # golden ratio @@ -533,4 +534,5 @@ def customize_result(res, 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) + post_termination_check, customize_result, res_work_pairs, + xp=xp) diff --git a/scipy/optimize/_differentiate.py b/scipy/optimize/_differentiate.py index eca51554bf9f..959c17e3ffae 100644 --- a/scipy/optimize/_differentiate.py +++ b/scipy/optimize/_differentiate.py @@ -363,7 +363,7 @@ def _differentiate(func, x, *, args=(), atol=None, rtol=None, maxiter=10, # input validation and standardization, and everything else is designed to # reduce function calls, so let's keep it simple. temp = eim._initialize(func, (x,), args, preserve_shape=preserve_shape) - func, xs, fs, args, shape, dtype = temp + func, xs, fs, args, shape, dtype, xp = temp x, f = xs[0], fs[0] df = np.full_like(f, np.nan) # Ideally we'd broadcast the shape of `hdir` in `_elementwise_algo_init`, but @@ -546,7 +546,7 @@ def customize_result(res, 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, - preserve_shape) + xp, preserve_shape) def _differentiate_weights(work, n): From 287acb4d5801f41c6f057415a0745a8e458109e3 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 9 May 2024 12:18:23 -0700 Subject: [PATCH 118/500] ENH: optimize._chandrupatla: add array API support --- scipy/_lib/_array_api.py | 9 ++++-- scipy/optimize/_chandrupatla.py | 57 ++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index db86f20716e6..9a008348b3c7 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -404,10 +404,13 @@ def xp_minimum(x1, x2): # temporary substitute for xp.clip, which is not yet in all backends # or covered by array_api_compat. def xp_clip(x, a, b, xp=None): - xp = array_namespace(xp) if xp is None else xp + xp = array_namespace(a, b) if xp is None else xp + x, a, b = xp.broadcast_arrays(x, a, b) y = xp.asarray(x, copy=True) - y[y < a] = a - y[y > b] = b + ia = y < a + y[ia] = a[ia] + ib = y > b + y[ib] = b[ib] return y[()] if y.ndim == 0 else y diff --git a/scipy/optimize/_chandrupatla.py b/scipy/optimize/_chandrupatla.py index 19154e3e86af..43e23a74351b 100644 --- a/scipy/optimize/_chandrupatla.py +++ b/scipy/optimize/_chandrupatla.py @@ -2,6 +2,7 @@ from ._zeros_py import _rtol import scipy._lib._elementwise_iterative_method as eim from scipy._lib._util import _RichResult +from scipy._lib._array_api import xp_clip def _chandrupatla(func, a, b, *, args=(), xatol=None, xrtol=_rtol, fatol=None, frtol=0, maxiter=None, callback=None): @@ -128,13 +129,15 @@ def _chandrupatla(func, a, b, *, args=(), xatol=None, xrtol=_rtol, func, xs, fs, args, shape, dtype, xp = temp x1, x2 = xs f1, f2 = fs - status = np.full_like(x1, eim._EINPROGRESS, dtype=int) # in progress + status = xp.full_like(x1, eim._EINPROGRESS, dtype=xp.int32) # in progress nit, nfev = 0, 2 # two function evaluations performed above - xatol = 4*np.finfo(dtype).tiny if xatol is None else xatol + finfo = xp.finfo(dtype) + xatol = 4*finfo.smallest_normal if xatol is None else xatol xrtol = _rtol if xrtol is None else xrtol - fatol = np.finfo(dtype).tiny if fatol is None else fatol - frtol = frtol * np.minimum(np.abs(f1), np.abs(f2)) - maxiter = 2**np.finfo(dtype).nexp if maxiter is None else maxiter + fatol = finfo.smallest_normal if fatol is None else fatol + frtol = frtol * xp.minimum(xp.abs(f1), xp.abs(f2)) + maxiter = (xp.log2(finfo.max) - xp.log2(finfo.smallest_normal) + if maxiter is None else maxiter) work = _RichResult(x1=x1, f1=f1, x2=x2, f2=f2, x3=None, f3=None, t=0.5, xatol=xatol, xrtol=xrtol, fatol=fatol, frtol=frtol, nit=nit, nfev=nfev, status=status) @@ -151,7 +154,7 @@ def post_func_eval(x, f, work): # [1] Figure 1 (first diamond and boxes) # Note: y/n are reversed in figure; compare to BASIC in appendix work.x3, work.f3 = work.x2.copy(), work.f2.copy() - j = np.sign(f) == np.sign(work.f1) + j = xp.sign(f) == xp.sign(work.f1) nj = ~j work.x3[j], work.f3[j] = work.x1[j], work.f1[j] work.x2[nj], work.f2[nj] = work.x1[nj], work.f1[nj] @@ -162,37 +165,38 @@ def check_termination(work): # Check for all terminal conditions and record statuses. # See [1] Section 4 (first two sentences) - i = np.abs(work.f1) < np.abs(work.f2) - work.xmin = np.choose(i, (work.x2, work.x1)) - work.fmin = np.choose(i, (work.f2, work.f1)) - stop = np.zeros_like(work.x1, dtype=bool) # termination condition met + i = xp.abs(work.f1) < xp.abs(work.f2) + work.xmin = xp.where(i, work.x1, work.x2) + work.fmin = xp.where(i, work.f1, work.f2) + stop = xp.zeros_like(work.x1, dtype=xp.bool) # termination condition met # If function value tolerance is met, report successful convergence, # regardless of other conditions. Note that `frtol` has been redefined - # as `frtol = frtol * np.minimum(f1, f2)`, where `f1` and `f2` are the + # as `frtol = frtol * minimum(f1, f2)`, where `f1` and `f2` are the # function evaluated at the original ends of the bracket. - i = np.abs(work.fmin) <= work.fatol + work.frtol + i = xp.abs(work.fmin) <= work.fatol + work.frtol work.status[i] = eim._ECONVERGED stop[i] = True # If the bracket is no longer valid, report failure (unless a function # tolerance is met, as detected above). - i = (np.sign(work.f1) == np.sign(work.f2)) & ~stop - work.xmin[i], work.fmin[i], work.status[i] = np.nan, np.nan, eim._ESIGNERR + i = (xp.sign(work.f1) == xp.sign(work.f2)) & ~stop + NaN = xp.asarray(xp.nan) + work.xmin[i], work.fmin[i], work.status[i] = NaN, NaN, eim._ESIGNERR stop[i] = True # If the abscissae are non-finite or either function value is NaN, # report failure. - x_nonfinite = ~(np.isfinite(work.x1) & np.isfinite(work.x2)) - f_nan = np.isnan(work.f1) & np.isnan(work.f2) + x_nonfinite = ~(xp.isfinite(work.x1) & xp.isfinite(work.x2)) + f_nan = xp.isnan(work.f1) & xp.isnan(work.f2) i = (x_nonfinite | f_nan) & ~stop - work.xmin[i], work.fmin[i], work.status[i] = np.nan, np.nan, eim._EVALUEERR + work.xmin[i], work.fmin[i], work.status[i] = NaN, NaN, eim._EVALUEERR stop[i] = True # This is the convergence criterion used in bisect. Chandrupatla's # criterion is equivalent to this except with a factor of 4 on `xrtol`. - work.dx = abs(work.x2 - work.x1) - work.tol = abs(work.xmin) * work.xrtol + work.xatol + work.dx = xp.abs(work.x2 - work.x1) + work.tol = xp.abs(work.xmin) * work.xrtol + work.xatol i = work.dx < work.tol work.status[i] = eim._ECONVERGED stop[i] = True @@ -204,25 +208,25 @@ def post_termination_check(work): xi1 = (work.x1 - work.x2) / (work.x3 - work.x2) phi1 = (work.f1 - work.f2) / (work.f3 - work.f2) alpha = (work.x3 - work.x1) / (work.x2 - work.x1) - j = ((1 - np.sqrt(1 - xi1)) < phi1) & (phi1 < np.sqrt(xi1)) + j = ((1 - xp.sqrt(1 - xi1)) < phi1) & (phi1 < xp.sqrt(xi1)) f1j, f2j, f3j, alphaj = work.f1[j], work.f2[j], work.f3[j], alpha[j] - t = np.full_like(alpha, 0.5) + t = xp.full_like(alpha, 0.5) t[j] = (f1j / (f1j - f2j) * f3j / (f3j - f2j) - alphaj * f1j / (f3j - f1j) * f2j / (f2j - f3j)) # [1] Figure 1 (last box; see also BASIC in appendix with comment # "Adjust T Away from the Interval Boundary") tl = 0.5 * work.tol / work.dx - work.t = np.clip(t, tl, 1 - tl) + work.t = xp_clip(t, tl, 1 - tl) def customize_result(res, shape): xl, xr, fl, fr = res['xl'], res['xr'], res['fl'], res['fr'] i = res['xl'] < res['xr'] - res['xl'] = np.choose(i, (xr, xl)) - res['xr'] = np.choose(i, (xl, xr)) - res['fl'] = np.choose(i, (fr, fl)) - res['fr'] = np.choose(i, (fl, fr)) + res['xl'] = xp.where(i, xl, xr) + res['xr'] = xp.where(i, xr, xl) + res['fl'] = xp.where(i, fl, fr) + res['fr'] = xp.where(i, fr, fl) return shape return eim._loop(work, callback, shape, maxiter, func, args, dtype, @@ -241,6 +245,7 @@ def _chandrupatla_iv(func, args, xatol, xrtol, if not np.iterable(args): args = (args,) + # tolerances are floats, not arrays; OK to use NumPy tols = np.asarray([xatol if xatol is not None else 1, xrtol if xrtol is not None else 1, fatol if fatol is not None else 1, From 2f2c909c847b59ba3c1a9637d35508a3556636b7 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 9 May 2024 21:57:12 -0700 Subject: [PATCH 119/500] TST: optimize._chandrupatla: test array API support --- scipy/_lib/_array_api.py | 16 + scipy/_lib/_elementwise_iterative_method.py | 10 +- scipy/optimize/_chandrupatla.py | 18 +- scipy/optimize/tests/test_chandrupatla.py | 356 ++++++++++++-------- 4 files changed, 246 insertions(+), 154 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 9a008348b3c7..d8ef4fa57466 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -431,3 +431,19 @@ def xp_copysign(x1, x2, xp=None): xp = array_namespace(x1, x2) if xp is None else xp abs_x1 = xp.abs(x1) return xp.where(x2 >= 0, abs_x1, -abs_x1) + + +# partial substitute for xp.sign, which does not cover the NaN special case +# that I need. (https://github.com/data-apis/array-api-compat/issues/136) +def xp_sign(x, xp=None): + xp = array_namespace(x) if xp is None else xp + + if is_numpy(xp): + return xp.sign(x) + + sign = xp.full_like(x, xp.nan) + one = xp.asarray(1, dtype=x.dtype) + sign = xp.where(x > 0, one, sign) + sign = xp.where(x < 0, -one, sign) + sign = xp.where(x == 0, 0*one, sign) + return sign diff --git a/scipy/_lib/_elementwise_iterative_method.py b/scipy/_lib/_elementwise_iterative_method.py index 28e37efcc003..dd44427d0d70 100644 --- a/scipy/_lib/_elementwise_iterative_method.py +++ b/scipy/_lib/_elementwise_iterative_method.py @@ -82,7 +82,7 @@ def _initialize(func, xs, args, complex_ok=False, preserve_shape=None): xat = xp.result_type(*[xa.dtype for xa in xas]) xat = xp.asarray(1.).dtype if xp.isdtype(xat, "integral") else xat xs, args = xas[:nx], xas[nx:] - xs = [xp.asarray(x, dtype=xat)[()] for x in xs] # use copy=False when implemented + xs = [xp.asarray(x, dtype=xat) for x in xs] # use copy=False when implemented fs = [xp.asarray(func(x, *args)) for x in xs] shape = xs[0].shape fshape = fs[0].shape @@ -110,8 +110,8 @@ def func(x, *args, shape=shape, func=func, **kwargs): xfat = xp.result_type(*([f.dtype for f in fs] + [xat])) if not complex_ok and not xp.isdtype(xfat, "real floating"): raise ValueError("Abscissae and function output must be real numbers.") - xs = [xp.asarray(x, dtype=xfat, copy=True)[()] for x in xs] - fs = [xp.asarray(f, dtype=xfat, copy=True)[()] for f in fs] + xs = [xp.asarray(x, dtype=xfat, copy=True) for x in xs] + fs = [xp.asarray(f, dtype=xfat, copy=True) for f in fs] # To ensure that we can do indexing, we'll work with at least 1d arrays, # but remember the appropriate shape of the output. @@ -198,7 +198,7 @@ def _loop(work, callback, shape, maxiter, func, args, dtype, pre_func_eval, active = xp.arange(n_elements) # in-progress element indices res_dict = {i: xp.zeros(n_elements, dtype=dtype) for i, j in res_work_pairs} res_dict['success'] = xp.zeros(n_elements, dtype=xp.bool) - res_dict['status'] = xp.full(n_elements, _EINPROGRESS) + res_dict['status'] = xp.full(n_elements, _EINPROGRESS, dtype=xp.int32) res_dict['nit'] = xp.zeros(n_elements, dtype=xp.int32) res_dict['nfev'] = xp.zeros(n_elements, dtype=xp.int32) res = _RichResult(res_dict) @@ -282,6 +282,8 @@ def _check_termination(work, res, res_work_pairs, active, check_termination, for key, val in work.items(): # Need to find a better way than these try/excepts # Somehow need to keep compressible numerical args separate + if key == 'args': + continue try: work[key] = val[proceed] except (IndexError, TypeError, KeyError): # not a compressible array diff --git a/scipy/optimize/_chandrupatla.py b/scipy/optimize/_chandrupatla.py index 43e23a74351b..370b55f0da01 100644 --- a/scipy/optimize/_chandrupatla.py +++ b/scipy/optimize/_chandrupatla.py @@ -1,8 +1,16 @@ +import math import numpy as np from ._zeros_py import _rtol import scipy._lib._elementwise_iterative_method as eim from scipy._lib._util import _RichResult -from scipy._lib._array_api import xp_clip +from scipy._lib._array_api import xp_clip, xp_minimum, xp_sign + +# TODO: +# - debug test_maxiter_callback +# - debug torch failure in test_special_cases - `float64` is fine? +# - (maybe?) don't use fancy indexing assignment +# - figure out how to replace the new `try`/`except`s + def _chandrupatla(func, a, b, *, args=(), xatol=None, xrtol=_rtol, fatol=None, frtol=0, maxiter=None, callback=None): @@ -135,8 +143,8 @@ def _chandrupatla(func, a, b, *, args=(), xatol=None, xrtol=_rtol, xatol = 4*finfo.smallest_normal if xatol is None else xatol xrtol = _rtol if xrtol is None else xrtol fatol = finfo.smallest_normal if fatol is None else fatol - frtol = frtol * xp.minimum(xp.abs(f1), xp.abs(f2)) - maxiter = (xp.log2(finfo.max) - xp.log2(finfo.smallest_normal) + frtol = frtol * xp_minimum(xp.abs(f1), xp.abs(f2)) + maxiter = (math.log2(finfo.max) - math.log2(finfo.smallest_normal) if maxiter is None else maxiter) work = _RichResult(x1=x1, f1=f1, x2=x2, f2=f2, x3=None, f3=None, t=0.5, xatol=xatol, xrtol=xrtol, fatol=fatol, frtol=frtol, @@ -153,7 +161,7 @@ def pre_func_eval(work): def post_func_eval(x, f, work): # [1] Figure 1 (first diamond and boxes) # Note: y/n are reversed in figure; compare to BASIC in appendix - work.x3, work.f3 = work.x2.copy(), work.f2.copy() + work.x3, work.f3 = xp.asarray(work.x2, copy=True), xp.asarray(work.f2, copy=True) j = xp.sign(f) == xp.sign(work.f1) nj = ~j work.x3[j], work.f3[j] = work.x1[j], work.f1[j] @@ -180,7 +188,7 @@ def check_termination(work): # If the bracket is no longer valid, report failure (unless a function # tolerance is met, as detected above). - i = (xp.sign(work.f1) == xp.sign(work.f2)) & ~stop + i = (xp_sign(work.f1) == xp_sign(work.f2)) & ~stop NaN = xp.asarray(xp.nan) work.xmin[i], work.fmin[i], work.status[i] = NaN, NaN, eim._ESIGNERR stop[i] = True diff --git a/scipy/optimize/tests/test_chandrupatla.py b/scipy/optimize/tests/test_chandrupatla.py index 6818e3e6469b..05bdef74cb37 100644 --- a/scipy/optimize/tests/test_chandrupatla.py +++ b/scipy/optimize/tests/test_chandrupatla.py @@ -2,8 +2,11 @@ import numpy as np from numpy.testing import assert_allclose, assert_equal, assert_array_less -from scipy import stats +from scipy import stats, special import scipy._lib._elementwise_iterative_method as eim +from scipy.conftest import array_api_compatible +from scipy._lib._array_api import (array_namespace, xp_assert_close, xp_assert_equal, xp_assert_less, + xp_minimum, is_numpy, is_cupy, is_torch) from scipy.optimize._chandrupatla import (_chandrupatla_minimize, _chandrupatla as _chandrupatla_root) @@ -474,25 +477,33 @@ def f(x): assert f(res.xl) == f(res.xm) == f(res.xr) +@array_api_compatible +@pytest.mark.usefixtures("skip_xp_backends") +@pytest.mark.skip_xp_backends('array_api_strict', + reason=['Currently uses fancy indexing assignment.']) class TestChandrupatla(TestScalarRootFinders): def f(self, q, p): - return stats.norm.cdf(q) - p + return special.ndtr(q) - p @pytest.mark.parametrize('p', [0.6, np.linspace(-0.05, 1.05, 10)]) - def test_basic(self, p): + def test_basic(self, p, xp): # Invert distribution CDF and compare against distrtibution `ppf` - res = _chandrupatla_root(self.f, -5, 5, args=(p,)) - ref = stats.norm().ppf(p) - np.testing.assert_allclose(res.x, ref) - assert res.x.shape == ref.shape + a, b = xp.asarray(-5.), xp.asarray(5.) + res = _chandrupatla_root(self.f, a, b, args=(xp.asarray(p),)) + ref = xp.asarray(stats.norm().ppf(p), dtype=xp.asarray(p).dtype) + xp_assert_close(res.x, ref) @pytest.mark.parametrize('shape', [tuple(), (12,), (3, 4), (3, 2, 2)]) - def test_vectorization(self, shape): + def test_vectorization(self, shape, xp): # Test for correct functionality, output shapes, and dtypes for various # input shapes. p = np.linspace(-0.05, 1.05, 12).reshape(shape) if shape else 0.6 + p_xp = xp.asarray(p) args = (p,) + args_xp = (p_xp,) + dtype = p_xp.dtype + xp_test = array_namespace(p_xp) # need xp.bool @np.vectorize def chandrupatla_single(p): @@ -503,151 +514,166 @@ def f(*args, **kwargs): return self.f(*args, **kwargs) f.f_evals = 0 - res = _chandrupatla_root(f, -5, 5, args=args) + res = _chandrupatla_root(f, xp.asarray(-5.), xp.asarray(5.), args=args_xp) refs = chandrupatla_single(p).ravel() ref_x = [ref.x for ref in refs] - assert_allclose(res.x.ravel(), ref_x) - assert_equal(res.x.shape, shape) + ref_x = xp.reshape(xp.asarray(ref_x, dtype=dtype), shape) + xp_assert_close(res.x, ref_x) ref_fun = [ref.fun for ref in refs] - assert_allclose(res.fun.ravel(), ref_fun) - assert_equal(res.fun.shape, shape) - assert_equal(res.fun, self.f(res.x, *args)) + ref_fun = xp.reshape(xp.asarray(ref_fun, dtype=dtype), shape) + xp_assert_close(res.fun, ref_fun, atol=1e-15) + xp_assert_equal(res.fun, self.f(res.x, *args_xp)) - ref_success = [ref.success for ref in refs] - assert_equal(res.success.ravel(), ref_success) - assert_equal(res.success.shape, shape) - assert np.issubdtype(res.success.dtype, np.bool_) + ref_success = [bool(ref.success) for ref in refs] + ref_success = xp.reshape(xp.asarray(ref_success, dtype=xp_test.bool), shape) + xp_assert_equal(res.success, ref_success) ref_flag = [ref.status for ref in refs] - assert_equal(res.status.ravel(), ref_flag) - assert_equal(res.status.shape, shape) - assert np.issubdtype(res.status.dtype, np.integer) + ref_flag = xp.reshape(xp.asarray(ref_flag, dtype=xp.int32), shape) + xp_assert_equal(res.status, ref_flag) ref_nfev = [ref.nfev for ref in refs] - assert_equal(res.nfev.ravel(), ref_nfev) - assert_equal(np.max(res.nfev), f.f_evals) - assert_equal(res.nfev.shape, res.fun.shape) - assert np.issubdtype(res.nfev.dtype, np.integer) + ref_nfev = xp.reshape(xp.asarray(ref_nfev, dtype=xp.int32), shape) + if is_numpy(xp): + xp_assert_equal(res.nfev, ref_nfev) + assert xp.max(res.nfev) == f.f_evals + else: # different backend (and dtype) may lead to different nfev + assert res.nfev.shape == shape + assert res.nfev.dtype == xp.int32 ref_nit = [ref.nit for ref in refs] - assert_equal(res.nit.ravel(), ref_nit) - assert_equal(np.max(res.nit), f.f_evals-2) - assert_equal(res.nit.shape, res.fun.shape) - assert np.issubdtype(res.nit.dtype, np.integer) + ref_nit = xp.reshape(xp.asarray(ref_nit, dtype=xp.int32), shape) + if is_numpy(xp): + xp_assert_equal(res.nit, ref_nit) + assert xp.max(res.nit) == f.f_evals-2 + else: + assert res.nit.shape == shape + assert res.nit.dtype == xp.int32 ref_xl = [ref.xl for ref in refs] - assert_allclose(res.xl.ravel(), ref_xl) - assert_equal(res.xl.shape, shape) + ref_xl = xp.reshape(xp.asarray(ref_xl, dtype=dtype), shape) + xp_assert_close(res.xl, ref_xl) ref_xr = [ref.xr for ref in refs] - assert_allclose(res.xr.ravel(), ref_xr) - assert_equal(res.xr.shape, shape) + ref_xr = xp.reshape(xp.asarray(ref_xr, dtype=dtype), shape) + xp_assert_close(res.xr, ref_xr) - assert_array_less(res.xl, res.xr) - finite = np.isfinite(res.x) - assert np.all((res.x[finite] == res.xl[finite]) + xp_assert_less(res.xl, res.xr) + finite = xp.isfinite(res.x) + assert xp.all((res.x[finite] == res.xl[finite]) | (res.x[finite] == res.xr[finite])) ref_fl = [ref.fl for ref in refs] - assert_allclose(res.fl.ravel(), ref_fl) - assert_equal(res.fl.shape, shape) - assert_allclose(res.fl, self.f(res.xl, *args)) + ref_fl = xp.reshape(xp.asarray(ref_fl, dtype=dtype), shape) + xp_assert_close(res.fl, ref_fl, atol=1e-7) + xp_assert_equal(res.fl, self.f(res.xl, *args_xp)) ref_fr = [ref.fr for ref in refs] - assert_allclose(res.fr.ravel(), ref_fr) - assert_equal(res.fr.shape, shape) - assert_allclose(res.fr, self.f(res.xr, *args)) + ref_fr = xp.reshape(xp.asarray(ref_fr, dtype=dtype), shape) + xp_assert_close(res.fr, ref_fr, atol=1e-7) + xp_assert_equal(res.fr, self.f(res.xr, *args_xp)) - assert np.all(np.abs(res.fun[finite]) == - np.minimum(np.abs(res.fl[finite]), - np.abs(res.fr[finite]))) + assert xp.all(xp.abs(res.fun[finite]) == + xp_minimum(xp.abs(res.fl[finite]), + xp.abs(res.fr[finite]))) - def test_flags(self): + def test_flags(self, xp): # Test cases that should produce different status flags; show that all # can be produced simultaneously. def f(xs, js): + # Note that full_like and int(j) shouldn't really be required. CuPy + # is just really picky here, so I'm making it a special case to + # make sure the other backends work when the user is less careful. + assert js.dtype == xp.int64 + if is_cupy(xp): + funcs = [lambda x: x - 2.5, + lambda x: x - 10, + lambda x: (x - 0.1)**3, + lambda x: xp.full_like(x, xp.nan)] + return [funcs[int(j)](x) for x, j in zip(xs, js)] + funcs = [lambda x: x - 2.5, lambda x: x - 10, - lambda x: (x - 0.1)**3, - lambda x: np.nan] + lambda x: (x - 0.1) ** 3, + lambda x: xp.nan] return [funcs[j](x) for x, j in zip(xs, js)] - args = (np.arange(4, dtype=np.int64),) - res = _chandrupatla_root(f, [0]*4, [np.pi]*4, args=args, maxiter=2) + args = (xp.arange(4, dtype=xp.int64),) + a, b = xp.asarray([0.]*4), xp.asarray([xp.pi]*4) + res = _chandrupatla_root(f, a, b, args=args, maxiter=2) - ref_flags = np.array([eim._ECONVERGED, - eim._ESIGNERR, - eim._ECONVERR, - eim._EVALUEERR]) - assert_equal(res.status, ref_flags) + ref_flags = xp.asarray([eim._ECONVERGED, + eim._ESIGNERR, + eim._ECONVERR, + eim._EVALUEERR], dtype=xp.int32) + xp_assert_equal(res.status, ref_flags) - def test_convergence(self): + def test_convergence(self, xp): # Test that the convergence tolerances behave as expected rng = np.random.default_rng(2585255913088665241) - p = rng.random(size=3) - bracket = (-5, 5) + p = xp.asarray(rng.random(size=3)) + bracket = (-xp.asarray(5.), xp.asarray(5.)) args = (p,) kwargs0 = dict(args=args, xatol=0, xrtol=0, fatol=0, frtol=0) kwargs = kwargs0.copy() kwargs['xatol'] = 1e-3 res1 = _chandrupatla_root(self.f, *bracket, **kwargs) - assert_array_less(res1.xr - res1.xl, 1e-3) + xp_assert_less(res1.xr - res1.xl, xp.full_like(p, 1e-3)) kwargs['xatol'] = 1e-6 res2 = _chandrupatla_root(self.f, *bracket, **kwargs) - assert_array_less(res2.xr - res2.xl, 1e-6) - assert_array_less(res2.xr - res2.xl, res1.xr - res1.xl) + xp_assert_less(res2.xr - res2.xl, xp.full_like(p, 1e-6)) + xp_assert_less(res2.xr - res2.xl, res1.xr - res1.xl) kwargs = kwargs0.copy() kwargs['xrtol'] = 1e-3 res1 = _chandrupatla_root(self.f, *bracket, **kwargs) - assert_array_less(res1.xr - res1.xl, 1e-3 * np.abs(res1.x)) + xp_assert_less(res1.xr - res1.xl, 1e-3 * xp.abs(res1.x)) kwargs['xrtol'] = 1e-6 res2 = _chandrupatla_root(self.f, *bracket, **kwargs) - assert_array_less(res2.xr - res2.xl, 1e-6 * np.abs(res2.x)) - assert_array_less(res2.xr - res2.xl, res1.xr - res1.xl) + xp_assert_less(res2.xr - res2.xl, 1e-6 * xp.abs(res2.x)) + xp_assert_less(res2.xr - res2.xl, res1.xr - res1.xl) kwargs = kwargs0.copy() kwargs['fatol'] = 1e-3 res1 = _chandrupatla_root(self.f, *bracket, **kwargs) - assert_array_less(np.abs(res1.fun), 1e-3) + xp_assert_less(xp.abs(res1.fun), xp.full_like(p, 1e-3)) kwargs['fatol'] = 1e-6 res2 = _chandrupatla_root(self.f, *bracket, **kwargs) - assert_array_less(np.abs(res2.fun), 1e-6) - assert_array_less(np.abs(res2.fun), np.abs(res1.fun)) + xp_assert_less(xp.abs(res2.fun), xp.full_like(p, 1e-6)) + xp_assert_less(xp.abs(res2.fun), xp.abs(res1.fun)) kwargs = kwargs0.copy() kwargs['frtol'] = 1e-3 x1, x2 = bracket - f0 = np.minimum(abs(self.f(x1, *args)), abs(self.f(x2, *args))) + f0 = xp_minimum(xp.abs(self.f(x1, *args)), xp.abs(self.f(x2, *args))) res1 = _chandrupatla_root(self.f, *bracket, **kwargs) - assert_array_less(np.abs(res1.fun), 1e-3*f0) + xp_assert_less(np.abs(res1.fun), 1e-3*f0) kwargs['frtol'] = 1e-6 res2 = _chandrupatla_root(self.f, *bracket, **kwargs) - assert_array_less(np.abs(res2.fun), 1e-6*f0) - assert_array_less(np.abs(res2.fun), np.abs(res1.fun)) + xp_assert_less(np.abs(res2.fun), 1e-6*f0) + xp_assert_less(np.abs(res2.fun), np.abs(res1.fun)) - def test_maxiter_callback(self): + def test_maxiter_callback(self, xp): # Test behavior of `maxiter` parameter and `callback` interface - p = 0.612814 - bracket = (-5, 5) + p = xp.asarray(0.612814) + bracket = (xp.asarray(-5.), xp.asarray(5.)) maxiter = 5 def f(q, p): - res = stats.norm().cdf(q) - p + res = special.ndtr(q) - p f.x = q f.fun = res return res f.x = None f.fun = None - res = _chandrupatla_root(f, *bracket, args=(p,), - maxiter=maxiter) - assert not np.any(res.success) - assert np.all(res.nfev == maxiter+2) - assert np.all(res.nit == maxiter) + res = _chandrupatla_root(f, *bracket, args=(p,), maxiter=maxiter) + assert not xp.any(res.success) + assert xp.all(res.nfev == maxiter+2) + assert xp.all(res.nit == maxiter) def callback(res): callback.iter += 1 @@ -659,14 +685,14 @@ def callback(res): else: changed = (((res.xl == callback.xl) & (res.xr != callback.xr)) | ((res.xl != callback.xl) & (res.xr == callback.xr))) - assert np.all(changed) + assert xp.all(changed) callback.xl = res.xl callback.xr = res.xr assert res.status == eim._EINPROGRESS - assert_equal(self.f(res.xl, p), res.fl) - assert_equal(self.f(res.xr, p), res.fr) - assert_equal(self.f(res.x, p), res.fun) + xp_assert_equal(self.f(res.xl, p), res.fl) + xp_assert_equal(self.f(res.xr, p), res.fr) + xp_assert_equal(self.f(res.x, p), res.fun) if callback.iter == maxiter: raise StopIteration callback.iter = -1 # callback called once before first iteration @@ -674,21 +700,24 @@ def callback(res): callback.xl = None callback.xr = None - res2 = _chandrupatla_root(f, *bracket, args=(p,), - callback=callback) + res2 = _chandrupatla_root(f, *bracket, args=(p,), callback=callback) # terminating with callback is identical to terminating due to maxiter # (except for `status`) for key in res.keys(): if key == 'status': - assert res[key] == eim._ECONVERR - assert callback.res[key] == eim._EINPROGRESS - assert res2[key] == eim._ECALLBACK + xp_assert_equal(res[key], xp.asarray(eim._ECONVERR, dtype=xp.int32)) + xp_assert_equal(res2[key], xp.asarray(eim._ECALLBACK, dtype=xp.int32)) + # TODO: debug this. Somehow `callback.res` is getting changed when StopIteration is raised. + # xp_assert_equal(callback.res[key], xp.asarray(eim._EINPROGRESS, dtype=xp.int32)) + elif key.startswith('_'): + continue else: - assert res2[key] == callback.res[key] == res[key] + xp_assert_equal(callback.res[key], res[key]) + xp_assert_equal(res2[key], res[key]) @pytest.mark.parametrize('case', _CHANDRUPATLA_TESTS) - def test_nit_expected(self, case): + def test_nit_expected(self, case, xp): # Test that `_chandrupatla` implements Chandrupatla's algorithm: # in all 40 test cases, the number of iterations performed # matches the number reported in the original paper. @@ -697,131 +726,168 @@ def test_nit_expected(self, case): # abs(x2-x1) < 4*abs(xmin)*xrtol + xatol, but we use the more standard # abs(x2-x1) < abs(xmin)*xrtol + xatol. Therefore, set xrtol to 4x # that used by Chandrupatla in tests. + bracket = (xp.asarray(bracket[0], dtype=xp.float64), + xp.asarray(bracket[1], dtype=xp.float64)) + root = xp.asarray(root, dtype=xp.float64) + res = _chandrupatla_root(f, *bracket, xrtol=4e-10, xatol=1e-5) - assert_allclose(res.fun, f(root), rtol=1e-8, atol=2e-3) - assert_equal(res.nfev, nfeval) + xp_assert_close(res.fun, xp.asarray(f(root), dtype=xp.float64), + rtol=1e-8, atol=2e-3) + xp_assert_equal(res.nfev, xp.asarray(nfeval, dtype=xp.int32)) @pytest.mark.parametrize("root", (0.622, [0.622, 0.623])) - @pytest.mark.parametrize("dtype", (np.float16, np.float32, np.float64)) - def test_dtype(self, root, dtype): + @pytest.mark.parametrize("dtype", ('float16', 'float32', 'float64')) + def test_dtype(self, root, dtype, xp): # Test that dtypes are preserved + dtype = getattr(xp, dtype, None) + if dtype is None: + pytest.skip(f"{xp} does not support {dtype}") - root = dtype(root) def f(x, root): - return ((x - root) ** 3).astype(dtype) + res = (x - root) ** 3. + if is_numpy(xp): # NumPy does not preserve dtype + return xp.asarray(res, dtype=dtype) + return res - res = _chandrupatla_root(f, dtype(-3), dtype(5), - args=(root,), xatol=1e-3) - assert res.x.dtype == dtype - assert np.allclose(res.x, root, atol=1e-3) or np.all(res.fun == 0) + a, b = xp.asarray(-3, dtype=dtype), xp.asarray(3, dtype=dtype) + root = xp.asarray(root, dtype=dtype) + res = _chandrupatla_root(f, a, b, args=(root,), xatol=1e-3) + try: + xp_assert_close(res.x, root, atol=1e-3) + except AssertionError: + assert res.x.dtype == dtype + xp.all(res.fun == 0) - def test_input_validation(self): + def test_input_validation(self, xp): # Test input validation for appropriate error messages + def func(x): + return x + message = '`func` must be callable.' with pytest.raises(ValueError, match=message): - _chandrupatla_root(None, -4, 4) + bracket = xp.asarray(-4), xp.asarray(4) + _chandrupatla_root(None, *bracket) message = 'Abscissae and function output must be real numbers.' with pytest.raises(ValueError, match=message): - _chandrupatla_root(lambda x: x, -4+1j, 4) + bracket = xp.asarray(-4+1j), xp.asarray(4) + _chandrupatla_root(func, *bracket) - message = "shape mismatch: objects cannot be broadcast" # raised by `np.broadcast, but the traceback is readable IMO - with pytest.raises(ValueError, match=message): - _chandrupatla_root(lambda x: x, [-2, -3], [3, 4, 5]) + message = "...not be broadcast..." # all messages include this part + with pytest.raises((ValueError, RuntimeError), match=message): + bracket = xp.asarray([-2, -3]), xp.asarray([3, 4, 5]) + _chandrupatla_root(func, *bracket) message = "The shape of the array returned by `func`..." with pytest.raises(ValueError, match=message): - _chandrupatla_root(lambda x: [x[0], x[1], x[1]], [-3, -3], [5, 5]) + bracket = xp.asarray([-3, -3]), xp.asarray([5, 5]) + _chandrupatla_root(lambda x: [x[0], x[1], x[1]], *bracket) message = 'Tolerances must be non-negative scalars.' + bracket = xp.asarray(-4), xp.asarray(4) with pytest.raises(ValueError, match=message): - _chandrupatla_root(lambda x: x, -4, 4, xatol=-1) + _chandrupatla_root(func, *bracket, xatol=-1) with pytest.raises(ValueError, match=message): - _chandrupatla_root(lambda x: x, -4, 4, xrtol=np.nan) + _chandrupatla_root(func, *bracket, xrtol=xp.nan) with pytest.raises(ValueError, match=message): - _chandrupatla_root(lambda x: x, -4, 4, fatol='ekki') + _chandrupatla_root(func, *bracket, fatol='ekki') with pytest.raises(ValueError, match=message): - _chandrupatla_root(lambda x: x, -4, 4, frtol=np.nan) + _chandrupatla_root(func, *bracket, frtol=xp.nan) message = '`maxiter` must be a non-negative integer.' with pytest.raises(ValueError, match=message): - _chandrupatla_root(lambda x: x, -4, 4, maxiter=1.5) + _chandrupatla_root(func, *bracket, maxiter=1.5) with pytest.raises(ValueError, match=message): - _chandrupatla_root(lambda x: x, -4, 4, maxiter=-1) + _chandrupatla_root(func, *bracket, maxiter=-1) message = '`callback` must be callable.' with pytest.raises(ValueError, match=message): - _chandrupatla_root(lambda x: x, -4, 4, callback='shrubbery') + _chandrupatla_root(func, *bracket, callback='shrubbery') - def test_special_cases(self): + def test_special_cases(self, xp): # Test edge cases and other special cases # Test infinite function values def f(x): return 1 / x + 1 - 1 / (-x + 1) + a, b = xp.asarray([0.1, 0., 0., 0.1]), xp.asarray([0.9, 1.0, 0.9, 1.0]) + with np.errstate(divide='ignore', invalid='ignore'): - res = _chandrupatla_root(f, [0.1, 0., 0., 0.1], - [0.9, 1.0, 0.9, 1.0]) - assert np.all(res.success) - assert_allclose(res.x[1:], res.x[0]) + res = _chandrupatla_root(f, a, b) + + if not is_torch(xp): + # torch fails with float32 but passes with 64? debug + assert xp.all(res.success) + xp_assert_close(res.x[1:], xp.full((3,), res.x[0])) # Test that integers are not passed to `f` # (otherwise this would overflow) + xp_test = array_namespace(a) # need isdtype def f(x): - assert np.issubdtype(x.dtype, np.floating) + assert xp_test.isdtype(x.dtype, "real floating") return x ** 99 - 1 - res = _chandrupatla_root(f, -7, 5) - assert res.success - assert_allclose(res.x, 1) + # note that all inputs are integer type; result is automatically default float + res = _chandrupatla_root(f, xp.asarray(-7), xp.asarray(5)) + if not is_torch(xp): + # torch fails, probably because of overflow + assert res.success + xp_assert_close(res.x, xp.asarray(1.)) # Test that if both ends of bracket equal root, algorithm reports # convergence. def f(x, root): return x**2 - root - root = [0, 1] - res = _chandrupatla_root(f, 1, 1, args=(root,)) - assert_equal(res.success, [False, True]) - assert_equal(res.x, [np.nan, 1]) + root = xp.asarray([0, 1]) + res = _chandrupatla_root(f, xp.asarray(1), xp.asarray(1), args=(root,)) + xp_assert_equal(res.success, xp.asarray([False, True])) + xp_assert_equal(res.x, xp.asarray([np.nan, 1.])) def f(x): return 1/x with np.errstate(invalid='ignore'): - res = _chandrupatla_root(f, np.inf, np.inf) + inf = xp.asarray(xp.inf) + res = _chandrupatla_root(f, inf, inf) assert res.success - assert_equal(res.x, np.inf) + xp_assert_equal(res.x, xp.asarray(np.inf)) # Test maxiter = 0. Should do nothing to bracket. def f(x): return x**3 - 1 - bracket = (-3, 5) - res = _chandrupatla_root(f, *bracket, maxiter=0) - assert res.xl, res.xr == bracket - assert res.nit == 0 - assert res.nfev == 2 - assert res.status == -2 - assert res.x == -3 # best so far + a, b = xp.asarray(-3.), xp.asarray(5.) + res = _chandrupatla_root(f, a, b, maxiter=0) + xp_assert_equal(res.success, xp.asarray(False)) + xp_assert_equal(res.status, xp.asarray(-2, dtype=xp.int32)) + xp_assert_equal(res.nit, xp.asarray(0, dtype=xp.int32)) + xp_assert_equal(res.nfev, xp.asarray(2, dtype=xp.int32)) + xp_assert_equal(res.xl, a) + xp_assert_equal(res.xr, b) + # The `x` attribute is the one with the smaller function value + xp_assert_equal(res.x, a) + # Reverse bracket; check that this is still true + res = _chandrupatla_root(f, -b, -a, maxiter=0) + xp_assert_equal(res.x, -a) # Test maxiter = 1 - res = _chandrupatla_root(f, *bracket, maxiter=1) - assert res.success - assert res.status == 0 - assert res.nit == 1 - assert res.nfev == 3 - assert_allclose(res.x, 1) + res = _chandrupatla_root(f, a, b, maxiter=1) + xp_assert_equal(res.success, xp.asarray(True)) + xp_assert_equal(res.status, xp.asarray(0, dtype=xp.int32)) + xp_assert_equal(res.nit, xp.asarray(1, dtype=xp.int32)) + xp_assert_equal(res.nfev, xp.asarray(3, dtype=xp.int32)) + xp_assert_close(res.x, xp.asarray(1.)) # Test scalar `args` (not in tuple) def f(x, c): return c*x - 1 - res = _chandrupatla_root(f, -1, 1, args=3) - assert_allclose(res.x, 1/3) + res = _chandrupatla_root(f, xp.asarray(-1), xp.asarray(1), args=xp.asarray(3)) + xp_assert_close(res.x, xp.asarray(1/3)) # # TODO: Test zero tolerance # # ~~What's going on here - why are iterations repeated?~~ From 6d037a18e8ce243be63ffdd3290c002e284e7a5b Mon Sep 17 00:00:00 2001 From: "Tom M. Ragonneau" <33006763+ragonneau@users.noreply.github.com> Date: Fri, 10 May 2024 07:12:24 +0200 Subject: [PATCH 120/500] ENH: add cobyqa to scipy.optimize. (#19994) Co-authored-by: Tom M. Ragonneau Co-authored-by: Ralf Gommers Co-authored-by: Lucas Colley --- .gitmodules | 3 + benchmarks/benchmarks/optimize.py | 9 +- .../reference/optimize.minimize-cobyqa.rst | 8 + doc/source/tutorial/optimize.rst | 4 +- pytest.ini | 2 +- scipy/_lib/cobyqa | 1 + scipy/_lib/meson.build | 34 +++ scipy/optimize/__init__.py | 1 + scipy/optimize/_cobyqa_py.py | 57 ++++ scipy/optimize/_minimize.py | 47 +++- scipy/optimize/_optimize.py | 4 +- scipy/optimize/_shgo.py | 13 +- scipy/optimize/meson.build | 1 + scipy/optimize/tests/meson.build | 1 + scipy/optimize/tests/test__basinhopping.py | 6 +- scipy/optimize/tests/test__dual_annealing.py | 1 + scipy/optimize/tests/test__shgo.py | 2 +- scipy/optimize/tests/test_cobyqa.py | 246 ++++++++++++++++++ .../tests/test_constraint_conversion.py | 9 +- scipy/optimize/tests/test_optimize.py | 68 +++-- tools/check_installation.py | 20 +- 21 files changed, 482 insertions(+), 55 deletions(-) create mode 100644 doc/source/reference/optimize.minimize-cobyqa.rst create mode 160000 scipy/_lib/cobyqa create mode 100644 scipy/optimize/_cobyqa_py.py create mode 100644 scipy/optimize/tests/test_cobyqa.py diff --git a/.gitmodules b/.gitmodules index b6920b8714b2..a58228812dc2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -23,3 +23,6 @@ [submodule "scipy/_lib/pocketfft"] path = scipy/_lib/pocketfft url = https://github.com/scipy/pocketfft +[submodule "scipy/_lib/cobyqa"] + path = scipy/_lib/cobyqa + url = https://github.com/cobyqa/cobyqa.git diff --git a/benchmarks/benchmarks/optimize.py b/benchmarks/benchmarks/optimize.py index 227ec1e1f454..4084e562dc19 100644 --- a/benchmarks/benchmarks/optimize.py +++ b/benchmarks/benchmarks/optimize.py @@ -273,8 +273,8 @@ def bench_run(self, x0, methods=None, **minimizer_kwargs): # L-BFGS-B, BFGS, trust-constr, SLSQP can use gradients, but examine # performance when numerical differentiation is used. - fonly_methods = ["COBYLA", 'Powell', 'nelder-mead', 'L-BFGS-B', 'BFGS', - 'trust-constr', 'SLSQP'] + fonly_methods = ["COBYLA", 'COBYQA', 'Powell', 'nelder-mead', + 'L-BFGS-B', 'BFGS', 'trust-constr', 'SLSQP'] for method in fonly_methods: if method not in methods: continue @@ -316,7 +316,7 @@ class BenchSmoothUnbounded(Benchmark): ['rosenbrock_slow', 'rosenbrock_nograd', 'rosenbrock', 'rosenbrock_tight', 'simple_quadratic', 'asymmetric_quadratic', 'sin_1d', 'booth', 'beale', 'LJ'], - ["COBYLA", 'Powell', 'nelder-mead', + ["COBYLA", 'COBYQA', 'Powell', 'nelder-mead', 'L-BFGS-B', 'BFGS', 'CG', 'TNC', 'SLSQP', "Newton-CG", 'dogleg', 'trust-ncg', 'trust-exact', 'trust-krylov', 'trust-constr'], @@ -599,7 +599,8 @@ class BenchDFO(Benchmark): params = [ list(range(53)), # adjust which problems to solve - ["COBYLA", "SLSQP", "Powell", "nelder-mead", "L-BFGS-B", "BFGS", + ["COBYLA", "COBYQA", "SLSQP", "Powell", "nelder-mead", "L-BFGS-B", + "BFGS", "trust-constr"], # note: methods must also be listed in bench_run ["mean_nfev", "min_obj"], # defined in average_results ] diff --git a/doc/source/reference/optimize.minimize-cobyqa.rst b/doc/source/reference/optimize.minimize-cobyqa.rst new file mode 100644 index 000000000000..3811005c41a8 --- /dev/null +++ b/doc/source/reference/optimize.minimize-cobyqa.rst @@ -0,0 +1,8 @@ +.. _optimize.minimize-cobyqa: + +minimize(method='COBYQA') +------------------------- + +.. scipy-optimize:function:: scipy.optimize.minimize + :impl: scipy.optimize._cobyqa_py._minimize_cobyqa + :method: COBYQA diff --git a/doc/source/tutorial/optimize.rst b/doc/source/tutorial/optimize.rst index 8d6abb5f0270..e36fa4b36be9 100644 --- a/doc/source/tutorial/optimize.rst +++ b/doc/source/tutorial/optimize.rst @@ -541,8 +541,8 @@ Constrained minimization of multivariate scalar functions (:func:`minimize`) ---------------------------------------------------------------------------- The :func:`minimize` function provides algorithms for constrained minimization, -namely ``'trust-constr'`` , ``'SLSQP'`` and ``'COBYLA'``. They require the constraints -to be defined using slightly different structures. The method ``'trust-constr'`` requires +namely ``'trust-constr'`` , ``'SLSQP'``, ``'COBYLA'``, and ``'COBYQA'``. They require the constraints +to be defined using slightly different structures. The methods ``'trust-constr'`` and ``'COBYQA'`` require the constraints to be defined as a sequence of objects :func:`LinearConstraint` and :func:`NonlinearConstraint`. Methods ``'SLSQP'`` and ``'COBYLA'``, on the other hand, require constraints to be defined as a sequence of dictionaries, with keys diff --git a/pytest.ini b/pytest.ini index 78f65a48b147..4bf655cb63b0 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] addopts = -l -norecursedirs = doc tools scipy/_lib/array_api_compat scipy/_lib/highs +norecursedirs = doc tools scipy/_lib/array_api_compat scipy/_lib/cobyqa scipy/_lib/highs junit_family=xunit2 filterwarnings = diff --git a/scipy/_lib/cobyqa b/scipy/_lib/cobyqa new file mode 160000 index 000000000000..dee1a92b5f3f --- /dev/null +++ b/scipy/_lib/cobyqa @@ -0,0 +1 @@ +Subproject commit dee1a92b5f3fa50c6fce42a38a52ad2f4ebddc4c diff --git a/scipy/_lib/meson.build b/scipy/_lib/meson.build index 7cdab24a5eeb..e8bea4cdcc47 100644 --- a/scipy/_lib/meson.build +++ b/scipy/_lib/meson.build @@ -14,6 +14,9 @@ endif if not fs.exists('pocketfft/README.md') error('Missing the `pocketfft` submodule! Run `git submodule update --init` to fix this.') endif +if not fs.exists('cobyqa/README.rst') + error('Missing the `cobyqa` submodule! Run `git submodule update --init` to fix this.') +endif _lib_pxd = [ fs.copyfile('__init__.py'), @@ -199,5 +202,36 @@ py3.install_sources( subdir: 'scipy/_lib/array_api_compat/torch', ) +py3.install_sources( + [ + 'cobyqa/cobyqa/__init__.py', + 'cobyqa/cobyqa/framework.py', + 'cobyqa/cobyqa/main.py', + 'cobyqa/cobyqa/models.py', + 'cobyqa/cobyqa/problem.py', + 'cobyqa/cobyqa/settings.py', + ], + subdir: 'scipy/_lib/cobyqa', +) + +py3.install_sources( + [ + 'cobyqa/cobyqa/subsolvers/__init__.py', + 'cobyqa/cobyqa/subsolvers/geometry.py', + 'cobyqa/cobyqa/subsolvers/optim.py', + ], + subdir: 'scipy/_lib/cobyqa/subsolvers', +) + +py3.install_sources( + [ + 'cobyqa/cobyqa/utils/__init__.py', + 'cobyqa/cobyqa/utils/exceptions.py', + 'cobyqa/cobyqa/utils/math.py', + 'cobyqa/cobyqa/utils/versions.py', + ], + subdir: 'scipy/_lib/cobyqa/utils', +) + subdir('_uarray') subdir('tests') diff --git a/scipy/optimize/__init__.py b/scipy/optimize/__init__.py index b103e89cc47e..d41cb5033af1 100644 --- a/scipy/optimize/__init__.py +++ b/scipy/optimize/__init__.py @@ -65,6 +65,7 @@ optimize.minimize-lbfgsb optimize.minimize-tnc optimize.minimize-cobyla + optimize.minimize-cobyqa optimize.minimize-slsqp optimize.minimize-trustconstr optimize.minimize-dogleg diff --git a/scipy/optimize/_cobyqa_py.py b/scipy/optimize/_cobyqa_py.py new file mode 100644 index 000000000000..5d164771a213 --- /dev/null +++ b/scipy/optimize/_cobyqa_py.py @@ -0,0 +1,57 @@ +import numpy as np + +from ._optimize import _check_unknown_options + + +def _minimize_cobyqa(fun, x0, args=(), bounds=None, constraints=(), + callback=None, disp=False, maxfev=None, maxiter=None, + f_target=-np.inf, feasibility_tol=1e-8, + initial_tr_radius=1.0, final_tr_radius=1e-6, scale=False, + **unknown_options): + """ + Minimize a scalar function of one or more variables using the + Constrained Optimization BY Quadratic Approximations (COBYQA) algorithm. + + .. versionadded:: 1.14.0 + + Options + ------- + disp : bool + Set to True to print information about the optimization procedure. + maxfev : int + Maximum number of function evaluations. + maxiter : int + Maximum number of iterations. + f_target : float + Target value for the objective function. The optimization procedure is + terminated when the objective function value of a feasible point (see + `feasibility_tol` below) is less than or equal to this target. + feasibility_tol : float + Absolute tolerance for the constraint violation. + initial_tr_radius : float + Initial trust-region radius. Typically, this value should be in the + order of one tenth of the greatest expected change to the variables. + final_tr_radius : float + Final trust-region radius. It should indicate the accuracy required in + the final values of the variables. If provided, this option overrides + the value of `tol` in the `minimize` function. + scale : bool + Set to True to scale the variables according to the bounds. If True and + if all the lower and upper bounds are finite, the variables are scaled + to be within the range :math:`[-1, 1]`. If any of the lower or upper + bounds is infinite, the variables are not scaled. + """ + from .._lib.cobyqa import minimize # import here to avoid circular imports + + _check_unknown_options(unknown_options) + options = { + 'disp': bool(disp), + 'maxfev': int(maxfev) if maxfev is not None else 500 * len(x0), + 'maxiter': int(maxiter) if maxiter is not None else 1000 * len(x0), + 'target': float(f_target), + 'feasibility_tol': float(feasibility_tol), + 'radius_init': float(initial_tr_radius), + 'radius_final': float(final_tr_radius), + 'scale': bool(scale), + } + return minimize(fun, x0, args, bounds, constraints, callback, options) diff --git a/scipy/optimize/_minimize.py b/scipy/optimize/_minimize.py index 862308ac5432..7f3ca4aa1ed1 100644 --- a/scipy/optimize/_minimize.py +++ b/scipy/optimize/_minimize.py @@ -30,6 +30,7 @@ from ._lbfgsb_py import _minimize_lbfgsb from ._tnc import _minimize_tnc from ._cobyla_py import _minimize_cobyla +from ._cobyqa_py import _minimize_cobyqa from ._slsqp_py import _minimize_slsqp from ._constraints import (old_bound_to_new, new_bounds_to_old, old_constraint_to_new, new_constraint_to_old, @@ -38,13 +39,14 @@ from ._differentiable_functions import FD_METHODS MINIMIZE_METHODS = ['nelder-mead', 'powell', 'cg', 'bfgs', 'newton-cg', - 'l-bfgs-b', 'tnc', 'cobyla', 'slsqp', 'trust-constr', - 'dogleg', 'trust-ncg', 'trust-exact', 'trust-krylov'] + 'l-bfgs-b', 'tnc', 'cobyla', 'cobyqa', 'slsqp', + 'trust-constr', 'dogleg', 'trust-ncg', 'trust-exact', + 'trust-krylov'] # These methods support the new callback interface (passed an OptimizeResult) MINIMIZE_METHODS_NEW_CB = ['nelder-mead', 'powell', 'cg', 'bfgs', 'newton-cg', 'l-bfgs-b', 'trust-constr', 'dogleg', 'trust-ncg', - 'trust-exact', 'trust-krylov'] + 'trust-exact', 'trust-krylov', 'cobyqa'] MINIMIZE_SCALAR_METHODS = ['brent', 'bounded', 'golden'] @@ -80,6 +82,7 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, - 'L-BFGS-B' :ref:`(see here) ` - 'TNC' :ref:`(see here) ` - 'COBYLA' :ref:`(see here) ` + - 'COBYQA' :ref:`(see here) ` - 'SLSQP' :ref:`(see here) ` - 'trust-constr':ref:`(see here) ` - 'dogleg' :ref:`(see here) ` @@ -146,15 +149,15 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, parameters. bounds : sequence or `Bounds`, optional Bounds on variables for Nelder-Mead, L-BFGS-B, TNC, SLSQP, Powell, - trust-constr, and COBYLA methods. There are two ways to specify the - bounds: + trust-constr, COBYLA, and COBYQA methods. There are two ways to specify + the bounds: 1. Instance of `Bounds` class. 2. Sequence of ``(min, max)`` pairs for each element in `x`. None is used to specify no bound. constraints : {Constraint, dict} or List of {Constraint, dict}, optional - Constraints definition. Only for COBYLA, SLSQP and trust-constr. + Constraints definition. Only for COBYLA, COBYQA, SLSQP and trust-constr. Constraints for 'trust-constr' are defined as a single object or a list of objects specifying constraints to the optimization problem. @@ -178,6 +181,8 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, Equality constraint means that the constraint function result is to be zero whereas inequality means that it is to be non-negative. Note that COBYLA only supports inequality constraints. + + Constraints for COBYQA are defined as any of the above. tol : float, optional Tolerance for termination. When `tol` is specified, the selected minimization algorithm sets some relevant solver-specific tolerance(s) @@ -335,6 +340,13 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, constraints functions 'fun' may return either a single number or an array or list of numbers. + Method :ref:`COBYQA ` uses the Constrained + Optimization BY Quadratic Approximations (COBYQA) method [18]_. The + algorithm is a derivative-free trust-region SQP method based on quadratic + approximations to the objective function and each nonlinear constraint. The + bounds are treated as unrelaxable constraints, in the sense that the + algorithm always respects them throughout the optimization process. + Method :ref:`SLSQP ` uses Sequential Least SQuares Programming to minimize a function of several variables with any combination of bounds, equality and inequality @@ -466,6 +478,10 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, .. [17] Lalee, Marucha, Jorge Nocedal, and Todd Plantega. 1998. On the implementation of an algorithm for large-scale equality constrained optimization. SIAM Journal on Optimization 8.3: 682-706. + .. [18] Ragonneau, T. M. *Model-Based Derivative-Free Optimization Methods + and Software*. PhD thesis, Department of Applied Mathematics, The Hong + Kong Polytechnic University, Hong Kong, China, 2022. URL: + https://theses.lib.polyu.edu.hk/handle/200/12294. Examples -------- @@ -558,7 +574,7 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, options = {} # check if optional parameters are supported by the selected method # - jac - if meth in ('nelder-mead', 'powell', 'cobyla') and bool(jac): + if meth in ('nelder-mead', 'powell', 'cobyla', 'cobyqa') and bool(jac): warn('Method %s does not use gradient information (jac).' % method, RuntimeWarning, stacklevel=2) # - hess @@ -574,16 +590,17 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, 'information (hessp).' % method, RuntimeWarning, stacklevel=2) # - constraints or bounds - if (meth not in ('cobyla', 'slsqp', 'trust-constr', '_custom') and + if (meth not in ('cobyla', 'cobyqa', 'slsqp', 'trust-constr', '_custom') and np.any(constraints)): warn('Method %s cannot handle constraints.' % method, RuntimeWarning, stacklevel=2) - if meth not in ('nelder-mead', 'powell', 'l-bfgs-b', 'cobyla', 'slsqp', - 'tnc', 'trust-constr', '_custom') and bounds is not None: + if meth not in ( + 'nelder-mead', 'powell', 'l-bfgs-b', 'cobyla', 'cobyqa', 'slsqp', + 'tnc', 'trust-constr', '_custom') and bounds is not None: warn('Method %s cannot handle bounds.' % method, RuntimeWarning, stacklevel=2) # - return_all - if (meth in ('l-bfgs-b', 'tnc', 'cobyla', 'slsqp') and + if (meth in ('l-bfgs-b', 'tnc', 'cobyla', 'cobyqa', 'slsqp') and options.get('return_all', False)): warn('Method %s does not support the return_all option.' % method, RuntimeWarning, stacklevel=2) @@ -624,6 +641,8 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, options.setdefault('gtol', tol) if meth in ('cobyla', '_custom'): options.setdefault('tol', tol) + if meth == 'cobyqa': + options.setdefault('final_tr_radius', tol) if meth == 'trust-constr': options.setdefault('xtol', tol) options.setdefault('gtol', tol) @@ -718,6 +737,9 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, elif meth == 'cobyla': res = _minimize_cobyla(fun, x0, args, constraints, callback=callback, bounds=bounds, **options) + elif meth == 'cobyqa': + res = _minimize_cobyqa(fun, x0, args, bounds, constraints, callback, + **options) elif meth == 'slsqp': res = _minimize_slsqp(fun, x0, args, jac, bounds, constraints, callback=callback, **options) @@ -1016,7 +1038,8 @@ def _validate_bounds(bounds, x0, meth): def standardize_bounds(bounds, x0, meth): """Converts bounds to the form required by the solver.""" - if meth in {'trust-constr', 'powell', 'nelder-mead', 'cobyla', 'new'}: + if meth in {'trust-constr', 'powell', 'nelder-mead', 'cobyla', 'cobyqa', + 'new'}: if not isinstance(bounds, Bounds): lb, ub = old_bound_to_new(bounds) bounds = Bounds(lb, ub) diff --git a/scipy/optimize/_optimize.py b/scipy/optimize/_optimize.py index cd189e16a109..37472486fd53 100644 --- a/scipy/optimize/_optimize.py +++ b/scipy/optimize/_optimize.py @@ -86,7 +86,7 @@ def derivative(self, x, *args): def _wrap_callback(callback, method=None): """Wrap a user-provided callback so that attributes can be attached.""" - if callback is None or method in {'tnc', 'slsqp', 'cobyla'}: + if callback is None or method in {'tnc', 'slsqp', 'cobyla', 'cobyqa'}: return callback # don't wrap sig = inspect.signature(callback) @@ -3908,6 +3908,7 @@ def show_options(solver=None, method=None, disp=True): - :ref:`L-BFGS-B ` - :ref:`TNC ` - :ref:`COBYLA ` + - :ref:`COBYQA ` - :ref:`SLSQP ` - :ref:`dogleg ` - :ref:`trust-ncg ` @@ -3982,6 +3983,7 @@ def show_options(solver=None, method=None, disp=True): ('bfgs', 'scipy.optimize._optimize._minimize_bfgs'), ('cg', 'scipy.optimize._optimize._minimize_cg'), ('cobyla', 'scipy.optimize._cobyla_py._minimize_cobyla'), + ('cobyqa', 'scipy.optimize._cobyqa_py._minimize_cobyqa'), ('dogleg', 'scipy.optimize._trustregion_dogleg._minimize_dogleg'), ('l-bfgs-b', 'scipy.optimize._lbfgsb_py._minimize_lbfgsb'), ('nelder-mead', 'scipy.optimize._optimize._minimize_neldermead'), diff --git a/scipy/optimize/_shgo.py b/scipy/optimize/_shgo.py index 61d686d02afd..4dce006dcbca 100644 --- a/scipy/optimize/_shgo.py +++ b/scipy/optimize/_shgo.py @@ -46,12 +46,12 @@ def shgo( Any additional fixed parameters needed to completely specify the objective function. constraints : {Constraint, dict} or List of {Constraint, dict}, optional - Constraints definition. Only for COBYLA, SLSQP and trust-constr. + Constraints definition. Only for COBYLA, COBYQA, SLSQP and trust-constr. See the tutorial [5]_ for further details on specifying constraints. .. note:: - Only COBYLA, SLSQP, and trust-constr local minimize methods + Only COBYLA, COBYQA, SLSQP, and trust-constr local minimize methods currently support constraint arguments. If the ``constraints`` sequence used in the local optimization problem is not defined in ``minimizer_kwargs`` and a constrained method is used then the @@ -273,8 +273,9 @@ def shgo( The local search method may be specified using the ``minimizer_kwargs`` parameter which is passed on to ``scipy.optimize.minimize``. By default, the ``SLSQP`` method is used. In general, it is recommended to use the - ``SLSQP`` or ``COBYLA`` local minimization if inequality constraints - are defined for the problem since the other methods do not use constraints. + ``SLSQP``, ``COBYLA``, or ``COBYQA`` local minimization if inequality + constraints are defined for the problem since the other methods do not use + constraints. The ``halton`` and ``sobol`` method points are generated using `scipy.stats.qmc`. Any other QMC method could be used. @@ -541,7 +542,7 @@ def __init__(self, func, bounds, args=(), constraints=None, n=None, # self.constraints is used to create Complex, so need # to be stored internally in old-style. # `minimize` takes care of normalising these constraints - # for slsqp/cobyla/trust-constr. + # for slsqp/cobyla/cobyqa/trust-constr. self.constraints = standardize_constraints( constraints, np.empty(self.dim, float), @@ -576,6 +577,7 @@ def __init__(self, func, bounds, args=(), constraints=None, n=None, if ( self.minimizer_kwargs['method'].lower() in ('slsqp', 'cobyla', + 'cobyqa', 'trust-constr') and ( minimizer_kwargs is not None and @@ -626,6 +628,7 @@ def __init__(self, func, bounds, args=(), constraints=None, n=None, 'l-bfgs-b': ['jac', 'bounds'], 'tnc': ['jac', 'bounds'], 'cobyla': ['constraints', 'catol'], + 'cobyqa': ['bounds', 'constraints', 'feasibility_tol'], 'slsqp': ['jac', 'bounds', 'constraints'], 'dogleg': ['jac', 'hess'], 'trust-ncg': ['jac', 'hess', 'hessp'], diff --git a/scipy/optimize/meson.build b/scipy/optimize/meson.build index e608325741df..8e84d6921926 100644 --- a/scipy/optimize/meson.build +++ b/scipy/optimize/meson.build @@ -246,6 +246,7 @@ py3.install_sources([ '_bracket.py', '_chandrupatla.py', '_cobyla_py.py', + '_cobyqa_py.py', '_constraints.py', '_differentiable_functions.py', '_differentialevolution.py', diff --git a/scipy/optimize/tests/meson.build b/scipy/optimize/tests/meson.build index 719e92c0d3bb..9574f577a645 100644 --- a/scipy/optimize/tests/meson.build +++ b/scipy/optimize/tests/meson.build @@ -12,6 +12,7 @@ py3.install_sources([ 'test_bracket.py', 'test_chandrupatla.py', 'test_cobyla.py', + 'test_cobyqa.py', 'test_constraint_conversion.py', 'test_constraints.py', 'test_cython_optimize.py', diff --git a/scipy/optimize/tests/test__basinhopping.py b/scipy/optimize/tests/test__basinhopping.py index d4230dc5ab49..88dbda3dc1d6 100644 --- a/scipy/optimize/tests/test__basinhopping.py +++ b/scipy/optimize/tests/test__basinhopping.py @@ -200,8 +200,8 @@ def test_2d_nograd(self): assert_almost_equal(res.x, self.sol[i], self.tol) def test_all_minimizers(self): - # Test 2-D minimizations with gradient. Nelder-Mead, Powell, and COBYLA - # don't accept jac=True, so aren't included here. + # Test 2-D minimizations with gradient. Nelder-Mead, Powell, COBYLA, and + # COBYQA don't accept jac=True, so aren't included here. i = 1 methods = ['CG', 'BFGS', 'Newton-CG', 'L-BFGS-B', 'TNC', 'SLSQP'] minimizer_kwargs = copy.copy(self.kwargs) @@ -217,7 +217,7 @@ def test_all_nograd_minimizers(self): # so not included here. i = 1 methods = ['CG', 'BFGS', 'L-BFGS-B', 'TNC', 'SLSQP', - 'Nelder-Mead', 'Powell', 'COBYLA'] + 'Nelder-Mead', 'Powell', 'COBYLA', 'COBYQA'] minimizer_kwargs = copy.copy(self.kwargs_nograd) for method in methods: minimizer_kwargs["method"] = method diff --git a/scipy/optimize/tests/test__dual_annealing.py b/scipy/optimize/tests/test__dual_annealing.py index 7caa88bb0b63..51b2dd4066cd 100644 --- a/scipy/optimize/tests/test__dual_annealing.py +++ b/scipy/optimize/tests/test__dual_annealing.py @@ -251,6 +251,7 @@ def test_callback_stop(self): @pytest.mark.parametrize('method, atol', [ ('Nelder-Mead', 2e-5), ('COBYLA', 1e-5), + ('COBYQA', 1e-8), ('Powell', 1e-8), ('CG', 1e-8), ('BFGS', 1e-8), diff --git a/scipy/optimize/tests/test__shgo.py b/scipy/optimize/tests/test__shgo.py index 3bd7fe9de4aa..a83b8510de78 100644 --- a/scipy/optimize/tests/test__shgo.py +++ b/scipy/optimize/tests/test__shgo.py @@ -637,7 +637,7 @@ def test_6_2_simplicial_min_iter(self): def test_7_1_minkwargs(self): """Test the minimizer_kwargs arguments for solvers with constraints""" # Test solvers - for solver in ['COBYLA', 'SLSQP']: + for solver in ['COBYLA', 'COBYQA', 'SLSQP']: # Note that passing global constraints to SLSQP is tested in other # unittests which run test4_1 normally minimizer_kwargs = {'method': solver, diff --git a/scipy/optimize/tests/test_cobyqa.py b/scipy/optimize/tests/test_cobyqa.py new file mode 100644 index 000000000000..a77cca135849 --- /dev/null +++ b/scipy/optimize/tests/test_cobyqa.py @@ -0,0 +1,246 @@ +import numpy as np +import pytest +from numpy.testing import assert_allclose, assert_equal + +from scipy.optimize import ( + Bounds, + LinearConstraint, + NonlinearConstraint, + OptimizeResult, + minimize, +) + + +class TestCOBYQA: + + def setup_method(self): + self.x0 = [4.95, 0.66] + self.options = {'maxfev': 100} + + @staticmethod + def fun(x, c=1.0): + return x[0]**2 + c * abs(x[1])**3 + + @staticmethod + def con(x): + return x[0]**2 + x[1]**2 - 25.0 + + def test_minimize_simple(self): + class Callback: + def __init__(self): + self.n_calls = 0 + + def __call__(self, x): + assert isinstance(x, np.ndarray) + self.n_calls += 1 + + class CallbackNewSyntax: + def __init__(self): + self.n_calls = 0 + + def __call__(self, intermediate_result): + assert isinstance(intermediate_result, OptimizeResult) + self.n_calls += 1 + + callback = Callback() + callback_new_syntax = CallbackNewSyntax() + + # Minimize with method='cobyqa'. + constraints = NonlinearConstraint(self.con, 0.0, 0.0) + sol = minimize( + self.fun, + self.x0, + method='cobyqa', + constraints=constraints, + callback=callback, + options=self.options, + ) + sol_new = minimize( + self.fun, + self.x0, + method='cobyqa', + constraints=constraints, + callback=callback_new_syntax, + options=self.options, + ) + solution = [np.sqrt(25.0 - 4.0 / 9.0), 2.0 / 3.0] + assert_allclose(sol.x, solution, atol=1e-4) + assert sol.success, sol.message + assert sol.maxcv < 1e-8, sol + assert sol.nfev <= 100, sol + assert sol.fun < self.fun(solution) + 1e-3, sol + assert sol.nfev == callback.n_calls, \ + "Callback is not called exactly once for every function eval." + assert_equal(sol.x, sol_new.x) + assert sol_new.success, sol_new.message + assert sol.fun == sol_new.fun + assert sol.maxcv == sol_new.maxcv + assert sol.nfev == sol_new.nfev + assert sol.nit == sol_new.nit + assert sol_new.nfev == callback_new_syntax.n_calls, \ + "Callback is not called exactly once for every function eval." + + def test_minimize_bounds(self): + def fun_check_bounds(x): + assert np.all(bounds.lb <= x) and np.all(x <= bounds.ub) + return self.fun(x) + + # Case where the bounds are not active at the solution. + bounds = Bounds([4.5, 0.6], [5.0, 0.7]) + constraints = NonlinearConstraint(self.con, 0.0, 0.0) + sol = minimize( + fun_check_bounds, + self.x0, + method='cobyqa', + bounds=bounds, + constraints=constraints, + options=self.options, + ) + solution = [np.sqrt(25.0 - 4.0 / 9.0), 2.0 / 3.0] + assert_allclose(sol.x, solution, atol=1e-4) + assert sol.success, sol.message + assert sol.maxcv < 1e-8, sol + assert np.all(bounds.lb <= sol.x) and np.all(sol.x <= bounds.ub), sol + assert sol.nfev <= 100, sol + assert sol.fun < self.fun(solution) + 1e-3, sol + + # Case where the bounds are active at the solution. + bounds = Bounds([5.0, 0.6], [5.5, 0.65]) + sol = minimize( + fun_check_bounds, + self.x0, + method='cobyqa', + bounds=bounds, + constraints=constraints, + options=self.options, + ) + assert not sol.success, sol.message + assert sol.maxcv > 0.35, sol + assert np.all(bounds.lb <= sol.x) and np.all(sol.x <= bounds.ub), sol + assert sol.nfev <= 100, sol + + def test_minimize_linear_constraints(self): + constraints = LinearConstraint([1.0, 1.0], 1.0, 1.0) + sol = minimize( + self.fun, + self.x0, + method='cobyqa', + constraints=constraints, + options=self.options, + ) + solution = [(4 - np.sqrt(7)) / 3, (np.sqrt(7) - 1) / 3] + assert_allclose(sol.x, solution, atol=1e-4) + assert sol.success, sol.message + assert sol.maxcv < 1e-8, sol + assert sol.nfev <= 100, sol + assert sol.fun < self.fun(solution) + 1e-3, sol + + def test_minimize_args(self): + constraints = NonlinearConstraint(self.con, 0.0, 0.0) + sol = minimize( + self.fun, + self.x0, + args=(2.0,), + method='cobyqa', + constraints=constraints, + options=self.options, + ) + solution = [np.sqrt(25.0 - 4.0 / 36.0), 2.0 / 6.0] + assert_allclose(sol.x, solution, atol=1e-4) + assert sol.success, sol.message + assert sol.maxcv < 1e-8, sol + assert sol.nfev <= 100, sol + assert sol.fun < self.fun(solution, 2.0) + 1e-3, sol + + def test_minimize_array(self): + def fun_array(x, dim): + f = np.array(self.fun(x)) + return np.reshape(f, (1,) * dim) + + # The argument fun can return an array with a single element. + bounds = Bounds([4.5, 0.6], [5.0, 0.7]) + constraints = NonlinearConstraint(self.con, 0.0, 0.0) + sol = minimize( + self.fun, + self.x0, + method='cobyqa', + bounds=bounds, + constraints=constraints, + options=self.options, + ) + for dim in [0, 1, 2]: + sol_array = minimize( + fun_array, + self.x0, + args=(dim,), + method='cobyqa', + bounds=bounds, + constraints=constraints, + options=self.options, + ) + assert_equal(sol.x, sol_array.x) + assert sol_array.success, sol_array.message + assert sol.fun == sol_array.fun + assert sol.maxcv == sol_array.maxcv + assert sol.nfev == sol_array.nfev + assert sol.nit == sol_array.nit + + # The argument fun cannot return an array with more than one element. + with pytest.raises(TypeError): + minimize( + lambda x: np.array([self.fun(x), self.fun(x)]), + self.x0, + method='cobyqa', + bounds=bounds, + constraints=constraints, + options=self.options, + ) + + def test_minimize_maxfev(self): + constraints = NonlinearConstraint(self.con, 0.0, 0.0) + options = {'maxfev': 2} + sol = minimize( + self.fun, + self.x0, + method='cobyqa', + constraints=constraints, + options=options, + ) + assert not sol.success, sol.message + assert sol.nfev <= 2, sol + + def test_minimize_maxiter(self): + constraints = NonlinearConstraint(self.con, 0.0, 0.0) + options = {'maxiter': 2} + sol = minimize( + self.fun, + self.x0, + method='cobyqa', + constraints=constraints, + options=options, + ) + assert not sol.success, sol.message + assert sol.nit <= 2, sol + + def test_minimize_f_target(self): + constraints = NonlinearConstraint(self.con, 0.0, 0.0) + sol_ref = minimize( + self.fun, + self.x0, + method='cobyqa', + constraints=constraints, + options=self.options, + ) + options = dict(self.options) + options['f_target'] = sol_ref.fun + sol = minimize( + self.fun, + self.x0, + method='cobyqa', + constraints=constraints, + options=options, + ) + assert sol.success, sol.message + assert sol.maxcv < 1e-8, sol + assert sol.nfev <= sol_ref.nfev, sol + assert sol.fun <= sol_ref.fun, sol diff --git a/scipy/optimize/tests/test_constraint_conversion.py b/scipy/optimize/tests/test_constraint_conversion.py index f52c65cdd1d5..d3ae20f23342 100644 --- a/scipy/optimize/tests/test_constraint_conversion.py +++ b/scipy/optimize/tests/test_constraint_conversion.py @@ -67,7 +67,7 @@ def fun(x): return (x[0] - 1) ** 2 + (x[1] - 2.5) ** 2 + (x[2] - 0.75) ** 2 x0 = [2, 0, 1] coni = [] # only inequality constraints (can use cobyla) - methods = ["slsqp", "cobyla", "trust-constr"] + methods = ["slsqp", "cobyla", "cobyqa", "trust-constr"] # mixed old and new coni.append([{'type': 'ineq', 'fun': lambda x: x[0] - 2 * x[1] + 2}, @@ -88,6 +88,7 @@ def fun(x): funs[method] = result.fun assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-4) assert_allclose(funs['cobyla'], funs['trust-constr'], rtol=1e-4) + assert_allclose(funs['cobyqa'], funs['trust-constr'], rtol=1e-4) def test_individual_constraint_objects(self): def fun(x): @@ -96,7 +97,7 @@ def fun(x): cone = [] # with equality constraints (can't use cobyla) coni = [] # only inequality constraints (can use cobyla) - methods = ["slsqp", "cobyla", "trust-constr"] + methods = ["slsqp", "cobyla", "cobyqa", "trust-constr"] # nonstandard data types for constraint equality bounds cone.append(NonlinearConstraint(lambda x: x[0] - x[1], 1, 1)) @@ -156,15 +157,17 @@ def fun(x): funs[method] = result.fun assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-3) assert_allclose(funs['cobyla'], funs['trust-constr'], rtol=1e-3) + assert_allclose(funs['cobyqa'], funs['trust-constr'], rtol=1e-3) for con in cone: funs = {} - for method in methods[::2]: # skip cobyla + for method in [method for method in methods if method != 'cobyla']: with suppress_warnings() as sup: sup.filter(UserWarning) result = minimize(fun, x0, method=method, constraints=con) funs[method] = result.fun assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-3) + assert_allclose(funs['cobyqa'], funs['trust-constr'], rtol=1e-3) class TestNewToOldSLSQP: diff --git a/scipy/optimize/tests/test_optimize.py b/scipy/optimize/tests/test_optimize.py index 84a3d8115aa0..ce1983000eec 100644 --- a/scipy/optimize/tests/test_optimize.py +++ b/scipy/optimize/tests/test_optimize.py @@ -649,6 +649,25 @@ def test_ncg_hessp(self): [-4.35700753e-07, -5.24869401e-01, 4.87527774e-01]], atol=1e-6, rtol=1e-7) + def test_cobyqa(self): + # COBYQA method. + if self.use_wrapper: + res = optimize.minimize( + self.func, + self.startparams, + method='cobyqa', + options={'maxiter': self.maxiter, 'disp': self.disp}, + ) + assert_allclose(res.fun, self.func(self.solution), atol=1e-6) + + # Ensure that function call counts are 'known good'; these are from + # SciPy 1.14.0. Don't allow them to increase. The exact evaluation + # count is sensitive to numerical error and floating-point + # computations are not bit-for-bit reproducible across machines. It + # takes 45 calls on my machine, but we can add the same +20 margin + # as is used in `test_powell` + assert self.funccalls <= 45 + 20, self.funccalls + def test_maxfev_test(): rng = np.random.default_rng(271707100830272976862395227613146332411) @@ -1229,15 +1248,15 @@ def dfunc(z): for method in ['nelder-mead', 'powell', 'cg', 'bfgs', 'newton-cg', 'l-bfgs-b', 'tnc', - 'cobyla', 'slsqp']: - if method in ('nelder-mead', 'powell', 'cobyla'): + 'cobyla', 'cobyqa', 'slsqp']: + if method in ('nelder-mead', 'powell', 'cobyla', 'cobyqa'): jac = None else: jac = dfunc - sol1 = optimize.minimize(func, [1, 1], jac=jac, tol=1e-10, + sol1 = optimize.minimize(func, [2, 2], jac=jac, tol=1e-10, method=method) - sol2 = optimize.minimize(func, [1, 1], jac=jac, tol=1.0, + sol2 = optimize.minimize(func, [2, 2], jac=jac, tol=1.0, method=method) assert func(sol1.x) < func(sol2.x), \ f"{method}: {func(sol1.x)} vs. {func(sol2.x)}" @@ -1311,7 +1330,7 @@ def callback(x, *args, **kwargs): @pytest.mark.parametrize('method', ['nelder-mead', 'powell', 'cg', 'bfgs', 'newton-cg', 'l-bfgs-b', - 'tnc', 'cobyla', 'slsqp']) + 'tnc', 'cobyla', 'cobyqa', 'slsqp']) def test_no_increase(self, method): # Check that the solver doesn't return a value worse than the # initial point. @@ -1328,7 +1347,7 @@ def bad_grad(x): f0 = func(x0) jac = bad_grad options = dict(maxfun=20) if method == 'tnc' else dict(maxiter=20) - if method in ['nelder-mead', 'powell', 'cobyla']: + if method in ['nelder-mead', 'powell', 'cobyla', 'cobyqa']: jac = None sol = optimize.minimize(func, x0, jac=jac, method=method, options=options) @@ -1355,7 +1374,8 @@ def cons(x): @pytest.mark.parametrize('method', ['Nelder-Mead', 'Powell', 'CG', 'BFGS', 'Newton-CG', 'L-BFGS-B', 'SLSQP', 'trust-constr', 'dogleg', 'trust-ncg', - 'trust-exact', 'trust-krylov']) + 'trust-exact', 'trust-krylov', + 'cobyqa']) def test_respect_maxiter(self, method): # Check that the number of iterations equals max_iter, assuming # convergence doesn't establish before @@ -1385,6 +1405,8 @@ def test_respect_maxiter(self, method): # method specific tests if method == 'SLSQP': assert sol.status == 9 # Iteration limit reached + elif method == 'cobyqa': + assert sol.status == 6 # Iteration limit reached @pytest.mark.parametrize('method', ['Nelder-Mead', 'Powell', 'fmin', 'fmin_powell']) @@ -1511,9 +1533,9 @@ def g(x): @pytest.mark.parametrize('method', ['nelder-mead', 'powell', 'cg', 'bfgs', 'newton-cg', 'l-bfgs-b', 'tnc', - 'cobyla', 'slsqp', 'trust-constr', - 'dogleg', 'trust-ncg', 'trust-exact', - 'trust-krylov']) + 'cobyla', 'cobyqa', 'slsqp', + 'trust-constr', 'dogleg', 'trust-ncg', + 'trust-exact', 'trust-krylov']) def test_nan_values(self, method): # Check nan values result to failed exit status np.random.seed(1234) @@ -1561,9 +1583,9 @@ def hess(x): @pytest.mark.parametrize('method', ['nelder-mead', 'cg', 'bfgs', 'l-bfgs-b', 'tnc', - 'cobyla', 'slsqp', 'trust-constr', - 'dogleg', 'trust-ncg', 'trust-exact', - 'trust-krylov']) + 'cobyla', 'cobyqa', 'slsqp', + 'trust-constr', 'dogleg', 'trust-ncg', + 'trust-exact', 'trust-krylov']) def test_duplicate_evaluations(self, method): # check that there are no duplicate evaluations for any methods jac = hess = None @@ -1636,11 +1658,18 @@ def callback(): res = optimize.minimize(**kwargs, callback=callback_interface) if method == 'nelder-mead': maxiter = maxiter + 1 # nelder-mead counts differently - ref = optimize.minimize(**kwargs, options={'maxiter': maxiter}) + if method == 'cobyqa': + ref = optimize.minimize(**kwargs, options={'maxfev': maxiter}) + assert res.nfev == ref.nfev == maxiter + else: + ref = optimize.minimize(**kwargs, options={'maxiter': maxiter}) + assert res.nit == ref.nit == maxiter assert res.fun == ref.fun assert_equal(res.x, ref.x) - assert res.nit == ref.nit == maxiter - assert res.status == (3 if method == 'trust-constr' else 99) + assert res.status == (3 if method in [ + 'trust-constr', + 'cobyqa', + ] else 99) def test_ndim_error(self): msg = "'x0' must only have one dimension." @@ -1648,7 +1677,8 @@ def test_ndim_error(self): optimize.minimize(lambda x: x, np.ones((2, 1))) @pytest.mark.parametrize('method', ('nelder-mead', 'l-bfgs-b', 'tnc', - 'powell', 'cobyla', 'trust-constr')) + 'powell', 'cobyla', 'cobyqa', + 'trust-constr')) def test_minimize_invalid_bounds(self, method): def f(x): return np.sum(x**2) @@ -1683,7 +1713,7 @@ def test_minimize_warnings_gh1953(self, method): @pytest.mark.parametrize( 'method', - ['l-bfgs-b', 'tnc', 'Powell', 'Nelder-Mead'] + ['l-bfgs-b', 'tnc', 'Powell', 'Nelder-Mead', 'cobyqa'] ) def test_minimize_with_scalar(method): # checks that minimize works with a scalar being provided to it. @@ -2695,7 +2725,7 @@ def hess(x): return np.array([[2.]]) methods = ['Nelder-Mead', 'Powell', 'CG', 'BFGS', 'L-BFGS-B', 'TNC', - 'COBYLA', 'SLSQP'] + 'COBYLA', 'COBYQA', 'SLSQP'] for method in methods: res = optimize.minimize(fun, np.array([0.1]), method=method) assert res.x.shape == (1,) diff --git a/tools/check_installation.py b/tools/check_installation.py index 10783ab9d446..eb589e1e7d7b 100644 --- a/tools/check_installation.py +++ b/tools/check_installation.py @@ -6,8 +6,8 @@ $ python check_installation.py install_directory_name install_directory_name: - the relative path to the directory where SciPy is installed after - building and running `meson install`. + the relative path from the root of the repo to the directory where + SciPy is installed (for dev.py usually "build-install") Notes ===== @@ -34,13 +34,23 @@ 'scipy/_lib/tests/test_scipy_version.py' } -# We do not want the following tests to be checked +# We do not want the following tests to be checked. +# Note: only 3 subdirs are listed here, due to how `get_test_files` is +# implemented. +# If this list gets too annoying, we should implement excluding directories +# rather than (or in addition to) files. exception_list_test_files = [ "_lib/array_api_compat/tests/test_all.py", "_lib/array_api_compat/tests/test_array_namespace.py", "_lib/array_api_compat/tests/test_common.py", "_lib/array_api_compat/tests/test_isdtype.py", "_lib/array_api_compat/tests/test_vendoring.py", + "_lib/array_api_compat/tests/test_array_namespace.py", + "cobyqa/cobyqa/tests/test_main.py", + "cobyqa/cobyqa/tests/test_models.py", + "cobyqa/cobyqa/tests/test_problem.py", + "cobyqa/utils/tests/test_exceptions.py", + "cobyqa/utils/tests/test_math.py", ] @@ -58,7 +68,9 @@ def main(install_dir): continue if test_file not in installed_test_files.keys(): - raise Exception("%s is not installed" % scipy_test_files[test_file]) + raise Exception(f"{scipy_test_files[test_file]} is not installed; " + f"either install it or add `{test_file}` to the " + "exception list in `tools/check_installation.py`") print("----------- All the test files were installed --------------") From 58d63eec5869241195f6603c6028db4c6d3f7c09 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Fri, 10 May 2024 15:49:18 +0200 Subject: [PATCH 121/500] MAINT: improve comment related to when `f2py_gen` cannot be used Addresses review comments on PR 20612. [skip ci] --- scipy/meson.build | 6 ++++-- scipy/stats/meson.build | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scipy/meson.build b/scipy/meson.build index bbad81ae3df0..0c4c95f220e9 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -158,8 +158,10 @@ if f2py_version.version_compare('<'+min_numpy_version) error(f'Found f2py executable is too old: @f2py_version@') endif -# Note: cannot handle .pyf.src or targets with #include's (due to no -# `depend_files` - see feature request at meson#8295) +# Note: this generato cannot handle: +# 1. `.pyf.src` files, because `@BASENAME@` will still include .pyf +# 2. targets with #include's (due to no `depend_files` - see feature request +# at meson#8295) f2py_gen = generator(generate_f2pymod, arguments : ['@INPUT@', '-o', '@BUILD_DIR@'], output : ['_@BASENAME@module.c', '_@BASENAME@-f2pywrappers.f'], diff --git a/scipy/stats/meson.build b/scipy/stats/meson.build index 51fd8c10255b..b1eb66c9e5a0 100644 --- a/scipy/stats/meson.build +++ b/scipy/stats/meson.build @@ -32,7 +32,6 @@ py3.extension_module('_ansari_swilk_statistics', ) py3.extension_module('_mvn', - #[mvn_module, 'mvndst.f'], [f2py_gen.process('mvn.pyf'), 'mvndst.f'], # Wno-surprising is to suppress a pointless warning with GCC 10-12 # (see GCC bug 98411: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98411) From 79e4194a5ff2d5b57752da01d7bbc623b11e82d9 Mon Sep 17 00:00:00 2001 From: Christian Lorentzen Date: Fri, 10 May 2024 16:08:20 +0200 Subject: [PATCH 122/500] MAINT add comment in wright_bessel (#20679) --- scipy/special/special/wright_bessel.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scipy/special/special/wright_bessel.h b/scipy/special/special/wright_bessel.h index 2d21771dde87..cc0fffdb9e67 100644 --- a/scipy/special/special/wright_bessel.h +++ b/scipy/special/special/wright_bessel.h @@ -564,7 +564,8 @@ namespace detail { * for any eps > 0. * * Note that P has a misprint in Luchko (2008) Eq. 9, the cos(phi(beta-1)) at - * the end of the first line should be removed. + * the end of the first line should be removed and the −sin(phi(beta−1)) at + * the end of the second line should read +(1-b)*phi. * This integral representation introduced the free parameter eps (from the * radius of complex contour integration). We try to choose eps such that * the integrand behaves smoothly. Note that this is quite diffrent from how From 3559245f5438d81aad7dcb1ccfcca84ac3dc2b0f Mon Sep 17 00:00:00 2001 From: Budjen Jovan <92484860+budjen-jovan@users.noreply.github.com> Date: Fri, 10 May 2024 20:21:44 +0200 Subject: [PATCH 123/500] Fix missing whitespace in signal.iirdesign, spacing consistency fix (#20684) As per #20683, added the missing whitespace, and moved existing whitespace separators to the end of first strings for consistency --- scipy/signal/_filter_design.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index d93f810c7f43..cc6d8a5548bc 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -2392,7 +2392,7 @@ def iirdesign(wp, ws, gpass, gstop, analog=False, ftype='ellip', output='ba', 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" + raise ValueError("wp and ws must have one or two elements each, and " f"the same shape, got {wp.shape} and {ws.shape}") if any(wp <= 0) or any(ws <= 0): @@ -2403,14 +2403,14 @@ def iirdesign(wp, ws, gpass, gstop, analog=False, ftype='ellip', output='ba', if any(wp >= 1) or any(ws >= 1): raise ValueError("Values for wp, ws must be less than 1") elif any(wp >= fs/2) or any(ws >= fs/2): - raise ValueError("Values for wp, ws must be less than fs/2" - f" (fs={fs} -> fs/2={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") + raise ValueError("Passband must lie strictly inside stopband " + "or vice versa") band_type = 2 * (len(wp) - 1) band_type += 1 From 46408a0ce8cd3467520b3671227485bd2b9376bf Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 10 May 2024 18:56:58 -0700 Subject: [PATCH 124/500] MAINT: stats.gstd: return result rather than raising --- scipy/stats/_stats_py.py | 114 +++++++++++--------------------- scipy/stats/tests/test_stats.py | 28 ++++---- 2 files changed, 52 insertions(+), 90 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index d2072e6b03e4..76291d5139c7 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -3147,25 +3147,25 @@ def zmap(scores, compare, axis=0, ddof=0, nan_policy='propagate'): def gstd(a, axis=0, ddof=1): - """ + 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 exponent of the standard deviation of ``log(a)``. - Mathematically the population geometric standard deviation can be - evaluated as:: - - gstd = exp(std(log(a))) - - .. versionadded:: 1.3.0 + It is defined as the exponential of the standard deviation of the + natural logarithms of the observations. Parameters ---------- a : array_like - An array like object containing the sample data. + An array containing finite, strictly positive, real numbers. + + .. deprecated:: 1.14.0 + Support for masked array input was deprecated in + SciPy 1.14.0 and will be removed in version 1.16.0. + axis : int, tuple or None, optional Axis along which to operate. Default is 0. If None, compute over the whole array `a`. @@ -3187,14 +3187,28 @@ def gstd(a, axis=0, ddof=1): Notes ----- - As the calculation requires the use of logarithms the geometric standard - deviation only supports strictly positive values. Any non-positive or - infinite values will raise a `ValueError`. - The geometric standard deviation is sometimes confused with the exponent of - the standard deviation, ``exp(std(a))``. Instead the geometric standard + 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_y), s_y = \sqrt{ \frac{1}{n - 1} \sum_{i=1}^n (y_i - \bar y)^2 } + + where :math:`n` is the number of observations and :math:`\bar y` denotes the + mean of the natural logarithms of the observations. + + Note that the default ``ddof=1``, which corresponds with the :math:`n - 1` term + above, 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 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)))``. - The default value for `ddof` is different to the default value (0) used - by other ddof containing functions, such as ``np.std`` and ``np.nanstd``. References ---------- @@ -3206,7 +3220,7 @@ def gstd(a, axis=0, ddof=1): Examples -------- Find the geometric standard deviation of a log-normally distributed sample. - Note that the standard deviation of the distribution is one, on a + Note that the standard deviation of the distribution is one; on a log scale this evaluates to approximately ``exp(1)``. >>> import numpy as np @@ -3228,66 +3242,18 @@ def gstd(a, axis=0, ddof=1): >>> gstd(a, axis=(1,2)) array([2.12939215, 1.22120169]) - The geometric standard deviation further handles masked arrays. - - >>> a = np.arange(1, 25).reshape(2, 3, 4) - >>> ma = np.ma.masked_where(a > 16, a) - >>> ma - masked_array( - data=[[[1, 2, 3, 4], - [5, 6, 7, 8], - [9, 10, 11, 12]], - [[13, 14, 15, 16], - [--, --, --, --], - [--, --, --, --]]], - mask=[[[False, False, False, False], - [False, False, False, False], - [False, False, False, False]], - [[False, False, False, False], - [ True, True, True, True], - [ True, True, True, True]]], - fill_value=999999) - >>> gstd(ma, axis=2) - masked_array( - data=[[1.8242475707663655, 1.2243686572447428, 1.1318311657788478], - [1.0934830582350938, --, --]], - mask=[[False, False, False], - [False, True, True]], - fill_value=999999) - """ a = np.asanyarray(a) - log = ma.log if isinstance(a, ma.MaskedArray) else np.log + if isinstance(a, ma.MaskedArray): + message = ("`gstd` support for masked array input was deprecated in " + "SciPy 1.14.0 and will be removed in version 1.16.0.") + warnings.warn(message, DeprecationWarning, stacklevel=2) + log = ma.log + else: + log = np.log - try: - with warnings.catch_warnings(): - warnings.simplefilter("error", RuntimeWarning) - return np.exp(np.std(log(a), axis=axis, ddof=ddof)) - except RuntimeWarning as w: - if np.isinf(a).any(): - raise ValueError( - 'Infinite value encountered. The geometric standard deviation ' - 'is defined for strictly positive values only.' - ) from w - a_nan = np.isnan(a) - a_nan_any = a_nan.any() - # exclude NaN's from negativity check, but - # avoid expensive masking for arrays with no NaN - if ((a_nan_any and np.less_equal(np.nanmin(a), 0)) or - (not a_nan_any and np.less_equal(a, 0).any())): - raise ValueError( - 'Non positive value encountered. The geometric standard ' - 'deviation is defined for strictly positive values only.' - ) from w - elif 'Degrees of freedom <= 0 for slice' == str(w): - raise ValueError(w) from w - else: - # Remaining warnings don't need to be exceptions. - return np.exp(np.std(log(a, where=~a_nan), axis=axis, ddof=ddof)) - except TypeError as e: - raise ValueError( - 'Invalid array input. The inputs could not be ' - 'safely coerced to any supported types') from e + with np.errstate(invalid='ignore', divide='ignore'): + return np.exp(np.std(log(a), axis=axis, ddof=ddof)) # Private dictionary initialized only once at module level diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 175ebf97c7de..00289d8e475c 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6828,21 +6828,15 @@ def test_1d_numeric_array_like_input(self): gstd_actual = stats.gstd(tuple(self.array_1d)) assert_allclose(gstd_actual, self.gstd_array_1d) - def test_raises_value_error_non_array_like_input(self): - with pytest.raises(ValueError, match='Invalid array input'): - stats.gstd('This should fail as it can not be cast to an array.') + def test_raises_value_error_non_numeric_input(self): + # this is raised by NumPy, but it's quite interpretable + with pytest.raises(TypeError, match="ufunc 'log' not supported"): + stats.gstd('You cannot take the logarithm of a string.') - def test_raises_value_error_zero_entry(self): - with pytest.raises(ValueError, match='Non positive value'): - stats.gstd(np.append(self.array_1d, [0])) - - def test_raises_value_error_negative_entry(self): - with pytest.raises(ValueError, match='Non positive value'): - stats.gstd(np.append(self.array_1d, [-1])) - - def test_raises_value_error_inf_entry(self): - with pytest.raises(ValueError, match='Infinite value'): - stats.gstd(np.append(self.array_1d, [np.inf])) + @pytest.mark.parametrize('bad_value', (0, -1, np.inf)) + def test_returns_nan_invalid_value(self, bad_value): + x = np.append(self.array_1d, [bad_value]) + assert_equal(stats.gstd(x), np.nan) def test_propagates_nan_values(self): a = array([[1, 1, 1, 16], [np.nan, 1, 2, 3]]) @@ -6850,7 +6844,7 @@ def test_propagates_nan_values(self): assert_allclose(gstd_actual, np.array([4, np.nan])) def test_ddof_equal_to_number_of_observations(self): - with pytest.raises(ValueError, match='Degrees of freedom <= 0'): + with pytest.warns(RuntimeWarning, match='Degrees of freedom <= 0'): stats.gstd(self.array_1d, ddof=self.array_1d.size) def test_3d_array(self): @@ -6888,7 +6882,9 @@ def test_3d_array_axis_2(self): def test_masked_3d_array(self): ma = np.ma.masked_where(self.array_3d > 16, self.array_3d) - gstd_actual = stats.gstd(ma, axis=2) + message = "`gstd` support for masked array input was deprecated in..." + with pytest.warns(DeprecationWarning, match=message): + gstd_actual = stats.gstd(ma, axis=2) gstd_desired = stats.gstd(self.array_3d, axis=2) mask = [[0, 0, 0], [0, 1, 1]] assert_allclose(gstd_actual, gstd_desired) From 2eb78a52b44d8e1f102e9161eeafae1ff000d330 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Sat, 11 May 2024 17:57:22 +1000 Subject: [PATCH 125/500] ENH: add --with-accelerate flag to dev.py --- .github/workflows/macos.yml | 2 +- dev.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index a63e88cfdf66..3007356aef5b 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -220,7 +220,7 @@ jobs: export DYLD_LIBRARY_PATH=$GFORTRAN_LIB pip install click doit pydevtool rich_click meson cython pythran pybind11 ninja numpy - python dev.py build -C-Dblas=accelerate + python dev.py build --with-accelerate pip install pooch pytest hypothesis python dev.py -n test diff --git a/dev.py b/dev.py index 1b11b9f54d13..1809979e48d9 100644 --- a/dev.py +++ b/dev.py @@ -431,6 +431,11 @@ class Build(Task): ['--with-scipy-openblas'], default=False, is_flag=True, help=("If set, use the `scipy-openblas32` wheel installed into the " "current environment as the BLAS/LAPACK to build against.")) + with_accelerate = Option( + ['--with-accelerate'], default=False, is_flag=True, + help=("If set, use `Accelerate` as the BLAS/LAPACK to build against." + " Takes precedence over -with-scipy-openblas (macOS only)") + ) @classmethod def setup_build(cls, dirs, args): @@ -483,7 +488,10 @@ def setup_build(cls, dirs, args): cmd += ['-Db_sanitize=address,undefined'] if args.setup_args: cmd += [str(arg) for arg in args.setup_args] - if args.with_scipy_openblas: + if args.with_accelerate: + # on a mac you probably want to use accelerate over scipy_openblas + cmd += ["-Dblas=accelerate"] + elif args.with_scipy_openblas: cls.configure_scipy_openblas() env['PKG_CONFIG_PATH'] = os.pathsep.join([ os.path.join(os.getcwd(), '.openblas'), From 4bbcfffae725c457fefa54a8ef0e0459479e114d Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 May 2024 11:49:42 -0500 Subject: [PATCH 126/500] STY: optimize._chandrupatla: fix PEP8 --- scipy/_lib/_array_api.py | 8 +++++--- scipy/optimize/_chandrupatla.py | 3 ++- scipy/optimize/tests/test_chandrupatla.py | 12 +++++++----- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index d8ef4fa57466..76955f1e1231 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -393,6 +393,8 @@ def is_complex(x: Array, xp: ModuleType) -> bool: def xp_minimum(x1, x2): # xp won't be passed in because it doesn't need to be passed in to xp.minimum xp = array_namespace(x1, x2) + if hasattr(xp, 'minimum'): + return xp.minimum(x1, x2) x1, x2 = xp.broadcast_arrays(x1, x2) dtype = xp.result_type(x1.dtype, x2.dtype) res = xp.asarray(x1, copy=True, dtype=dtype) @@ -405,6 +407,8 @@ def xp_minimum(x1, x2): # or covered by array_api_compat. def xp_clip(x, a, b, xp=None): xp = array_namespace(a, b) if xp is None else xp + if hasattr(xp, 'clip'): + return xp.clip(x, a, b) x, a, b = xp.broadcast_arrays(x, a, b) y = xp.asarray(x, copy=True) ia = y < a @@ -437,10 +441,8 @@ def xp_copysign(x1, x2, xp=None): # that I need. (https://github.com/data-apis/array-api-compat/issues/136) def xp_sign(x, xp=None): xp = array_namespace(x) if xp is None else xp - - if is_numpy(xp): + if is_numpy(xp): # only NumPy implements the special cases correctly return xp.sign(x) - sign = xp.full_like(x, xp.nan) one = xp.asarray(1, dtype=x.dtype) sign = xp.where(x > 0, one, sign) diff --git a/scipy/optimize/_chandrupatla.py b/scipy/optimize/_chandrupatla.py index 370b55f0da01..7d59a3aaae40 100644 --- a/scipy/optimize/_chandrupatla.py +++ b/scipy/optimize/_chandrupatla.py @@ -161,7 +161,8 @@ def pre_func_eval(work): def post_func_eval(x, f, work): # [1] Figure 1 (first diamond and boxes) # Note: y/n are reversed in figure; compare to BASIC in appendix - work.x3, work.f3 = xp.asarray(work.x2, copy=True), xp.asarray(work.f2, copy=True) + work.x3, work.f3 = (xp.asarray(work.x2, copy=True), + xp.asarray(work.f2, copy=True)) j = xp.sign(f) == xp.sign(work.f1) nj = ~j work.x3[j], work.f3[j] = work.x1[j], work.f1[j] diff --git a/scipy/optimize/tests/test_chandrupatla.py b/scipy/optimize/tests/test_chandrupatla.py index 05bdef74cb37..309e75abf20a 100644 --- a/scipy/optimize/tests/test_chandrupatla.py +++ b/scipy/optimize/tests/test_chandrupatla.py @@ -5,8 +5,9 @@ from scipy import stats, special import scipy._lib._elementwise_iterative_method as eim from scipy.conftest import array_api_compatible -from scipy._lib._array_api import (array_namespace, xp_assert_close, xp_assert_equal, xp_assert_less, - xp_minimum, is_numpy, is_cupy, is_torch) +from scipy._lib._array_api import (array_namespace, xp_assert_close, xp_assert_equal, + xp_assert_less, xp_minimum, is_numpy, is_cupy, + is_torch) from scipy.optimize._chandrupatla import (_chandrupatla_minimize, _chandrupatla as _chandrupatla_root) @@ -500,7 +501,6 @@ def test_vectorization(self, shape, xp): # input shapes. p = np.linspace(-0.05, 1.05, 12).reshape(shape) if shape else 0.6 p_xp = xp.asarray(p) - args = (p,) args_xp = (p_xp,) dtype = p_xp.dtype xp_test = array_namespace(p_xp) # need xp.bool @@ -708,8 +708,10 @@ def callback(res): if key == 'status': xp_assert_equal(res[key], xp.asarray(eim._ECONVERR, dtype=xp.int32)) xp_assert_equal(res2[key], xp.asarray(eim._ECALLBACK, dtype=xp.int32)) - # TODO: debug this. Somehow `callback.res` is getting changed when StopIteration is raised. - # xp_assert_equal(callback.res[key], xp.asarray(eim._EINPROGRESS, dtype=xp.int32)) + # TODO: debug this. Somehow `callback.res` is getting changed when + # StopIteration is raised. + # xp_assert_equal(callback.res[key], + # xp.asarray(eim._EINPROGRESS, dtype=xp.int32)) elif key.startswith('_'): continue else: From 96a270148a3f44ab3ea1a6737053cb66122a0d58 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 May 2024 12:11:39 -0500 Subject: [PATCH 127/500] MAINT: optimize._chandrupatla: fix first torch special case bug --- scipy/optimize/_chandrupatla.py | 5 ++--- scipy/optimize/tests/test_chandrupatla.py | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/scipy/optimize/_chandrupatla.py b/scipy/optimize/_chandrupatla.py index 7d59a3aaae40..be1382c4319b 100644 --- a/scipy/optimize/_chandrupatla.py +++ b/scipy/optimize/_chandrupatla.py @@ -1,6 +1,5 @@ import math import numpy as np -from ._zeros_py import _rtol import scipy._lib._elementwise_iterative_method as eim from scipy._lib._util import _RichResult from scipy._lib._array_api import xp_clip, xp_minimum, xp_sign @@ -12,7 +11,7 @@ # - figure out how to replace the new `try`/`except`s -def _chandrupatla(func, a, b, *, args=(), xatol=None, xrtol=_rtol, +def _chandrupatla(func, a, b, *, args=(), xatol=None, xrtol=None, fatol=None, frtol=0, maxiter=None, callback=None): """Find the root of an elementwise function using Chandrupatla's algorithm. @@ -141,7 +140,7 @@ def _chandrupatla(func, a, b, *, args=(), xatol=None, xrtol=_rtol, nit, nfev = 0, 2 # two function evaluations performed above finfo = xp.finfo(dtype) xatol = 4*finfo.smallest_normal if xatol is None else xatol - xrtol = _rtol if xrtol is None else xrtol + xrtol = 4*finfo.eps if xrtol is None else xrtol fatol = finfo.smallest_normal if fatol is None else fatol frtol = frtol * xp_minimum(xp.abs(f1), xp.abs(f2)) maxiter = (math.log2(finfo.max) - math.log2(finfo.smallest_normal) diff --git a/scipy/optimize/tests/test_chandrupatla.py b/scipy/optimize/tests/test_chandrupatla.py index 309e75abf20a..ed51a48aeb0d 100644 --- a/scipy/optimize/tests/test_chandrupatla.py +++ b/scipy/optimize/tests/test_chandrupatla.py @@ -820,10 +820,8 @@ def f(x): with np.errstate(divide='ignore', invalid='ignore'): res = _chandrupatla_root(f, a, b) - if not is_torch(xp): - # torch fails with float32 but passes with 64? debug - assert xp.all(res.success) - xp_assert_close(res.x[1:], xp.full((3,), res.x[0])) + assert xp.all(res.success) + xp_assert_close(res.x[1:], xp.full((3,), res.x[0])) # Test that integers are not passed to `f` # (otherwise this would overflow) From 277c184e7dd94c1db7015adb513ba16b46e17a20 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 May 2024 12:43:47 -0500 Subject: [PATCH 128/500] MAINT: optimize._chandrupatla: fix torch issue in test_vectorization --- scipy/optimize/tests/test_chandrupatla.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scipy/optimize/tests/test_chandrupatla.py b/scipy/optimize/tests/test_chandrupatla.py index ed51a48aeb0d..1948f597ff65 100644 --- a/scipy/optimize/tests/test_chandrupatla.py +++ b/scipy/optimize/tests/test_chandrupatla.py @@ -499,7 +499,8 @@ def test_basic(self, p, xp): def test_vectorization(self, shape, xp): # Test for correct functionality, output shapes, and dtypes for various # input shapes. - p = np.linspace(-0.05, 1.05, 12).reshape(shape) if shape else 0.6 + p = (np.linspace(-0.05, 1.05, 12).reshape(shape) if shape + else np.float64(0.6)) p_xp = xp.asarray(p) args_xp = (p_xp,) dtype = p_xp.dtype @@ -539,7 +540,7 @@ def f(*args, **kwargs): if is_numpy(xp): xp_assert_equal(res.nfev, ref_nfev) assert xp.max(res.nfev) == f.f_evals - else: # different backend (and dtype) may lead to different nfev + else: # different backend may lead to different nfev assert res.nfev.shape == shape assert res.nfev.dtype == xp.int32 @@ -565,14 +566,18 @@ def f(*args, **kwargs): assert xp.all((res.x[finite] == res.xl[finite]) | (res.x[finite] == res.xr[finite])) + # Torch and NumPy don't solve to precisely the same accuracy + # on all machines. That's OK. + atol = 1e-9 if is_torch(xp) else 1e-15 + ref_fl = [ref.fl for ref in refs] ref_fl = xp.reshape(xp.asarray(ref_fl, dtype=dtype), shape) - xp_assert_close(res.fl, ref_fl, atol=1e-7) + xp_assert_close(res.fl, ref_fl, atol=atol) xp_assert_equal(res.fl, self.f(res.xl, *args_xp)) ref_fr = [ref.fr for ref in refs] ref_fr = xp.reshape(xp.asarray(ref_fr, dtype=dtype), shape) - xp_assert_close(res.fr, ref_fr, atol=1e-7) + xp_assert_close(res.fr, ref_fr, atol=atol) xp_assert_equal(res.fr, self.f(res.xr, *args_xp)) assert xp.all(xp.abs(res.fun[finite]) == From cac99cf60d9f556d4bb7b0de456bf370a8d8ff95 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 May 2024 12:56:44 -0500 Subject: [PATCH 129/500] MAINT: optimize._chandrupatla: fix second torch special case issue --- scipy/optimize/tests/test_chandrupatla.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scipy/optimize/tests/test_chandrupatla.py b/scipy/optimize/tests/test_chandrupatla.py index 1948f597ff65..e5c9413dd278 100644 --- a/scipy/optimize/tests/test_chandrupatla.py +++ b/scipy/optimize/tests/test_chandrupatla.py @@ -833,14 +833,13 @@ def f(x): xp_test = array_namespace(a) # need isdtype def f(x): assert xp_test.isdtype(x.dtype, "real floating") - return x ** 99 - 1 + # this would overflow if x were an xp integer dtype + return x ** 31 - 1 # note that all inputs are integer type; result is automatically default float res = _chandrupatla_root(f, xp.asarray(-7), xp.asarray(5)) - if not is_torch(xp): - # torch fails, probably because of overflow - assert res.success - xp_assert_close(res.x, xp.asarray(1.)) + assert res.success + xp_assert_close(res.x, xp.asarray(1.)) # Test that if both ends of bracket equal root, algorithm reports # convergence. From ed9eec59d703d59da8933d6bb1de9910de84ffb9 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 May 2024 14:10:06 -0500 Subject: [PATCH 130/500] TST: optimize._chandrupatla: remove unnecessary test --- scipy/optimize/_chandrupatla.py | 2 -- scipy/optimize/tests/test_chandrupatla.py | 5 ----- 2 files changed, 7 deletions(-) diff --git a/scipy/optimize/_chandrupatla.py b/scipy/optimize/_chandrupatla.py index be1382c4319b..9a7ed1952db0 100644 --- a/scipy/optimize/_chandrupatla.py +++ b/scipy/optimize/_chandrupatla.py @@ -5,8 +5,6 @@ from scipy._lib._array_api import xp_clip, xp_minimum, xp_sign # TODO: -# - debug test_maxiter_callback -# - debug torch failure in test_special_cases - `float64` is fine? # - (maybe?) don't use fancy indexing assignment # - figure out how to replace the new `try`/`except`s diff --git a/scipy/optimize/tests/test_chandrupatla.py b/scipy/optimize/tests/test_chandrupatla.py index e5c9413dd278..c36346947348 100644 --- a/scipy/optimize/tests/test_chandrupatla.py +++ b/scipy/optimize/tests/test_chandrupatla.py @@ -713,14 +713,9 @@ def callback(res): if key == 'status': xp_assert_equal(res[key], xp.asarray(eim._ECONVERR, dtype=xp.int32)) xp_assert_equal(res2[key], xp.asarray(eim._ECALLBACK, dtype=xp.int32)) - # TODO: debug this. Somehow `callback.res` is getting changed when - # StopIteration is raised. - # xp_assert_equal(callback.res[key], - # xp.asarray(eim._EINPROGRESS, dtype=xp.int32)) elif key.startswith('_'): continue else: - xp_assert_equal(callback.res[key], res[key]) xp_assert_equal(res2[key], res[key]) @pytest.mark.parametrize('case', _CHANDRUPATLA_TESTS) From 7f920c454f4eb93c7b4bc397b56b04698c2faebc Mon Sep 17 00:00:00 2001 From: fancidev Date: Mon, 13 May 2024 08:00:30 +0800 Subject: [PATCH 131/500] ENH: Add `scipy.special._ufuncs._iv_ratio` (#20457) * Add continued fraction evaluator * Add stub for iv_ratio * Basic implementation of iv_ratio. * Polish the continued fraction evaluator. * Refactor continued fraction convergent difference into a generator. * Refactor into Kahan series evaluator. * Clean up and add continued_fraction_series() function. * Refactor to use generator protocol. * Cosmetic changes to make code style more consistent. * Handle invalid arguments to _iv_ratio. * Clean up unused code. * Rearrange formula to simplify code and improve accuracy. * Unify definition of inv_ratio(v,x) := iv(v,x)/iv(v-1,x). * Add comp argument to return one's complement of iv_ratio. Adjust c.f. leading term. * Scale multiplier by 2 to avoid subnormal multiplier. * Remove the comp argument of iv_ratio. A separate function to compute the complement of iv_ratio is preferred and may be added in the future. * Move iv_ratio.h into special/ directory. * Add float overload of iv_ratio(). * Return quiet NaN as error handling is delegated to set_error(). * Pass arguments to kahan_step() by reference. * Return signed zero if x is signed zero. * Add unit tests for iv_ratio. * Ignore spurious mypy error in unit test file. --------- Co-authored-by: Albert Steppi --- scipy/special/_generate_pyx.py | 3 +- scipy/special/_special_ufuncs.cpp | 8 + scipy/special/_special_ufuncs_docs.cpp | 8 + scipy/special/special/config.h | 3 + scipy/special/special/iv_ratio.h | 106 +++++++++++++ scipy/special/special/tools.h | 198 ++++++++++++++++++++++++- scipy/special/tests/meson.build | 1 + scipy/special/tests/test_iv_ratio.py | 136 +++++++++++++++++ 8 files changed, 459 insertions(+), 4 deletions(-) create mode 100644 scipy/special/special/iv_ratio.h create mode 100644 scipy/special/tests/test_iv_ratio.py diff --git a/scipy/special/_generate_pyx.py b/scipy/special/_generate_pyx.py index 0873672dda62..8044a6c79154 100644 --- a/scipy/special/_generate_pyx.py +++ b/scipy/special/_generate_pyx.py @@ -82,7 +82,8 @@ '_spherical_kn', '_spherical_kn_d', 'airy', 'airye', 'bei', 'beip', 'ber', 'berp', 'binom', 'exp1', 'expi', 'expit', 'exprel', 'gamma', 'gammaln', 'hankel1', 'hankel1e', 'hankel2', 'hankel2e', 'hyp2f1', 'it2i0k0', 'it2j0y0', 'it2struve0', - 'itairy', 'iti0k0', 'itj0y0', 'itmodstruve0', 'itstruve0', 'iv', 'ive', 'jv', + 'itairy', 'iti0k0', 'itj0y0', 'itmodstruve0', 'itstruve0', + 'iv', '_iv_ratio', 'ive', 'jv', 'jve', 'kei', 'keip', 'kelvin', 'ker', 'kerp', 'kv', 'kve', 'log_expit', 'log_wright_bessel', 'loggamma', 'logit', 'mathieu_a', 'mathieu_b', 'mathieu_cem', 'mathieu_modcem1', 'mathieu_modcem2', 'mathieu_modsem1', 'mathieu_modsem2', diff --git a/scipy/special/_special_ufuncs.cpp b/scipy/special/_special_ufuncs.cpp index b74feafb5f9a..d143f3180645 100644 --- a/scipy/special/_special_ufuncs.cpp +++ b/scipy/special/_special_ufuncs.cpp @@ -14,6 +14,7 @@ #include "special/fresnel.h" #include "special/gamma.h" #include "special/hyp2f1.h" +#include "special/iv_ratio.h" #include "special/kelvin.h" #include "special/lambertw.h" #include "special/legendre.h" @@ -139,6 +140,7 @@ extern const char *hankel2_doc; extern const char *hankel2e_doc; extern const char *hyp2f1_doc; extern const char *iv_doc; +extern const char *iv_ratio_doc; extern const char *ive_doc; extern const char *jv_doc; extern const char *jve_doc; @@ -413,6 +415,12 @@ PyMODINIT_FUNC PyInit__special_ufuncs() { ); PyModule_AddObjectRef(_special_ufuncs, "iv", iv); + PyObject *iv_ratio = SpecFun_NewUFunc( + {static_cast(special::iv_ratio), static_cast(special::iv_ratio)}, + "_iv_ratio", iv_ratio_doc + ); + PyModule_AddObjectRef(_special_ufuncs, "_iv_ratio", iv_ratio); + PyObject *ive = SpecFun_NewUFunc( {static_cast(special::cyl_bessel_ie), static_cast(special::cyl_bessel_ie), static_cast(special::cyl_bessel_ie), static_cast(special::cyl_bessel_ie)}, diff --git a/scipy/special/_special_ufuncs_docs.cpp b/scipy/special/_special_ufuncs_docs.cpp index a18fd29d1728..c44a83f24325 100644 --- a/scipy/special/_special_ufuncs_docs.cpp +++ b/scipy/special/_special_ufuncs_docs.cpp @@ -1760,6 +1760,14 @@ const char *iv_doc = R"( )"; +const char *iv_ratio_doc = R"( + _iv_ratio(v, x, out=None) + + Internal function, do not use. + + Return `iv(v, x) / iv(v-1, x)` for `v >= 1` and `x >= 0`. + )"; + const char *ive_doc = R"( ive(v, z, out=None) diff --git a/scipy/special/special/config.h b/scipy/special/special/config.h index bfba3d8bf188..559ab0c39906 100644 --- a/scipy/special/special/config.h +++ b/scipy/special/special/config.h @@ -210,9 +210,12 @@ using cuda::std::uint64_t; #include #include #include +#include +#include #include #include #include +#include #ifdef DEBUG #define SPECFUN_ASSERT(a) assert(a) diff --git a/scipy/special/special/iv_ratio.h b/scipy/special/special/iv_ratio.h new file mode 100644 index 000000000000..6a2731afa8f1 --- /dev/null +++ b/scipy/special/special/iv_ratio.h @@ -0,0 +1,106 @@ +// Numerically stable computation of iv(v+1, x) / iv(v, x) + +#pragma once + +#include "config.h" +#include "tools.h" +#include "error.h" + +namespace special { + +/* Generates the "tail" of Perron's continued fraction for `iv(v,x)/iv(v-1,x)` + * for v >= 1 and x >= 0. + * + * The Perron continued fraction is studied in [1]. It is given by + * + * iv(v, x) x -(2v+1)x -(2v+3)x -(2v+5)x + * R := --------- = ------ ---------- ---------- ---------- ... + * iv(v-1,x) 2v+x + 2(v+x)+1 + 2(v+x)+2 + 2(v+x)+3 + + * + * Rearrange the expression by making an equivalent transform to prevent + * floating point overflow and extracting the first fraction to simplify + * the recurrence relation. This leads to + * + * xc -(2vc+c)(xc) -(2vc+3c)(xc) -(2vc+5c)(xc) + * R = -----, fc = 2vc + ------------ ------------- ------------- ... + * xc+fc 2(vc+xc)+c + 2(vc+xc)+2c + 2(vc+xc)+3c + + * + * This class generates the fractions of fc after 2vc. + * + * [1] Gautschi, W. and Slavik, J. (1978). "On the computation of modified + * Bessel function ratios." Mathematics of Computation, 32(143):865-875. + */ +struct IvRatioCFTailGenerator { + + // It is assumed that v >= 1, x >= 0, c > 0, and all are finite. + IvRatioCFTailGenerator(double vc, double xc, double c) noexcept { + a0_ = -(2*vc-c)*xc; + as_ = -2*c*xc; + b0_ = 2*(vc+xc); + bs_ = c; + k_ = 0; + } + + std::pair operator()() { + ++k_; + return {std::fma(k_, as_, a0_), std::fma(k_, bs_, b0_)}; + } + +private: + double a0_, as_; // a[k] == a0 + as*k, k >= 1 + double b0_, bs_; // b[k] == b0 + bs*k, k >= 1 + std::uint64_t k_; // current index +}; + +SPECFUN_HOST_DEVICE inline double iv_ratio(double v, double x) { + + if (std::isnan(v) || std::isnan(x)) { + return std::numeric_limits::quiet_NaN(); + } + if (v < 1 || x < 0) { + set_error("iv_ratio", SF_ERROR_DOMAIN, NULL); + return std::numeric_limits::quiet_NaN(); + } + if (std::isinf(v) && std::isinf(x)) { + // There is not a unique limit as both v and x tends to infinity. + set_error("iv_ratio", SF_ERROR_DOMAIN, NULL); + return std::numeric_limits::quiet_NaN(); + } + if (x == 0.0) { + // If x is +/-0.0, return +/-0.0 to agree with the limiting behavior. + return x; + } + if (std::isinf(v)) { + return 0.0; + } + if (std::isinf(x)) { + return 1.0; + } + + // Now v >= 1 and x >= 0 and both are finite. + int e; + std::frexp(std::fmax(v, x), &e); + double c = std::ldexp(1, 2-e); // rescaling multiplier + double vc = v * c; + double xc = x * c; + + IvRatioCFTailGenerator cf(vc, xc, c); + auto [fc, terms] = detail::series_eval_kahan( + detail::continued_fraction_series(cf), + std::numeric_limits::epsilon() * 0.5, + 1000, + 2*vc); + + if (terms == 0) { // failed to converge; should not happen + set_error("iv_ratio", SF_ERROR_NO_RESULT, NULL); + return std::numeric_limits::quiet_NaN(); + } + + return xc / (xc + fc); +} + +SPECFUN_HOST_DEVICE inline float iv_ratio(float v, float x) { + return iv_ratio(static_cast(v), static_cast(x)); +} + +} // namespace special diff --git a/scipy/special/special/tools.h b/scipy/special/special/tools.h index 66fcef58f17c..1b94453cb239 100644 --- a/scipy/special/special/tools.h +++ b/scipy/special/special/tools.h @@ -8,6 +8,38 @@ namespace special { namespace detail { + /* Result type of a "generator", a callable object that produces a value + * each time it is called. + */ + template + using generator_result_t = std::decay_t>; + + /* Used to deduce the type of the numerator/denominator of a fraction. */ + template + struct pair_traits; + + template + struct pair_traits> { + using value_type = T; + }; + + template + using pair_value_t = typename pair_traits::value_type; + + /* Used to extract the "value type" of a complex type. */ + template + struct real_type { + using type = T; + }; + + template + struct real_type> { + using type = T; + }; + + template + using real_type_t = typename real_type::type; + // Return NaN, handling both real and complex types. template SPECFUN_HOST_DEVICE inline std::enable_if_t, T> maybe_complex_NaN() { @@ -21,8 +53,8 @@ namespace detail { } // Series evaluators. - template - SPECFUN_HOST_DEVICE T series_eval(Generator &g, T init_val, double tol, std::uint64_t max_terms, + template > + SPECFUN_HOST_DEVICE T series_eval(Generator &g, T init_val, real_type_t tol, std::uint64_t max_terms, const char *func_name) { /* Sum an infinite series to a given precision. * @@ -54,7 +86,7 @@ namespace detail { return maybe_complex_NaN(); } - template + template > SPECFUN_HOST_DEVICE T series_eval_fixed_length(Generator &g, T init_val, std::uint64_t num_terms) { /* Sum a fixed number of terms from a series. * @@ -73,5 +105,165 @@ namespace detail { return result; } + /* Performs one step of Kahan summation. */ + template + SPECFUN_HOST_DEVICE void kahan_step(T& sum, T& comp, T x) { + T y = x - comp; + T t = sum + y; + comp = (t - sum) - y; + sum = t; + } + + /* Evaluates an infinite series using Kahan summation. + * + * Denote the series by + * + * S = a[0] + a[1] + a[2] + ... + * + * And for n = 0, 1, 2, ..., denote its n-th partial sum by + * + * S[n] = a[0] + a[1] + ... + a[n] + * + * This function computes S[0], S[1], ... until a[n] is sufficiently + * small or if the maximum number of terms have been evaluated. + * + * Parameters + * ---------- + * g + * Reference to generator that yields the sequence of values a[1], + * a[2], a[3], ... + * + * tol + * Relative tolerance for convergence. Specifically, stop iteration + * as soon as `abs(a[n]) <= tol * abs(S[n])` for some n >= 1. + * + * max_terms + * Maximum number of terms after a[0] to evaluate. It should be set + * large enough such that the convergence criterion is guaranteed + * to have been satisfied within that many terms if there is no + * rounding error. + * + * init_val + * a[0]. Default is zero. The type of this parameter (T) is used + * for intermediary computations as well as the result. + * + * Return Value + * ------------ + * If the convergence criterion is satisfied by some `n <= max_terms`, + * returns `(S[n], n)`. Otherwise, returns `(S[max_terms], 0)`. + */ + template > + SPECFUN_HOST_DEVICE std::pair series_eval_kahan( + Generator &&g, real_type_t tol, std::uint64_t max_terms, T init_val = T(0)) { + + T sum = init_val; + T comp = 0; + for (std::uint64_t i = 0; i < max_terms; ++i) { + T term = g(); + kahan_step(sum, comp, term); + if (std::abs(term) <= tol * std::abs(sum)) { + return {sum, i + 1}; + } + } + return {sum, 0}; + } + + /* Generator that yields the difference of successive convergents of a + * continued fraction. + * + * Let f[n] denote the n-th convergent of a continued fraction: + * + * a[1] a[2] a[n] + * f[n] = b[0] + ------ ------ ... ---- + * b[1] + b[2] + b[n] + * + * with f[0] = b[0]. This generator yields the sequence of values + * f[1]-f[0], f[2]-f[1], f[3]-f[2], ... + * + * Constructor Arguments + * --------------------- + * cf + * Reference to generator that yields the terms of the continued + * fraction as (numerator, denominator) pairs, starting from + * (a[1], b[1]). + * + * `cf` must outlive the ContinuedFractionSeriesGenerator object. + * + * The constructed object always eagerly retrieves the next term + * of the continued fraction. Specifically, (a[1], b[1]) is + * retrieved upon construction, and (a[n], b[n]) is retrieved after + * (n-1) calls of `()`. + * + * Type Arguments + * -------------- + * T + * Type in which computations are performed and results are turned. + * + * Remarks + * ------- + * The series is computed using the recurrence relation described in [1]. + * + * No error checking is performed. The caller must ensure that all terms + * are finite and that intermediary computations do not trigger floating + * point exceptions such as overflow. + * + * The numerical stability of this method depends on the characteristics + * of the continued fraction being evaluated. + * + * Reference + * --------- + * [1] Gautschi, W. (1967). “Computational Aspects of Three-Term + * Recurrence Relations.” SIAM Review, 9(1):24-82. + */ + template >> + class ContinuedFractionSeriesGenerator { + + public: + explicit ContinuedFractionSeriesGenerator(Generator &cf) : cf_(cf) { + init(); + } + + double operator()() { + double v = v_; + advance(); + return v; + } + + private: + void init() { + auto [num, denom] = cf_(); + T a = num; + T b = denom; + u_ = T(1); + v_ = a / b; + b_ = b; + } + + void advance() { + auto [num, denom] = cf_(); + T a = num; + T b = denom; + u_ = T(1) / (T(1) + (a * u_) / (b * b_)); + v_ *= (u_ - T(1)); + b_ = b; + } + + Generator& cf_; // reference to continued fraction generator + T v_; // v[n] == f[n] - f[n-1], n >= 1 + T u_; // u[1] = 1, u[n] = v[n]/v[n-1], n >= 2 + T b_; // last denominator, i.e. b[n-1] + }; + + /* Converts a continued fraction into a series whose terms are the + * difference of its successive convergents. + * + * See ContinuedFractionSeriesGenerator for details. + */ + template >> + SPECFUN_HOST_DEVICE ContinuedFractionSeriesGenerator + continued_fraction_series(Generator &cf) { + return ContinuedFractionSeriesGenerator(cf); + } + } // namespace detail } // namespace special diff --git a/scipy/special/tests/meson.build b/scipy/special/tests/meson.build index e876cc440e2a..7a803fc8663c 100644 --- a/scipy/special/tests/meson.build +++ b/scipy/special/tests/meson.build @@ -19,6 +19,7 @@ python_sources = [ 'test_gammainc.py', 'test_hyp2f1.py', 'test_hypergeometric.py', + 'test_iv_ratio.py', 'test_kolmogorov.py', 'test_lambertw.py', 'test_log_softmax.py', diff --git a/scipy/special/tests/test_iv_ratio.py b/scipy/special/tests/test_iv_ratio.py new file mode 100644 index 000000000000..d814d8d06695 --- /dev/null +++ b/scipy/special/tests/test_iv_ratio.py @@ -0,0 +1,136 @@ +# This file contains unit tests for the iv_ratio() function. + +import pytest +import numpy as np +from numpy.testing import assert_equal, assert_allclose +from scipy.special._ufuncs import _iv_ratio as iv_ratio # type: ignore[attr-defined] + + +class TestIvRatio: + + @pytest.mark.parametrize('v,x,r', [ + (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 (1, 2.34, 56.789): + xs = _sample(5, v=v) + for x in xs: + print(f"({v}, {x}, {float(iv_ratio_mp_float(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. + + Also check that the function + never returns -0.0.""" + assert_equal(iv_ratio(v, x), r) + + @pytest.mark.parametrize('v', [np.nextafter(1, 0), -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', [1, np.finfo(float).max, np.inf]) + def test_zero_x(self, v): + """If x is +/-0.0, return x to agree with the limiting behavior.""" + 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-1+sqrt(x**2+(v+1)**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) From b02d6019ded511a5dfcecf27e1556898d3edf72b Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 May 2024 20:12:34 -0500 Subject: [PATCH 132/500] MAINT: _lib.xp_clip: resolve array_api_strict complaint --- scipy/_lib/_array_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 76955f1e1231..ac35122bc5c0 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -406,7 +406,8 @@ def xp_minimum(x1, x2): # temporary substitute for xp.clip, which is not yet in all backends # or covered by array_api_compat. def xp_clip(x, a, b, xp=None): - xp = array_namespace(a, b) if xp is None else xp + xp = array_namespace(x) if xp is None else xp + a, b = xp.asarray(a), xp.asarray(b) if hasattr(xp, 'clip'): return xp.clip(x, a, b) x, a, b = xp.broadcast_arrays(x, a, b) From cb45c37f30f645be986af8581f7545d1ab39851b Mon Sep 17 00:00:00 2001 From: Valerix <13818931+valer-ix@users.noreply.github.com> Date: Mon, 13 May 2024 07:31:01 +0100 Subject: [PATCH 133/500] MAINT: Add missing whitespace in error messages (#20705) --- scipy/integrate/_ode.py | 4 ++-- scipy/interpolate/_bsplines.py | 2 +- scipy/linalg/_decomp_qr.py | 2 +- scipy/signal/_fir_filter_design.py | 2 +- scipy/spatial/_ckdtree.pyx | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scipy/integrate/_ode.py b/scipy/integrate/_ode.py index eb03d194a353..794a4dc63721 100644 --- a/scipy/integrate/_ode.py +++ b/scipy/integrate/_ode.py @@ -742,8 +742,8 @@ def set_solout(self, solout): if self._integrator.supports_solout: self._integrator.set_solout(solout, complex=True) else: - raise TypeError("selected integrator does not support solouta," - + "choose another one") + raise TypeError("selected integrator does not support solouta, " + "choose another one") # ------------------------------------------------------------------------------ diff --git a/scipy/interpolate/_bsplines.py b/scipy/interpolate/_bsplines.py index 8cc6eb5411ed..76eb95bf8b61 100644 --- a/scipy/interpolate/_bsplines.py +++ b/scipy/interpolate/_bsplines.py @@ -802,7 +802,7 @@ def from_power_basis(cls, pp, bc_type='not-a-knot'): """ from ._cubic import CubicSpline if not isinstance(pp, CubicSpline): - raise NotImplementedError("Only CubicSpline objects are accepted" + raise NotImplementedError("Only CubicSpline objects are accepted " "for now. Got %s instead." % type(pp)) x = pp.x coef = pp.c diff --git a/scipy/linalg/_decomp_qr.py b/scipy/linalg/_decomp_qr.py index 8813d3745e04..8a2f3cc8cbb7 100644 --- a/scipy/linalg/_decomp_qr.py +++ b/scipy/linalg/_decomp_qr.py @@ -122,7 +122,7 @@ def qr(a, overwrite_a=False, lwork=None, mode='full', pivoting=False, # 'qr' are used below. # 'raw' is used internally by qr_multiply if mode not in ['full', 'qr', 'r', 'economic', 'raw']: - raise ValueError("Mode argument should be one of ['full', 'r'," + raise ValueError("Mode argument should be one of ['full', 'r', " "'economic', 'raw']") if check_finite: diff --git a/scipy/signal/_fir_filter_design.py b/scipy/signal/_fir_filter_design.py index 3d1c9c13dc26..476df46a0336 100644 --- a/scipy/signal/_fir_filter_design.py +++ b/scipy/signal/_fir_filter_design.py @@ -968,7 +968,7 @@ def firls(numtaps, bands, desired, *, weight=None, fs=None): desired = np.asarray(desired).flatten() if bands.size != desired.size: raise ValueError( - f"desired must have one entry per frequency, got {desired.size}" + f"desired must have one entry per frequency, got {desired.size} " f"gains for {bands.size} frequencies." ) desired.shape = (-1, 2) diff --git a/scipy/spatial/_ckdtree.pyx b/scipy/spatial/_ckdtree.pyx index 84240c9043b8..548cc5461368 100644 --- a/scipy/spatial/_ckdtree.pyx +++ b/scipy/spatial/_ckdtree.pyx @@ -557,7 +557,7 @@ cdef class cKDTree: data = np.array(data, order='C', copy=copy_data, dtype=np.float64) if data.ndim != 2: - raise ValueError("data must be of shape (n, m), where there are" + raise ValueError("data must be of shape (n, m), where there are " "n points of dimension m") if not np.isfinite(data).all(): From 84765ae796a4fca20c22ce95be1c9dea85344bfc Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 13 May 2024 08:27:53 -0500 Subject: [PATCH 134/500] MAINT: _lib.xp_clip: fix dtype bug --- scipy/_lib/_array_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index ac35122bc5c0..0e0d4382a332 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -407,7 +407,7 @@ def xp_minimum(x1, x2): # or covered by array_api_compat. def xp_clip(x, a, b, xp=None): xp = array_namespace(x) if xp is None else xp - a, b = xp.asarray(a), xp.asarray(b) + a, b = xp.asarray(a, dtype=x.dtype), xp.asarray(b, dtype=x.dtype) if hasattr(xp, 'clip'): return xp.clip(x, a, b) x, a, b = xp.broadcast_arrays(x, a, b) From 1c0bdc44fff93c5c6277a3cb4dac66b4bf07bfcf Mon Sep 17 00:00:00 2001 From: Albert Steppi Date: Mon, 13 May 2024 12:41:23 -0400 Subject: [PATCH 135/500] MAINT: optimize._bracket: graceful handling of invalid initial brackets (#20685) * MAINT: optimize._bracket: graceful handling of invalid initial brackets Co-authored-by: Matt Haberland --- scipy/_lib/_elementwise_iterative_method.py | 1 + scipy/optimize/_bracket.py | 38 ++++++++------ scipy/optimize/tests/test_bracket.py | 57 +++++++++------------ 3 files changed, 46 insertions(+), 50 deletions(-) diff --git a/scipy/_lib/_elementwise_iterative_method.py b/scipy/_lib/_elementwise_iterative_method.py index 1c7820bbfdff..2252a3a9717a 100644 --- a/scipy/_lib/_elementwise_iterative_method.py +++ b/scipy/_lib/_elementwise_iterative_method.py @@ -18,6 +18,7 @@ _ECONVERR = -2 _EVALUEERR = -3 _ECALLBACK = -4 +_EINPUTERR = -5 _ECONVERGED = 0 _EINPROGRESS = 1 diff --git a/scipy/optimize/_bracket.py b/scipy/optimize/_bracket.py index 2210295ea820..3c398f14fa09 100644 --- a/scipy/optimize/_bracket.py +++ b/scipy/optimize/_bracket.py @@ -46,9 +46,6 @@ def _bracket_root_iv(func, xl0, xr0, xmin, xmax, factor, args, maxiter): if not maxiter == maxiter_int or maxiter < 0: raise ValueError(message) - if not np.all((xmin <= xl0) & (xl0 < xr0) & (xr0 <= xmax)): - raise ValueError('`xmin <= xl0 < xr0 <= xmax` must be True (elementwise).') - return func, xl0, xr0, xmin, xmax, factor, args, maxiter @@ -116,6 +113,7 @@ def _bracket_root(func, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, - ``-2`` : The maximum number of iterations was reached. - ``-3`` : A non-finite value was encountered. - ``-4`` : Iteration was terminated by `callback`. + - ``-5``: The initial bracket does not satisfy `xmin <= xl0 < xr0 < xmax`. - ``1`` : The algorithm is proceeding normally (in `callback` only). - ``2`` : A bracket was found in the opposite search direction (in `callback` only). @@ -159,6 +157,10 @@ def _bracket_root(func, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, xs = (xl0, xr0) temp = eim._initialize(func, xs, args) func, xs, fs, args, shape, dtype = temp # line split for PEP8 + xl0, xr0 = xs + xmin = np.broadcast_to(xmin, shape).astype(dtype, copy=False).ravel() + xmax = np.broadcast_to(xmax, shape).astype(dtype, copy=False).ravel() + invalid_bracket = ~((xmin <= xl0) & (xl0 < xr0) & (xr0 <= xmax)) # The approach is to treat the left and right searches as though they were # (almost) totally independent one-sided bracket searches. (The interaction @@ -167,6 +169,7 @@ def _bracket_root(func, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, # `x` is the "moving" end of the bracket x = np.concatenate(xs) f = np.concatenate(fs) + invalid_bracket = np.concatenate((invalid_bracket, invalid_bracket)) n = len(x) // 2 # `x_last` is the previous location of the moving end of the bracket. If @@ -179,9 +182,6 @@ def _bracket_root(func, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, # We don't need to retain the corresponding function value, since the # fixed end of the bracket is only needed to compute the new value of the # moving end; it is never returned. - - xmin = np.broadcast_to(xmin, shape).astype(dtype, copy=False).ravel() - xmax = np.broadcast_to(xmax, shape).astype(dtype, copy=False).ravel() limit = np.concatenate((xmin, xmax)) factor = np.broadcast_to(factor, shape).astype(dtype, copy=False).ravel() @@ -206,6 +206,7 @@ def _bracket_root(func, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, d[ni] = limit[ni] - x[ni] status = np.full_like(x, eim._EINPROGRESS, dtype=int) # in progress + status[invalid_bracket] = eim._EINPUTERR nit, nfev = 0, 1 # one function evaluation per side performed above work = _RichResult(x=x, x0=x0, f=f, limit=limit, factor=factor, @@ -245,12 +246,13 @@ def post_func_eval(x, f, work): work.f = f def check_termination(work): - stop = np.zeros_like(work.x, dtype=bool) + # Condition 0: initial bracket is invalid + stop = (work.status == eim._EINPUTERR) # Condition 1: a valid bracket (or the root itself) has been found sf = np.sign(work.f) sf_last = np.sign(work.f_last) - i = (sf_last == -sf) | (sf_last == 0) | (sf == 0) + i = ((sf_last == -sf) | (sf_last == 0) | (sf == 0)) & ~stop work.status[i] = eim._ECONVERGED stop[i] = True @@ -449,11 +451,6 @@ def _bracket_minimum_iv(func, xm0, xl0, xr0, xmin, xmax, factor, args, maxiter): if not maxiter == maxiter_int or maxiter < 0: raise ValueError(message) - if not np.all((xmin <= xl0) & (xl0 < xm0) & (xm0 < xr0) & (xr0 <= xmax)): - raise ValueError( - '`xmin <= xl0 < xm0 < xr0 <= xmax` must be True (elementwise).' - ) - return func, xm0, xl0, xr0, xmin, xmax, factor, args, maxiter @@ -524,6 +521,9 @@ def _bracket_minimum(func, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, minimizer. - ``-2`` : The maximum number of iterations was reached. - ``-3`` : A non-finite value was encountered. + - ``-4`` : ``None`` shall pass. + - ``-5`` : The initial bracket does not satisfy + `xmin <= xl0 < xm0 < xr0 <= xmax`. success : bool ``True`` when the algorithm terminated successfully (status ``0``). @@ -568,6 +568,7 @@ def _bracket_minimum(func, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, fl0, fm0, fr0 = fs xmin = np.broadcast_to(xmin, shape).astype(dtype, copy=False).ravel() xmax = np.broadcast_to(xmax, shape).astype(dtype, copy=False).ravel() + invalid_bracket = ~((xmin <= xl0) & (xl0 < xm0) & (xm0 < xr0) & (xr0 <= xmax)) # We will modify factor later on so make a copy. np.broadcast_to returns # a read-only view. factor = np.broadcast_to(factor, shape).astype(dtype, copy=True).ravel() @@ -591,6 +592,7 @@ def _bracket_minimum(func, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, factor[limited] = 1 / factor[limited] status = np.full_like(xl0, eim._EINPROGRESS, dtype=int) + status[invalid_bracket] = eim._EINPUTERR nit, nfev = 0, 3 work = _RichResult(xl=xl0, xm=xm0, xr=xr0, xr0=xr0, fl=fl0, fm=fm0, fr=fr0, @@ -622,12 +624,16 @@ def post_func_eval(x, f, work): work.fl, work.fm, work.fr = work.fm, work.fr, f def check_termination(work): + # Condition 0: Initial bracket is invalid. + stop = (work.status == eim._EINPUTERR) + # Condition 1: A valid bracket has been found. - stop = ( + i = ( (work.fl >= work.fm) & (work.fr > work.fm) | (work.fl > work.fm) & (work.fr >= work.fm) - ) - work.status[stop] = eim._ECONVERGED + ) & ~stop + work.status[i] = eim._ECONVERGED + stop[i] = True # Condition 2: Moving end of bracket reaches limit. i = (work.xr == work.limit) & ~stop diff --git a/scipy/optimize/tests/test_bracket.py b/scipy/optimize/tests/test_bracket.py index af8e6c8842d4..370efe79e545 100644 --- a/scipy/optimize/tests/test_bracket.py +++ b/scipy/optimize/tests/test_bracket.py @@ -155,20 +155,25 @@ def f(xs, js): funcs = [lambda x: x - 1.5, lambda x: x - 1000, lambda x: x - 1000, - lambda x: np.nan] + lambda x: np.nan, + lambda x: x] return [funcs[j](x) for x, j in zip(xs, js)] - args = (np.arange(4, dtype=np.int64),) - res = _bracket_root(f, xl0=[-1, -1, -1, -1], xr0=[1, 1, 1, 1], - xmin=[-np.inf, -1, -np.inf, -np.inf], - xmax=[np.inf, 1, np.inf, np.inf], + args = (np.arange(5, dtype=np.int64),) + res = _bracket_root(f, + xl0=[-1, -1, -1, -1, 4], + xr0=[1, 1, 1, 1, -4], + xmin=[-np.inf, -1, -np.inf, -np.inf, 6], + xmax=[np.inf, 1, np.inf, np.inf, 2], args=args, maxiter=3) ref_flags = np.array([eim._ECONVERGED, _ELIMITS, eim._ECONVERR, - eim._EVALUEERR]) + eim._EVALUEERR, + eim._EINPUTERR]) + assert_equal(res.status, ref_flags) @pytest.mark.parametrize("root", (0.622, [0.622, 0.623])) @@ -213,14 +218,6 @@ def test_input_validation(self): with pytest.raises(ValueError, match=message): _bracket_root(lambda x: x, -4, 4, factor=0.5) - message = '`xmin <= xl0 < xr0 <= xmax` must be True' - with pytest.raises(ValueError, match=message): - _bracket_root(lambda x: x, 4, -4) - with pytest.raises(ValueError, match=message): - _bracket_root(lambda x: x, -4, 4, xmax=np.nan) - with pytest.raises(ValueError, match=message): - _bracket_root(lambda x: x, -4, 4, xmin=10) - message = "shape mismatch: objects cannot be broadcast" # raised by `np.broadcast, but the traceback is readable IMO with pytest.raises(ValueError, match=message): @@ -413,17 +410,23 @@ def f(xs, js): funcs = [lambda x: (x - 1.5)**2, lambda x: x, lambda x: x, - lambda x: np.nan] + lambda x: np.nan, + lambda x: x**2] + return [funcs[j](x) for x, j in zip(xs, js)] - args = (np.arange(4, dtype=np.int64),) - xl0, xm0, xr0 = np.full(4, -1.0), np.full(4, 0.0), np.full(4, 1.0) - result = _bracket_minimum(f, xm0, xl0=xl0, xr0=xr0, - xmin=[-np.inf, -1.0, -np.inf, -np.inf], + args = (np.arange(5, dtype=np.int64),) + xl0 = [-1.0, -1.0, -1.0, -1.0, 6.0] + xm0 = [0.0, 0.0, 0.0, 0.0, 4.0] + xr0 = [1.0, 1.0, 1.0, 1.0, 2.0] + xmin=[-np.inf, -1.0, -np.inf, -np.inf, 8.0] + + result = _bracket_minimum(f, xm0, xl0=xl0, xr0=xr0, xmin=xmin, args=args, maxiter=3) reference_flags = np.array([eim._ECONVERGED, _ELIMITS, - eim._ECONVERR, eim._EVALUEERR]) + eim._ECONVERR, eim._EVALUEERR, + eim._EINPUTERR]) assert_equal(result.status, reference_flags) @pytest.mark.parametrize("minimum", (0.622, [0.622, 0.623])) @@ -469,20 +472,6 @@ def test_input_validation(self): with pytest.raises(ValueError, match=message): _bracket_minimum(lambda x: x, -4, factor=0.5) - message = '`xmin <= xl0 < xm0 < xr0 <= xmax` must be True' - with pytest.raises(ValueError, match=message): - _bracket_minimum(lambda x: x**2, 4, xl0=6) - with pytest.raises(ValueError, match=message): - _bracket_minimum(lambda x: x**2, -4, xr0=-6) - with pytest.raises(ValueError, match=message): - _bracket_minimum(lambda x: x**2, -4, xl0=-3, xr0=-2) - with pytest.raises(ValueError, match=message): - _bracket_minimum(lambda x: x**2, -4, xl0=-6, xr0=-5) - with pytest.raises(ValueError, match=message): - _bracket_minimum(lambda x: x**2, -4, xl0=-np.nan) - with pytest.raises(ValueError, match=message): - _bracket_minimum(lambda x: x**2, -4, xr0=np.nan) - message = "shape mismatch: objects cannot be broadcast" # raised by `np.broadcast, but the traceback is readable IMO with pytest.raises(ValueError, match=message): From f0c89eaaab2a4a950ade37237bb3b53a7111092e Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 13 May 2024 14:11:09 -0500 Subject: [PATCH 136/500] Update scipy/optimize/_bracket.py --- scipy/optimize/_bracket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/_bracket.py b/scipy/optimize/_bracket.py index 74f4c824e1f5..6abfaaa94408 100644 --- a/scipy/optimize/_bracket.py +++ b/scipy/optimize/_bracket.py @@ -156,7 +156,7 @@ def _bracket_root(func, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, xs = (xl0, xr0) temp = eim._initialize(func, xs, args) - func, xs, fs, args, shape, dtype, _ = temp # line split for PEP8 + func, xs, fs, args, shape, dtype, xp = temp # line split for PEP8 xl0, xr0 = xs xmin = np.broadcast_to(xmin, shape).astype(dtype, copy=False).ravel() xmax = np.broadcast_to(xmax, shape).astype(dtype, copy=False).ravel() From 8d91da22c0b8ca955ba2b8e9a942ad315d29c6d5 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 14 May 2024 03:53:47 -0400 Subject: [PATCH 137/500] MAINT: stats._contains_nan: fix bug when -inf and inf are in sample (#20467) * MAINT: stats._contains_nan: replace summation check with max check * MAINT: stats._contains_nan: remove 'use_summation' option * TST: stats.kstest: add regression test for gh-20386 --- scipy/_lib/_util.py | 17 ++++++----------- scipy/stats/_stats_py.py | 4 ++-- scipy/stats/tests/test_stats.py | 11 +++++++++++ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py index c3811d675f4b..9457477e89bf 100644 --- a/scipy/_lib/_util.py +++ b/scipy/_lib/_util.py @@ -707,14 +707,11 @@ def _nan_allsame(a, axis, keepdims=False): return ((a0 == a) | np.isnan(a)).all(axis=axis, keepdims=keepdims) -def _contains_nan(a, nan_policy='propagate', use_summation=True, - policies=None, *, xp=None): +def _contains_nan(a, nan_policy='propagate', policies=None, *, xp=None): if xp is None: xp = array_namespace(a) not_numpy = not is_numpy(xp) - if not_numpy: - use_summation = False # some array_likes ignore nans (e.g. pandas) if policies is None: policies = {'propagate', 'raise', 'omit'} if nan_policy not in policies: @@ -722,13 +719,11 @@ def _contains_nan(a, nan_policy='propagate', use_summation=True, inexact = (xp.isdtype(a.dtype, "real floating") or xp.isdtype(a.dtype, "complex floating")) - if inexact: - # The summation method avoids creating another (potentially huge) array - if use_summation: - with np.errstate(invalid='ignore', over='ignore'): - contains_nan = xp.isnan(xp.sum(a)) - else: - contains_nan = xp.any(xp.isnan(a)) + if a.size == 0: + contains_nan = False + elif inexact: + # Faster and less memory-intensive than xp.any(xp.isnan(a)) + contains_nan = xp.isnan(xp.max(a)) elif is_numpy(xp) and np.issubdtype(a.dtype, object): contains_nan = False for el in a.ravel(): diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index d2072e6b03e4..736e2263d000 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -2383,8 +2383,8 @@ def percentileofscore(a, score, kind='rank', nan_policy='propagate'): score = np.asarray(score) # Nan treatment - cna, npa = _contains_nan(a, nan_policy, use_summation=False) - cns, nps = _contains_nan(score, nan_policy, use_summation=False) + cna, npa = _contains_nan(a, nan_policy) + cns, nps = _contains_nan(score, nan_policy) if (cna or cns) and nan_policy == 'raise': raise ValueError("The input contains nan values") diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 175ebf97c7de..6905e6adb6a2 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -4349,6 +4349,17 @@ def test_agree_with_ks_1samp(self): self._test_kstest_and_ks1samp(x, 'greater', mode='exact') self._test_kstest_and_ks1samp(x, 'less', mode='exact') + def test_pm_inf_gh20386(self): + # Check that gh-20386 is resolved - `kstest` does not + # return NaNs when both -inf and inf are in sample. + vals = [-np.inf, 0, 1, np.inf] + res = stats.kstest(vals, stats.cauchy.cdf) + ref = stats.kstest(vals, stats.cauchy.cdf, _no_deco=True) + assert np.all(np.isfinite(res)) + assert_equal(res, ref) + assert not np.isnan(res.statistic) + assert not np.isnan(res.pvalue) + # missing: no test that uses *args From 58e66faba645ea3b21b3f82de98d98c39c706866 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 14 May 2024 10:10:59 -0400 Subject: [PATCH 138/500] ENH: stats.entropy, special.{entr, rel_entr}: add array API support (#20673) --- scipy/special/__init__.py | 2 +- .../special/_support_alternative_backends.py | 48 ++++- .../test_support_alternative_backends.py | 20 ++ scipy/stats/_entropy.py | 17 +- scipy/stats/tests/test_entropy.py | 192 ++++++++++-------- 5 files changed, 179 insertions(+), 100 deletions(-) diff --git a/scipy/special/__init__.py b/scipy/special/__init__.py index 39db38ec110f..8f9e7e4c29ac 100644 --- a/scipy/special/__init__.py +++ b/scipy/special/__init__.py @@ -810,7 +810,7 @@ def _load_libsf_error_state(): # Replace some function definitions from _ufuncs to add Array API support from ._support_alternative_backends import ( log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, - gammaln, gammainc, gammaincc, logit, expit) + gammaln, gammainc, gammaincc, logit, expit, entr, rel_entr) from . import _basic from ._basic import * diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 1aa54969063e..c3ab21f3e175 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -9,13 +9,14 @@ # that these are defined in this file / report an error in __init__.py from ._ufuncs import ( log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, # noqa: F401 - gammaln, gammainc, gammaincc, logit, expit) # noqa: F401 + gammaln, gammainc, gammaincc, logit, expit, entr, rel_entr) # noqa: F401 _SCIPY_ARRAY_API = os.environ.get("SCIPY_ARRAY_API", False) array_api_compat_prefix = "scipy._lib.array_api_compat" def get_array_special_func(f_name, xp, n_array_args): + f = None if is_numpy(xp): f = getattr(_ufuncs, f_name, None) elif is_torch(xp): @@ -25,17 +26,52 @@ def get_array_special_func(f_name, xp, n_array_args): f = getattr(cupyx.scipy.special, f_name, None) elif xp.__name__ == f"{array_api_compat_prefix}.jax": f = getattr(xp.scipy.special, f_name, None) + + if f is not None: + return f + + # if generic array-API implementation is available, use that; + # otherwise, fall back to NumPy/SciPy + # Use of ` _f=_f, _xp=xp` is to avoid late-binding gotcha + if f_name in _generic_implementations: + _f = _generic_implementations[f_name] + def f(*args, _f=_f, _xp=xp, **kwargs): + return _f(*args, xp=_xp, **kwargs) else: - f_scipy = getattr(_ufuncs, f_name, None) - def f(*args, **kwargs): + _f = getattr(_ufuncs, f_name, None) + def f(*args, _f=_f, _xp=xp, **kwargs): array_args = args[:n_array_args] other_args = args[n_array_args:] array_args = [np.asarray(arg) for arg in array_args] - out = f_scipy(*array_args, *other_args, **kwargs) - return xp.asarray(out) + out = _f(*array_args, *other_args, **kwargs) + return _xp.asarray(out) return f + +def _get_shape_dtype(*args, xp): + args = xp.broadcast_arrays(*args) + shape = args[0].shape + dtype = xp.result_type(*args) + if xp.isdtype(dtype, 'integral'): + dtype = xp.float64 + args = [xp.asarray(arg, dtype=dtype) for arg in args] + return args, shape, dtype + + +def _rel_entr(x, y, *, xp): + args, shape, dtype = _get_shape_dtype(x, y, xp=xp) + x, y = args + res = xp.full(x.shape, xp.inf, dtype=dtype) + res[(x == 0) & (y >= 0)] = xp.asarray(0, dtype=dtype) + i = (x > 0) & (y > 0) + res[i] = x[i] * (xp.log(x[i]) - xp.log(y[i])) + return res + + +_generic_implementations = {'rel_entr': _rel_entr} + + # functools.wraps doesn't work because: # 'numpy.ufunc' object has no attribute '__module__' def support_alternative_backends(f_name, n_array_args): @@ -65,6 +101,8 @@ def wrapped(*args, **kwargs): 'gammaincc': 2, 'logit': 1, 'expit': 1, + 'entr': 1, + 'rel_entr': 2, } for f_name, n_array_args in array_special_func_map.items(): diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index 9c00ab0c0db1..bda52d9884bd 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -27,6 +27,26 @@ def test_dispatch_to_unrecognize_library(): xp_assert_close(res, ref, xp=xp) +@pytest.mark.parametrize('dtype', ['float32', 'float64', 'int64']) +@pytest.mark.skipif(not HAVE_ARRAY_API_STRICT, + reason="`array_api_strict` not installed") +def test_rel_entr_generic(dtype): + xp = array_api_strict + f = get_array_special_func('rel_entr', xp=xp, n_array_args=2) + dtype_np = getattr(np, dtype) + dtype_xp = getattr(xp, dtype) + x, y = [-1, 0, 0, 1], [1, 0, 2, 3] + + x_xp, y_xp = xp.asarray(x, dtype=dtype_xp), xp.asarray(y, dtype=dtype_xp) + res = f(x_xp, y_xp) + + x_np, y_np = np.asarray(x, dtype=dtype_np), np.asarray(y, dtype=dtype_np) + ref = special.rel_entr(x_np[-1], y_np[-1]) + ref = np.asarray([np.inf, 0, 0, ref], dtype=ref.dtype) + + xp_assert_close(res, xp.asarray(ref), xp=xp) + + @array_api_compatible @given(data=strategies.data()) @pytest.mark.parametrize('f_name_n_args', array_special_func_map.items()) diff --git a/scipy/stats/_entropy.py b/scipy/stats/_entropy.py index f4a81b05749f..1e6c8d9e1963 100644 --- a/scipy/stats/_entropy.py +++ b/scipy/stats/_entropy.py @@ -9,6 +9,7 @@ import numpy as np from scipy import special from ._axis_nan_policy import _axis_nan_policy_factory, _broadcast_arrays +from scipy._lib._array_api import array_namespace __all__ = ['entropy', 'differential_entropy'] @@ -139,20 +140,22 @@ def entropy(pk: np.typing.ArrayLike, if base is not None and base <= 0: raise ValueError("`base` must be a positive number or `None`.") - pk = np.asarray(pk) + xp = array_namespace(pk) if qk is None else array_namespace(pk, qk) + + pk = xp.asarray(pk) with np.errstate(invalid='ignore'): - pk = 1.0*pk / np.sum(pk, axis=axis, keepdims=True) + pk = 1.0*pk / xp.sum(pk, axis=axis, keepdims=True) # type: ignore[operator] if qk is None: vec = special.entr(pk) else: - qk = np.asarray(qk) - pk, qk = _broadcast_arrays((pk, qk), axis=None) # don't ignore any axes + qk = xp.asarray(qk) + pk, qk = _broadcast_arrays((pk, qk), axis=None, xp=xp) # don't ignore any axes sum_kwargs = dict(axis=axis, keepdims=True) - qk = 1.0*qk / np.sum(qk, **sum_kwargs) # type: ignore[operator, call-overload] + qk = 1.0*qk / xp.sum(qk, **sum_kwargs) # type: ignore[operator, call-overload] vec = special.rel_entr(pk, qk) - S = np.sum(vec, axis=axis) + S = xp.sum(vec, axis=axis) if base is not None: - S /= np.log(base) + S /= math.log(base) return S diff --git a/scipy/stats/tests/test_entropy.py b/scipy/stats/tests/test_entropy.py index a25da83952b7..4f72fa06f674 100644 --- a/scipy/stats/tests/test_entropy.py +++ b/scipy/stats/tests/test_entropy.py @@ -1,111 +1,129 @@ -import numpy as np -from numpy.testing import assert_equal, assert_allclose -# avoid new uses of the following; prefer assert/np.testing.assert_allclose -from numpy.testing import (assert_, assert_almost_equal, - assert_array_almost_equal) - +import math import pytest from pytest import raises as assert_raises -import scipy.stats as stats +import numpy as np +from numpy.testing import assert_allclose + +from scipy import stats +from scipy.conftest import array_api_compatible +from scipy._lib._array_api import xp_assert_close, xp_assert_equal, xp_assert_less class TestEntropy: - def test_entropy_positive(self): + @array_api_compatible + def test_entropy_positive(self, xp): # See ticket #497 - pk = [0.5, 0.2, 0.3] - qk = [0.1, 0.25, 0.65] + 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) - assert_(0.0 == eself) - assert_(edouble >= 0.0) + xp_assert_equal(eself, xp.asarray(0.)) + xp_assert_less(-edouble, xp.asarray(0.)) - def test_entropy_base(self): - pk = np.ones(16, float) + @array_api_compatible + def test_entropy_base(self, xp): + pk = xp.ones(16) S = stats.entropy(pk, base=2.) - assert_(abs(S - 4.) < 1.e-5) + xp_assert_less(xp.abs(S - 4.), xp.asarray(1.e-5)) - qk = np.ones(16, float) - qk[:8] = 2. + qk = xp.ones(16) + qk = xp.where(xp.arange(16) < 8, xp.asarray(2.), qk) S = stats.entropy(pk, qk) S2 = stats.entropy(pk, qk, base=2.) - assert_(abs(S/S2 - np.log(2.)) < 1.e-5) + xp_assert_less(xp.abs(S/S2 - math.log(2.)), xp.asarray(1.e-5)) - def test_entropy_zero(self): + @array_api_compatible + def test_entropy_zero(self, xp): # Test for PR-479 - assert_almost_equal(stats.entropy([0, 1, 2]), 0.63651416829481278, - decimal=12) - - def test_entropy_2d(self): - pk = [[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]] - qk = [[0.2, 0.1], [0.3, 0.6], [0.5, 0.3]] - assert_array_almost_equal(stats.entropy(pk, qk), - [0.1933259, 0.18609809]) - - def test_entropy_2d_zero(self): - pk = [[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]] - qk = [[0.0, 0.1], [0.3, 0.6], [0.5, 0.3]] - assert_array_almost_equal(stats.entropy(pk, qk), - [np.inf, 0.18609809]) - - pk[0][0] = 0.0 - assert_array_almost_equal(stats.entropy(pk, qk), - [0.17403988, 0.18609809]) - - def test_entropy_base_2d_nondefault_axis(self): - pk = [[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]] - assert_array_almost_equal(stats.entropy(pk, axis=1), - [0.63651417, 0.63651417, 0.66156324]) - - def test_entropy_2d_nondefault_axis(self): - pk = [[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]] - qk = [[0.2, 0.1], [0.3, 0.6], [0.5, 0.3]] - assert_array_almost_equal(stats.entropy(pk, qk, axis=1), - [0.231049, 0.231049, 0.127706]) - - def test_entropy_raises_value_error(self): - pk = [[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]] - qk = [[0.1, 0.2], [0.6, 0.3]] - assert_raises(ValueError, stats.entropy, pk, qk) - - def test_base_entropy_with_axis_0_is_equal_to_default(self): - pk = [[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]] - assert_array_almost_equal(stats.entropy(pk, axis=0), - stats.entropy(pk)) - - def test_entropy_with_axis_0_is_equal_to_default(self): - pk = [[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]] - qk = [[0.2, 0.1], [0.3, 0.6], [0.5, 0.3]] - assert_array_almost_equal(stats.entropy(pk, qk, axis=0), - stats.entropy(pk, qk)) - - def test_base_entropy_transposed(self): - pk = np.array([[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]) - assert_array_almost_equal(stats.entropy(pk.T).T, - stats.entropy(pk, axis=1)) - - def test_entropy_transposed(self): - pk = np.array([[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]) - qk = np.array([[0.2, 0.1], [0.3, 0.6], [0.5, 0.3]]) - assert_array_almost_equal(stats.entropy(pk.T, qk.T).T, - stats.entropy(pk, qk, axis=1)) - - def test_entropy_broadcasting(self): - np.random.rand(0) - x = np.random.rand(3) - y = np.random.rand(2, 1) + x = xp.asarray([0., 1., 2.]) + xp_assert_close(stats.entropy(x), + xp.asarray(0.63651416829481278)) + + @array_api_compatible + 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])) + + @array_api_compatible + 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])) + + @array_api_compatible + 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])) + + @array_api_compatible + 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])) + + @array_api_compatible + 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) + + @array_api_compatible + 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)) + + @array_api_compatible + 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)) + + @array_api_compatible + 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)) + + @array_api_compatible + 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)) + + @array_api_compatible + 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) - assert_equal(res[0], stats.entropy(x, y[0])) - assert_equal(res[1], stats.entropy(x, y[1])) + xp_assert_equal(res[0], stats.entropy(x, y[0, ...])) + xp_assert_equal(res[1], stats.entropy(x, y[1, ...])) - def test_entropy_shape_mismatch(self): - x = np.random.rand(10, 1, 12) - y = np.random.rand(11, 2) + @array_api_compatible + 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): - x = np.random.rand(10) + @array_api_compatible + 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) From 59f477dd1866593c229ec007c8d8c5a632768a75 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 14 May 2024 14:32:48 -0400 Subject: [PATCH 139/500] ENH: stats.describe: add array API support (#20667) * ENH: stats.describe: add array API support * TST: stats.describe: uncomment tests * MAINT: stats.describe: remove dead code; work around Tensor 'size' in _contains_nan --- scipy/_lib/_util.py | 4 +- scipy/stats/_stats_py.py | 13 ++- scipy/stats/tests/test_stats.py | 175 ++++++++++++++++++++------------ 3 files changed, 121 insertions(+), 71 deletions(-) diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py index 9457477e89bf..d964f6fdba1a 100644 --- a/scipy/_lib/_util.py +++ b/scipy/_lib/_util.py @@ -15,7 +15,7 @@ ) import numpy as np -from scipy._lib._array_api import array_namespace, is_numpy +from scipy._lib._array_api import array_namespace, is_numpy, size as xp_size AxisError: type[Exception] @@ -719,7 +719,7 @@ def _contains_nan(a, nan_policy='propagate', policies=None, *, xp=None): inexact = (xp.isdtype(a.dtype, "real floating") or xp.isdtype(a.dtype, "complex floating")) - if a.size == 0: + if xp_size(a) == 0: contains_nan = False elif inexact: # Faster and less memory-intensive than xp.any(xp.isnan(a)) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 736e2263d000..0ea252ad9b96 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -1402,20 +1402,23 @@ def describe(a, axis=0, ddof=1, bias=True, nan_policy='propagate'): skewness=array([0., 0.]), kurtosis=array([-2., -2.])) """ - a, axis = _chk_asarray(a, axis) + xp = array_namespace(a) + a, axis = _chk_asarray(a, axis, xp=xp) contains_nan, nan_policy = _contains_nan(a, nan_policy) if contains_nan and nan_policy == 'omit': + # 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 a.size == 0: + if xp_size(a) == 0: raise ValueError("The input must not be empty.") + n = a.shape[axis] - mm = (np.min(a, axis=axis), np.max(a, axis=axis)) - m = np.mean(a, axis=axis) - v = _var(a, axis=axis, ddof=ddof) + mm = (xp.min(a, axis=axis), xp.max(a, axis=axis)) + m = xp.mean(a, axis=axis) + v = _var(a, axis=axis, ddof=ddof, xp=xp) sk = skew(a, axis, bias=bias) kurt = kurtosis(a, axis, bias=bias) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 6905e6adb6a2..0d22869cf357 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -5981,40 +5981,49 @@ def test_ttest_1samp_popmean_array(xp): class TestDescribe: - def test_describe_scalar(self): + @array_api_compatible + def test_describe_scalar(self, xp): with suppress_warnings() as sup, \ np.errstate(invalid="ignore", divide="ignore"): sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice") - n, mm, m, v, sk, kurt = stats.describe(4.) - assert_equal(n, 1) - assert_equal(mm, (4.0, 4.0)) - assert_equal(m, 4.0) - assert np.isnan(v) - assert np.isnan(sk) - assert np.isnan(kurt) - - def test_describe_numbers(self): - x = np.vstack((np.ones((3,4)), np.full((2, 4), 2))) - nc, mmc = (5, ([1., 1., 1., 1.], [2., 2., 2., 2.])) - mc = np.array([1.4, 1.4, 1.4, 1.4]) - vc = np.array([0.3, 0.3, 0.3, 0.3]) - skc = [0.40824829046386357] * 4 - kurtc = [-1.833333333333333] * 4 + 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)) + + @array_api_compatible + def test_describe_numbers(self, xp): + xp_test = array_namespace(xp.asarray(1.)) # numpy needs `concat` + x = xp_test.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_equal(n, nc) - assert_equal(mm, mmc) - assert_equal(m, mc) - assert_equal(v, vc) - assert_array_almost_equal(sk, skc, decimal=13) - assert_array_almost_equal(kurt, kurtc, decimal=13) - n, mm, m, v, sk, kurt = stats.describe(x.T, axis=1) - assert_equal(n, nc) - assert_equal(mm, mmc) - assert_equal(m, mc) - assert_equal(v, vc) - assert_array_almost_equal(sk, skc, decimal=13) - assert_array_almost_equal(kurt, kurtc, decimal=13) + assert n == nc + xp_assert_equal(mm[0], mmc[0]) + xp_assert_equal(mm[1], mmc[1]) + xp_assert_equal(m, mc) + xp_assert_equal(v, vc) + 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_equal(m, mc) + xp_assert_equal(v, vc) + 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 @@ -6031,52 +6040,90 @@ def test_describe_numbers(self): assert_array_almost_equal(sk, skc) assert_array_almost_equal(kurt, kurtc, decimal=13) - assert_raises(ValueError, stats.describe, x, nan_policy='raise') - assert_raises(ValueError, stats.describe, x, nan_policy='foobar') + @array_api_compatible + def test_describe_nan_policy_other(self, xp): + x = xp.arange(10.) + x = xp.where(x==9, xp.asarray(xp.nan), x) + + message = 'The input contains nan values' + with pytest.raises(ValueError, match=message): + 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() + else: + message = "`nan_policy='omit' is incompatible with non-NumPy arrays." + with pytest.raises(ValueError, match=message): + 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): - actual = stats.describe(np.arange(5)) - attributes = ('nobs', 'minmax', 'mean', 'variance', 'skewness', - 'kurtosis') + @array_api_compatible + def test_describe_result_attributes(self, xp): + actual = stats.describe(xp.arange(5.)) + attributes = ('nobs', 'minmax', 'mean', 'variance', 'skewness', 'kurtosis') check_named_results(actual, attributes) - def test_describe_ddof(self): - x = np.vstack((np.ones((3, 4)), np.full((2, 4), 2))) - nc, mmc = (5, ([1., 1., 1., 1.], [2., 2., 2., 2.])) - mc = np.array([1.4, 1.4, 1.4, 1.4]) - vc = np.array([0.24, 0.24, 0.24, 0.24]) - skc = [0.40824829046386357] * 4 - kurtc = [-1.833333333333333] * 4 + @array_api_compatible + def test_describe_ddof(self, xp): + xp_test = array_namespace(xp.asarray(1.)) # numpy needs `concat` + x = xp_test.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_equal(n, nc) - assert_allclose(mm, mmc, rtol=1e-15) - assert_allclose(m, mc, rtol=1e-15) - assert_allclose(v, vc, rtol=1e-15) - assert_array_almost_equal(sk, skc, decimal=13) - assert_array_almost_equal(kurt, kurtc, decimal=13) + 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): - x = np.vstack((np.ones((3, 4)), np.full((2, 4), 2))) + @array_api_compatible + def test_describe_axis_none(self, xp): + xp_test = array_namespace(xp.asarray(1.)) # numpy needs `concat` + x = xp_test.concat((xp.ones((3, 4)), xp.full((2, 4), 2.))) # expected values - e_nobs, e_minmax = (20, (1.0, 2.0)) - e_mean = 1.3999999999999999 - e_var = 0.25263157894736848 - e_skew = 0.4082482904638634 - e_kurt = -1.8333333333333333 + 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 - a = stats.describe(x, axis=None) + n, mm, m, v, sk, kurt = stats.describe(x, axis=None) - assert_equal(a.nobs, e_nobs) - assert_almost_equal(a.minmax, e_minmax) - assert_almost_equal(a.mean, e_mean) - assert_almost_equal(a.variance, e_var) - assert_array_almost_equal(a.skewness, e_skew, decimal=13) - assert_array_almost_equal(a.kurtosis, e_kurt, decimal=13) + 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): - assert_raises(ValueError, stats.describe, []) + @array_api_compatible + def test_describe_empty(self, xp): + message = "The input must not be empty." + with pytest.raises(ValueError, match=message): + stats.describe(xp.asarray([])) @pytest.mark.skip_xp_backends(cpu_only=True, From 471c3d120def0049bcbd93ad70b20cefab00075b Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Tue, 14 May 2024 14:00:50 -0600 Subject: [PATCH 140/500] Micro-optimizations for Rotation methods (#20656) Co-authored-by: Scott Shambaugh --- scipy/spatial/transform/_rotation.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/spatial/transform/_rotation.pyx b/scipy/spatial/transform/_rotation.pyx index 2c4a3b70df41..2a9bfc89fd8e 100644 --- a/scipy/spatial/transform/_rotation.pyx +++ b/scipy/spatial/transform/_rotation.pyx @@ -2699,9 +2699,9 @@ cdef class Rotation: return self.inv() elif n == 1: if self._single: - return self.__class__(self._quat[0], copy=True) + return self.__class__(self._quat[0], normalize=False, copy=True) else: - return self.__class__(self._quat, copy=True) + return self.__class__(self._quat, normalize=False, copy=True) else: # general scaling of rotation angle return Rotation.from_rotvec(n * self.as_rotvec()) @@ -2744,7 +2744,7 @@ cdef class Rotation: quat[:, 2] *= -1 if self._single: quat = quat[0] - return self.__class__(quat, copy=False) + return self.__class__(quat, normalize=False, copy=False) @cython.embedsignature(True) @cython.boundscheck(False) From 0f8796680a5e5572c71b5f56f30a46da1901b85e Mon Sep 17 00:00:00 2001 From: "Giuseppe \"Peppe\" Dilillo" Date: Tue, 14 May 2024 22:42:02 +0200 Subject: [PATCH 141/500] MAINT: numpy cleanup version bumps: fixes issue #20458 (#20711) * The minimum numpy version in `scipy/scipy/__init__.py` has been aligned to the minimum requirement of `pyproject.toml`. * A numpy version check has been removed from test `test_moments` in `scipy/stats/tests/test_multivariate.py` because the check looked for a numpy version earlier than the minimum. * After checking discussion to PR #20459, found that the pytest guards could be removed. So I did, and removed a couple imports and a function which would became unused too. Co-authored-by: jacobogle --- scipy/__init__.py | 2 +- scipy/conftest.py | 13 ++----------- scipy/sparse/tests/test_base.py | 14 ++++---------- scipy/stats/tests/test_multivariate.py | 5 ----- 4 files changed, 7 insertions(+), 27 deletions(-) diff --git a/scipy/__init__.py b/scipy/__init__.py index 462f52b739e3..e89bc85713e4 100644 --- a/scipy/__init__.py +++ b/scipy/__init__.py @@ -66,7 +66,7 @@ from scipy._lib import _pep440 # In maintenance branch, change to np_maxversion N+3 if numpy is at N -np_minversion = '1.22.4' +np_minversion = '1.23.5' np_maxversion = '9.9.99' if (_pep440.parse(__numpy_version__) < _pep440.Version(np_minversion) or _pep440.parse(__numpy_version__) >= _pep440.Version(np_maxversion)): diff --git a/scipy/conftest.py b/scipy/conftest.py index 3d44eb24db73..91b353f53c33 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -11,7 +11,6 @@ from scipy._lib._fpumode import get_fpu_mode from scipy._lib._testutils import FPUModeChangeWarning -from scipy._lib import _pep440 from scipy._lib._array_api import SCIPY_ARRAY_API, SCIPY_DEVICE @@ -39,16 +38,8 @@ def pytest_configure(config): "mark the desired skip configuration for the `skip_xp_backends` fixture.") -def _get_mark(item, name): - if _pep440.parse(pytest.__version__) >= _pep440.Version("3.6.0"): - mark = item.get_closest_marker(name) - else: - mark = item.get_marker(name) - return mark - - def pytest_runtest_setup(item): - mark = _get_mark(item, "xslow") + mark = item.get_closest_marker("xslow") if mark is not None: try: v = int(os.environ.get('SCIPY_XSLOW', '0')) @@ -57,7 +48,7 @@ def pytest_runtest_setup(item): if not v: pytest.skip("very slow test; " "set environment variable SCIPY_XSLOW=1 to run it") - mark = _get_mark(item, 'xfail_on_32bit') + mark = item.get_closest_marker("xfail_on_32bit") if mark is not None and np.intp(0).itemsize < 8: pytest.xfail(f'Fails on our 32-bit test platform(s): {mark.args[0]}') diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index da381f1c3eea..e80aa5a2225b 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -15,7 +15,6 @@ class for generic tests" section. import platform import itertools import sys -from scipy._lib import _pep440 import numpy as np from numpy import (arange, zeros, array, dot, asarray, @@ -5032,15 +5031,10 @@ def cases_64bit(): if bool(msg): marks += [pytest.mark.skip(reason=msg)] - if _pep440.parse(pytest.__version__) >= _pep440.Version("3.6.0"): - markers = getattr(method, 'pytestmark', []) - for mark in markers: - if mark.name in ('skipif', 'skip', 'xfail', 'xslow'): - marks.append(mark) - else: - for mname in ['skipif', 'skip', 'xfail', 'xslow']: - if hasattr(method, mname): - marks += [getattr(method, mname)] + 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) diff --git a/scipy/stats/tests/test_multivariate.py b/scipy/stats/tests/test_multivariate.py index 3777b2d5796a..6f49f0141b96 100644 --- a/scipy/stats/tests/test_multivariate.py +++ b/scipy/stats/tests/test_multivariate.py @@ -32,7 +32,6 @@ from scipy.integrate import romb, qmc_quad, tplquad from scipy.special import multigammaln -from scipy._lib._pep440 import Version from .common_tests import check_random_state_property from .data._mvt import _qsimvtv @@ -3775,10 +3774,6 @@ def test_against_betabinom_moments(self, method_name): assert_allclose(res, ref) def test_moments(self): - message = 'Needs NumPy 1.22.0 for multinomial broadcasting' - if Version(np.__version__) < Version("1.22.0"): - pytest.skip(reason=message) - rng = np.random.default_rng(28469824356873456) dim = 5 n = rng.integers(1, 100) From dddb3f20b09c96c9ab37343b5efb20485bac9288 Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Wed, 15 May 2024 00:18:31 -0400 Subject: [PATCH 142/500] MAINT: sparse: reformat str and repr for sparse arrays, correct 1D coords, improve dtype looks (#20649) * repr: improve 1D shape, dtype, wording * str: Fix for 1D, add repr as intro and "coords/values" column titles when nnz>0 * fix doc_strings and add d to make dtype * address review comments * add unittests of str and repr format * fix numpy 2 printing --- doc/source/tutorial/sparse.rst | 34 +++++++++---------- scipy/io/_fast_matrix_market/__init__.py | 4 +-- scipy/io/_harwell_boeing/hb.py | 6 ++++ scipy/sparse/_base.py | 24 +++++++------ scipy/sparse/_bsr.py | 7 ++-- scipy/sparse/_construct.py | 20 +++++------ scipy/sparse/_dia.py | 7 ++-- scipy/sparse/_extract.py | 8 ++--- scipy/sparse/_lil.py | 7 ---- scipy/sparse/_matrix_io.py | 16 ++++----- scipy/sparse/csgraph/_reordering.pyx | 6 ++++ scipy/sparse/csgraph/_shortest_path.pyx | 18 ++++++++++ scipy/sparse/csgraph/_tools.pyx | 22 +++++++----- scipy/sparse/csgraph/_traversal.pyx | 9 +++++ scipy/sparse/linalg/_matfuncs.py | 12 +++---- scipy/sparse/linalg/_special_sparse_arrays.py | 28 +++++++-------- scipy/sparse/tests/test_base.py | 32 +++++++++++++++-- scipy/stats/_crosstab.py | 4 +-- 18 files changed, 166 insertions(+), 98 deletions(-) diff --git a/doc/source/tutorial/sparse.rst b/doc/source/tutorial/sparse.rst index 756b12487f6d..7b4f24c7ab67 100644 --- a/doc/source/tutorial/sparse.rst +++ b/doc/source/tutorial/sparse.rst @@ -26,10 +26,10 @@ Sparse arrays are a special kind of array where only a few locations in the arra [0, 4, 1, 0], [0, 0, 5, 0]]) >>> sparse - <3x4 sparse array of type '' - with 5 stored elements in COOrdinate format> + -Note that in our dense array, we have five nonzero values. For example, ``2`` is at location ``0,3``, and ``4`` is at location ``1,1``. All of the other values are zero. The sparse array records these five values *explicitly* (see the ``5 stored elements in COOrdinate format``), and then represents all of the remaining zeros as *implicit* values. +Note that in our dense array, we have five nonzero values. For example, ``2`` is at location ``0,3``, and ``4`` is at location ``1,1``. All of the other values are zero. The sparse array records these five values *explicitly* (see the ``5 stored elements and shape (3, 4)``), and then represents all of the remaining zeros as *implicit* values. Most sparse array methods work in a similar fashion to dense array methods: @@ -78,8 +78,8 @@ But, other formats, such as the Compressed Sparse Row (CSR) :func:`csr_array()` Sometimes, `scipy.sparse` will return a different sparse matrix format than the input sparse matrix format. For example, the dot product of two sparse arrays in COO format will be a CSR format array: >>> sparse @ sparse.T - <3x3 sparse array of type '' - with 5 stored elements in Compressed Sparse Row format> + This change occurs because `scipy.sparse` will change the format of input sparse arrays in order to use the most efficient computational method. @@ -112,8 +112,8 @@ Using these, we can now define a sparse array without building a dense array fir >>> csr = sp.sparse.csr_array((data, (row, col))) >>> csr - <3x4 sparse array of type '' - with 5 stored elements in Compressed Sparse Row format> + Different classes have different constructors, but the :func:`scipy.sparse.csr_array`, :func:`scipy.sparse.csc_array`, and :func:`scipy.sparse.coo_array` allow for this style of construction. @@ -132,8 +132,8 @@ Then, our sparse array will have *six* stored elements, not five: >>> csr = sp.sparse.csr_array((data, (row, col))) >>> csr - <3x4 sparse array of type '' - with 6 stored elements in Compressed Sparse Row format> + The "extra" element is our *explicit zero*. The two are still identical when converted back into a dense array, because dense arrays represent *everything* explicitly: @@ -149,12 +149,12 @@ The "extra" element is our *explicit zero*. The two are still identical when con But, for sparse arithmetic, linear algebra, and graph methods, the value at ``2,3`` will be considered an *explicit zero*. To remove this explicit zero, we can use the ``csr.eliminate_zeros()`` method. This operates on the sparse array *in place*, and removes any zero-value stored elements: >>> csr - <3x4 sparse array of type '' - with 6 stored elements in Compressed Sparse Row format> + >>> csr.eliminate_zeros() >>> csr - <3x4 sparse array of type '' - with 5 stored elements in Compressed Sparse Row format> + Before ``csr.eliminate_zeros()``, there were six stored elements. After, there are only five stored elements. @@ -168,8 +168,8 @@ In this case, we can see that there are *two* ``data`` values that correspond to >>> dupes = sp.sparse.coo_array((data, (row, col))) >>> dupes - <3x4 sparse array of type '' - with 6 stored elements in COOrdinate format> + Note that there are six stored elements in this sparse array, despite only having five unique locations where data occurs. When these arrays are converted back to dense arrays, the duplicate values are summed. So, at location ``1,1``, the dense array will contain the sum of duplicate stored entries, ``1 + 3``: @@ -182,8 +182,8 @@ To remove duplicate values within the sparse array itself and thus reduce the nu >>> dupes.sum_duplicates() >>> dupes - <3x4 sparse array of type '' - with 5 stored elements in COOrdinate format> + Now there are only five stored elements in our sparse array, and it is identical to the array we have been working with throughout this guide: diff --git a/scipy/io/_fast_matrix_market/__init__.py b/scipy/io/_fast_matrix_market/__init__.py index 5233b1cb027f..182179d2e75c 100644 --- a/scipy/io/_fast_matrix_market/__init__.py +++ b/scipy/io/_fast_matrix_market/__init__.py @@ -332,8 +332,8 @@ def mmread(source): >>> m = mmread(StringIO(text)) >>> m - <5x5 sparse matrix of type '' - with 7 stored elements in COOrdinate format> + >>> m.toarray() array([[0., 0., 0., 0., 0.], [0., 0., 1., 0., 0.], diff --git a/scipy/io/_harwell_boeing/hb.py b/scipy/io/_harwell_boeing/hb.py index 210982bee2ff..9e88f28c881f 100644 --- a/scipy/io/_harwell_boeing/hb.py +++ b/scipy/io/_harwell_boeing/hb.py @@ -499,6 +499,9 @@ def hb_read(path_or_open_file): >>> data = csr_matrix(eye(3)) # create a sparse matrix >>> hb_write("data.hb", data) # write a hb file >>> print(hb_read("data.hb")) # read a hb file + + Coords Values (0, 0) 1.0 (1, 1) 1.0 (2, 2) 1.0 @@ -550,6 +553,9 @@ def hb_write(path_or_open_file, m, hb_info=None): >>> data = csr_matrix(eye(3)) # create a sparse matrix >>> hb_write("data.hb", data) # write a hb file >>> print(hb_read("data.hb")) # read a hb file + + Coords Values (0, 0) 1.0 (1, 1) 1.0 (2, 2) 1.0 diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 1a297ff498ab..510f7565065d 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -332,10 +332,9 @@ def imag(self): def __repr__(self): _, format_name = _formats[self.format] sparse_cls = 'array' if isinstance(self, sparray) else 'matrix' - shape_str = 'x'.join(str(x) for x in self.shape) return ( - f"<{shape_str} sparse {sparse_cls} of type '{self.dtype.type}'\n" - f"\twith {self.nnz} stored elements in {format_name} format>" + f"<{format_name} sparse {sparse_cls} of dtype '{self.dtype}'\n" + f"\twith {self.nnz} stored elements and shape {self.shape}>" ) def __str__(self): @@ -344,18 +343,23 @@ def __str__(self): A = self.tocoo() # helper function, outputs "(i,j) v" - def tostr(row, col, data): - triples = zip(list(zip(row, col)), data) - return '\n'.join([(' {}\t{}'.format(*t)) for t in triples]) + 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(A.row[:half], A.col[:half], A.data[:half]) + out += tostr(tuple(c[:half] for c in A.coords), A.data[:half]) out += "\n :\t:\n" - half = maxprint - maxprint//2 - out += tostr(A.row[-half:], A.col[-half:], A.data[-half:]) + half = maxprint - half + out += tostr(tuple(c[-half:] for c in A.coords), A.data[-half:]) else: - out = tostr(A.row, A.col, A.data) + out += tostr(A.coords, A.data) return out diff --git a/scipy/sparse/_bsr.py b/scipy/sparse/_bsr.py index 0d3fe31f0419..6a8a1be7dabe 100644 --- a/scipy/sparse/_bsr.py +++ b/scipy/sparse/_bsr.py @@ -222,11 +222,10 @@ def _getnnz(self, axis=None): def __repr__(self): _, fmt = _formats[self.format] sparse_cls = 'array' if isinstance(self, sparray) else 'matrix' - shape_str = 'x'.join(str(x) for x in self.shape) - blksz = 'x'.join(str(x) for x in self.blocksize) + b = 'x'.join(str(x) for x in self.blocksize) return ( - f"<{shape_str} sparse {sparse_cls} of type '{self.dtype.type}'\n" - f"\twith {self.nnz} stored elements (blocksize = {blksz}) in {fmt} format>" + 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): diff --git a/scipy/sparse/_construct.py b/scipy/sparse/_construct.py index 6f5d3dd514d2..3ceec66bda14 100644 --- a/scipy/sparse/_construct.py +++ b/scipy/sparse/_construct.py @@ -312,11 +312,11 @@ def identity(n, dtype='d', format=None): [ 0., 1., 0.], [ 0., 0., 1.]]) >>> sp.sparse.identity(3, dtype='int8', format='dia') - <3x3 sparse matrix of type '' - with 3 stored elements (1 diagonals) in DIAgonal format> + >>> sp.sparse.eye_array(3, dtype='int8', format='dia') - <3x3 sparse array of type '' - with 3 stored elements (1 diagonals) in DIAgonal format> + """ return eye(n, n, dtype=dtype, format=format) @@ -351,8 +351,8 @@ def eye_array(m, n=None, *, k=0, dtype=float, format=None): [ 0., 1., 0.], [ 0., 0., 1.]]) >>> sp.sparse.eye_array(3, dtype=np.int8) - <3x3 sparse array of type '' - with 3 stored elements (1 diagonals) in DIAgonal format> + """ # TODO: delete next 15 lines [combine with _eye()] once spmatrix removed @@ -430,8 +430,8 @@ def eye(m, n=None, k=0, dtype=float, format=None): [ 0., 1., 0.], [ 0., 0., 1.]]) >>> sp.sparse.eye(3, dtype=np.int8) - <3x3 sparse matrix of type '' - with 3 stored elements (1 diagonals) in DIAgonal format> + """ return _eye(m, n, k, dtype, format, False) @@ -1390,8 +1390,8 @@ def rand(m, n, density=0.01, format="coo", dtype=None, random_state=None): >>> from scipy.sparse import rand >>> matrix = rand(3, 4, density=0.25, format="csr", random_state=42) >>> matrix - <3x4 sparse matrix of type '' - with 3 stored elements in Compressed Sparse Row format> + >>> matrix.toarray() array([[0.05641158, 0. , 0. , 0.65088847], # random [0. , 0. , 0. , 0.14286682], diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index 086f9bf6d617..73b55908466d 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -98,11 +98,10 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): def __repr__(self): _, fmt = _formats[self.format] sparse_cls = 'array' if isinstance(self, sparray) else 'matrix' - shape_str = 'x'.join(str(x) for x in self.shape) - ndiag = self.data.shape[0] + d = self.data.shape[0] return ( - f"<{shape_str} sparse {sparse_cls} of type '{self.dtype.type}'\n" - f"\twith {self.nnz} stored elements ({ndiag} diagonals) in {fmt} format>" + 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): diff --git a/scipy/sparse/_extract.py b/scipy/sparse/_extract.py index 349a056bd2f6..052ef506f9ab 100644 --- a/scipy/sparse/_extract.py +++ b/scipy/sparse/_extract.py @@ -93,8 +93,8 @@ def tril(A, k=0, format=None): [4, 0, 0, 0, 0], [0, 0, 0, 0, 0]]) >>> tril(A, format='csc') - <3x5 sparse array of type '' - with 4 stored elements in Compressed Sparse Column format> + """ coo_sparse = coo_array if isinstance(A, sparray) else coo_matrix @@ -161,8 +161,8 @@ def triu(A, k=0, format=None): [4, 5, 0, 6, 7], [0, 0, 8, 9, 0]]) >>> triu(A, format='csc') - <3x5 sparse array of type '' - with 8 stored elements in Compressed Sparse Column format> + """ coo_sparse = coo_array if isinstance(A, sparray) else coo_matrix diff --git a/scipy/sparse/_lil.py b/scipy/sparse/_lil.py index 4b9057d53b5b..2503aa628b58 100644 --- a/scipy/sparse/_lil.py +++ b/scipy/sparse/_lil.py @@ -112,13 +112,6 @@ def count_nonzero(self): _getnnz.__doc__ = _spbase._getnnz.__doc__ count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ - def __str__(self): - val = '' - for i, row in enumerate(self.rows): - for pos, j in enumerate(row): - val += f" {str((i, j))}\t{str(self.data[i][pos])}\n" - return val[:-1] - def getrowview(self, i): """Returns a view of the 'i'th row (without copying). """ diff --git a/scipy/sparse/_matrix_io.py b/scipy/sparse/_matrix_io.py index e115260afb9f..5b7f533926fd 100644 --- a/scipy/sparse/_matrix_io.py +++ b/scipy/sparse/_matrix_io.py @@ -38,8 +38,8 @@ def save_npz(file, matrix, compressed=True): >>> import scipy as sp >>> sparse_matrix = sp.sparse.csc_matrix([[0, 0, 3], [4, 0, 0]]) >>> sparse_matrix - <2x3 sparse matrix of type '' - with 2 stored elements in Compressed Sparse Column format> + >>> sparse_matrix.toarray() array([[0, 0, 3], [4, 0, 0]], dtype=int64) @@ -48,8 +48,8 @@ def save_npz(file, matrix, compressed=True): >>> sparse_matrix = sp.sparse.load_npz('/tmp/sparse_matrix.npz') >>> sparse_matrix - <2x3 sparse matrix of type '' - with 2 stored elements in Compressed Sparse Column format> + >>> sparse_matrix.toarray() array([[0, 0, 3], [4, 0, 0]], dtype=int64) @@ -109,8 +109,8 @@ def load_npz(file): >>> import scipy as sp >>> sparse_array = sp.sparse.csc_array([[0, 0, 3], [4, 0, 0]]) >>> sparse_array - <2x3 sparse array of type '' - with 2 stored elements in Compressed Sparse Column format> + >>> sparse_array.toarray() array([[0, 0, 3], [4, 0, 0]], dtype=int64) @@ -119,8 +119,8 @@ def load_npz(file): >>> sparse_array = sp.sparse.load_npz('/tmp/sparse_array.npz') >>> sparse_array - <2x3 sparse array of type '' - with 2 stored elements in Compressed Sparse Column format> + >>> sparse_array.toarray() array([[0, 0, 3], [4, 0, 0]], dtype=int64) diff --git a/scipy/sparse/csgraph/_reordering.pyx b/scipy/sparse/csgraph/_reordering.pyx index d902749faf7c..db57d9a4d16c 100644 --- a/scipy/sparse/csgraph/_reordering.pyx +++ b/scipy/sparse/csgraph/_reordering.pyx @@ -59,6 +59,9 @@ def reverse_cuthill_mckee(graph, symmetric_mode=False): ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 3) 1 @@ -215,6 +218,9 @@ def structural_rank(graph): ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 0) 1 diff --git a/scipy/sparse/csgraph/_shortest_path.pyx b/scipy/sparse/csgraph/_shortest_path.pyx index 4cfa2ef6f064..d8d90f816fc8 100644 --- a/scipy/sparse/csgraph/_shortest_path.pyx +++ b/scipy/sparse/csgraph/_shortest_path.pyx @@ -148,6 +148,9 @@ def shortest_path(csgraph, method='auto', ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 3) 1 @@ -294,6 +297,9 @@ def floyd_warshall(csgraph, directed=True, ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 3) 1 @@ -519,6 +525,9 @@ def dijkstra(csgraph, directed=True, indices=None, ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 3) 1 @@ -1004,6 +1013,9 @@ def bellman_ford(csgraph, directed=True, indices=None, ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 3) 1 @@ -1241,6 +1253,9 @@ def johnson(csgraph, directed=True, indices=None, ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 3) 1 @@ -1771,6 +1786,9 @@ def yen( ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 3) 1 diff --git a/scipy/sparse/csgraph/_tools.pyx b/scipy/sparse/csgraph/_tools.pyx index 46276c45c75c..a1f804864437 100644 --- a/scipy/sparse/csgraph/_tools.pyx +++ b/scipy/sparse/csgraph/_tools.pyx @@ -51,8 +51,8 @@ def csgraph_from_masked(graph): ... fill_value = 0) >>> csgraph_from_masked(graph_masked) - <4x4 sparse matrix of type '' - with 4 stored elements in Compressed Sparse Row format> + """ # check that graph is a square matrix @@ -209,8 +209,8 @@ def csgraph_from_dense(graph, ... ] >>> csgraph_from_dense(graph) - <4x4 sparse matrix of type '' - with 4 stored elements in Compressed Sparse Row format> + """ return csgraph_from_masked(csgraph_masked_from_dense(graph, @@ -303,8 +303,8 @@ def csgraph_to_dense(csgraph, null_value=0): ... [0, 0, 0, 0] ... ]) >>> graph - <4x4 sparse matrix of type '' - with 4 stored elements in Compressed Sparse Row format> + >>> csgraph_to_dense(graph) array([[0., 1., 2., 0.], @@ -367,8 +367,8 @@ def csgraph_to_masked(csgraph): ... [0, 0, 0, 0] ... ]) >>> graph - <4x4 sparse matrix of type '' - with 4 stored elements in Compressed Sparse Row format> + >>> csgraph_to_masked(graph) masked_array( @@ -453,6 +453,9 @@ def reconstruct_path(csgraph, predecessors, directed=True): ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 3) 1 @@ -562,6 +565,9 @@ def construct_dist_matrix(graph, ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 3) 1 diff --git a/scipy/sparse/csgraph/_traversal.pyx b/scipy/sparse/csgraph/_traversal.pyx index 2a85a3a09027..149559b7e447 100644 --- a/scipy/sparse/csgraph/_traversal.pyx +++ b/scipy/sparse/csgraph/_traversal.pyx @@ -75,6 +75,9 @@ def connected_components(csgraph, directed=True, connection='weak', ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 1 (1, 2) 1 @@ -332,6 +335,9 @@ cpdef breadth_first_order(csgraph, i_start, ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 3) 1 @@ -538,6 +544,9 @@ cpdef depth_first_order(csgraph, i_start, ... ] >>> graph = csr_matrix(graph) >>> print(graph) + + Coords Values (0, 1) 1 (0, 2) 2 (1, 3) 1 diff --git a/scipy/sparse/linalg/_matfuncs.py b/scipy/sparse/linalg/_matfuncs.py index 525c97b2cca5..1c531e25b406 100644 --- a/scipy/sparse/linalg/_matfuncs.py +++ b/scipy/sparse/linalg/_matfuncs.py @@ -55,11 +55,11 @@ def inv(A): >>> A = csc_matrix([[1., 0.], [1., 2.]]) >>> Ainv = inv(A) >>> Ainv - <2x2 sparse matrix of type '' - with 3 stored elements in Compressed Sparse Column format> + >>> A.dot(Ainv) - <2x2 sparse matrix of type '' - with 2 stored elements in Compressed Sparse Column format> + >>> A.dot(Ainv).toarray() array([[ 1., 0.], [ 0., 1.]]) @@ -581,8 +581,8 @@ def expm(A): [0, 0, 3]], dtype=int64) >>> Aexp = expm(A) >>> Aexp - <3x3 sparse matrix of type '' - with 3 stored elements in Compressed Sparse Column format> + >>> Aexp.toarray() array([[ 2.71828183, 0. , 0. ], [ 0. , 7.3890561 , 0. ], diff --git a/scipy/sparse/linalg/_special_sparse_arrays.py b/scipy/sparse/linalg/_special_sparse_arrays.py index d6e35f6c7e47..ee68fce865c5 100644 --- a/scipy/sparse/linalg/_special_sparse_arrays.py +++ b/scipy/sparse/linalg/_special_sparse_arrays.py @@ -92,8 +92,8 @@ class LaplacianNd(LinearOperator): the default dtype for storing matrix representations. >>> lap.tosparse() - <6x6 sparse array of type '' - with 16 stored elements (3 diagonals) in DIAgonal format> + >>> lap.toarray() array([[-1, 1, 0, 0, 0, 0], [ 1, -2, 1, 0, 0, 0], @@ -165,8 +165,8 @@ class LaplacianNd(LinearOperator): >>> lap = LaplacianNd(grid_shape, boundary_conditions='dirichlet') >>> lap.tosparse() - <6x6 sparse array of type '' - with 20 stored elements in Compressed Sparse Row format> + >>> lap.toarray() array([[-4, 1, 0, 1, 0, 0], [ 1, -4, 1, 0, 1, 0], @@ -192,8 +192,8 @@ class LaplacianNd(LinearOperator): >>> lap = LaplacianNd(grid_shape, boundary_conditions='periodic') >>> lap.tosparse() - <6x6 sparse array of type '' - with 24 stored elements in Compressed Sparse Row format> + >>> lap.toarray() array([[-4, 1, 1, 2, 0, 0], [ 1, -4, 1, 0, 2, 0], @@ -218,8 +218,8 @@ class LaplacianNd(LinearOperator): >>> lap = LaplacianNd(grid_shape, boundary_conditions='neumann') >>> lap.tosparse() - <6x6 sparse array of type '' - with 20 stored elements in Compressed Sparse Row format> + >>> lap.toarray() array([[-2, 1, 0, 1, 0, 0], [ 1, -3, 1, 0, 1, 0], @@ -589,8 +589,8 @@ class Sakurai(LinearOperator): [-4, -4, -4, -4, -4, -4], [ 5, 6, 6, 6, 6, 5]], dtype=int8) >>> sak.tosparse() - <6x6 sparse matrix of type '' - with 24 stored elements (5 diagonals) in DIAgonal format> + >>> np.array_equal(sak.dot(np.eye(n)), sak.tosparse().toarray()) True >>> sak.eigenvalues() @@ -906,11 +906,11 @@ class MikotaPair: array([1. , 0.5 , 0.33333333, 0.25 , 0.2 , 0.16666667]) >>> mik_k.tosparse() - <6x6 sparse matrix of type '' - with 16 stored elements (3 diagonals) in DIAgonal format> + >>> mik_m.tosparse() - <6x6 sparse matrix of type '' - with 6 stored elements (1 diagonals) in DIAgonal format> + >>> np.array_equal(mik_k(np.eye(n)), mik_k.toarray()) True >>> np.array_equal(mik_m(np.eye(n)), mik_m.toarray()) diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index e80aa5a2225b..f778c97e012c 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -33,6 +33,7 @@ class for generic tests" section. from scipy.sparse import (csc_matrix, csr_matrix, dok_matrix, coo_matrix, lil_matrix, dia_matrix, bsr_matrix, eye, issparse, SparseEfficiencyWarning, sparray) +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 @@ -644,10 +645,37 @@ def test_invalid_shapes(self): assert_raises(ValueError, self.spcreator, (-1,-1)) def test_repr(self): - repr(self.datsp) + 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] + expected = ( + f"<{fmt} sparse matrix of dtype '{datsp.dtype}'\n" + f"\twith {datsp.nnz} stored elements {extra}and shape {datsp.shape}>" + ) + assert repr(datsp) == expected def test_str(self): - str(self.datsp) + 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] + expected = ( + f"<{fmt} sparse matrix 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 diff --git a/scipy/stats/_crosstab.py b/scipy/stats/_crosstab.py index b6d0c95ac85f..6c267ff85eaf 100644 --- a/scipy/stats/_crosstab.py +++ b/scipy/stats/_crosstab.py @@ -145,8 +145,8 @@ def crosstab(*args, levels=None, sparse=False): >>> res = crosstab(a, x, sparse=True) >>> res.count - <2x3 sparse matrix of type '' - with 4 stored elements in COOrdinate format> + >>> res.count.toarray() array([[2, 3, 0], [1, 0, 4]]) From 8ce393f1172f2a5eac8ff50a57a683635ba2a955 Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Wed, 15 May 2024 00:21:13 -0400 Subject: [PATCH 143/500] BUG: sparse: Fix summing duplicates for CSR/CSC creation from (data,coords) (#20687) * test and then fix duplicates for CSR/CSC creation from (data,coords) * remove has_canonical_format check when summing duplicates. --- scipy/sparse/_compressed.py | 1 + scipy/sparse/tests/test_base.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/scipy/sparse/_compressed.py b/scipy/sparse/_compressed.py index fda5e2eb1596..a86c09fc5edf 100644 --- a/scipy/sparse/_compressed.py +++ b/scipy/sparse/_compressed.py @@ -55,6 +55,7 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): 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 diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index f778c97e012c..3415c70be2be 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -3829,6 +3829,10 @@ def test_constructor4(self): dense = array([[2**63 + 1, 0], [0, 1]], dtype=np.uint64) assert_array_equal(dense, csr.toarray()) + # with duplicates (should sum the duplicates) + csr = csr_matrix(([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]) @@ -4069,6 +4073,10 @@ def test_constructor4(self): csc = csc_matrix((data,ij),(4,3)) assert_array_equal(arange(12).reshape(4, 3), csc.toarray()) + # with duplicates (should sum the duplicates) + csc = csc_matrix(([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]) @@ -4483,6 +4491,13 @@ def test_todok_duplicates(self): dok = coo.todok() assert_array_equal(dok.toarray(), coo.toarray()) + def test_tocompressed_duplicates(self): + coo = coo_matrix(([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_eliminate_zeros(self): data = array([1, 0, 0, 0, 2, 0, 3, 0]) row = array([0, 0, 0, 1, 1, 1, 1, 1]) From 2f605bbb1470df7a2fb7661258c2d6c5c802ea66 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 15 May 2024 01:15:53 -0400 Subject: [PATCH 144/500] ENH: stats.kurtosistest: add array API support --- scipy/stats/_stats_py.py | 32 +++++++++------ scipy/stats/tests/test_stats.py | 70 ++++++++++++++++++++++----------- 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 0ea252ad9b96..2c9215c4c689 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -68,7 +68,7 @@ from scipy.optimize import root_scalar from scipy._lib._util import normalize_axis_index from scipy._lib._array_api import (array_namespace, is_numpy, atleast_nd, - xp_clip, xp_moveaxis_to_end) + xp_clip, xp_moveaxis_to_end, xp_sign) from scipy._lib.array_api_compat import size as xp_size # In __all__ but deprecated for removal in SciPy 1.13.0 @@ -1805,6 +1805,9 @@ def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): hypothesis [4]_. """ + xp = array_namespace(a) + a, axis = _chk_asarray(a, axis, xp=xp) + n = a.shape[axis] if n < 5: raise ValueError( @@ -1818,25 +1821,30 @@ def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): 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) / np.sqrt(varb2) # [1]_ Eq. 4 + x = (b2-E) / varb2**0.5 # [1]_ Eq. 4 # [1]_ Eq. 2: - 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))) + 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 + np.sqrt(1+4.0/(sqrtbeta1**2))) + 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*np.sqrt(2/(A-4.0)) - term2 = np.sign(denom) * np.where(denom == 0.0, np.nan, - np.power((1-2.0/A)/np.abs(denom), 1/3.0)) - if np.any(denom == 0): + denom = 1 + x * (2/(A-4.0))**0.5 + NaN = _get_nan(x, xp=xp) + term2 = xp_sign(denom) * xp.where(denom == 0.0, NaN, + ((1-2.0/A)/xp.abs(denom))**(1/3)) + if 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) / np.sqrt(2/(9.0*A)) # [1]_ Eq. 5 + Z = (term1 - term2) / (2/(9.0*A))**0.5 # [1]_ Eq. 5 - pvalue = _get_pvalue(Z, distributions.norm, alternative) - return KurtosistestResult(Z[()], pvalue[()]) + Z_np = np.asarray(Z) + pvalue = _get_pvalue(Z_np, distributions.norm, alternative) + pvalue = xp.asarray(pvalue, dtype=Z.dtype) + 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')) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 0d22869cf357..719c1a5513e8 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6143,10 +6143,12 @@ def test_normalitytests(xp): # test_result <- dagoTest(x) # test_result@test$statistic # test_result@test$p.value - st_normal, st_skew, st_kurt = (3.92371918, xp.asarray(1.98078826090875881), - -0.01403734) - pv_normal, pv_skew, pv_kurt = (0.14059673, xp.asarray(0.04761502382843208), - 0.98880019) + st_normal, st_skew, st_kurt = (3.92371918158185551, + xp.asarray(1.98078826090875881), + xp.asarray(-0.01403734404759738)) + pv_normal, pv_skew, pv_kurt = (0.14059672529747502, + xp.asarray(0.04761502382843208), + xp.asarray(0.98880018772590561)) pv_skew_less, pv_kurt_less = 1 - pv_skew / 2, pv_kurt / 2 pv_skew_greater, pv_kurt_greater = pv_skew / 2, 1 - pv_kurt / 2 x = np.array((-2, -1, 0, 1, 2, 3.)*4)**2 @@ -6155,6 +6157,7 @@ def test_normalitytests(xp): assert_array_almost_equal(stats.normaltest(x), (st_normal, pv_normal)) check_named_results(stats.normaltest(x), attributes) + res = stats.skewtest(x_xp) xp_assert_close(res.statistic, st_skew) xp_assert_close(res.pvalue, pv_skew) @@ -6165,11 +6168,16 @@ def test_normalitytests(xp): xp_assert_close(res.statistic, st_skew) xp_assert_close(res.pvalue, pv_skew_greater) check_named_results(stats.skewtest(x), attributes) - assert_array_almost_equal(stats.kurtosistest(x), (st_kurt, pv_kurt)) - assert_array_almost_equal(stats.kurtosistest(x, alternative='less'), - (st_kurt, pv_kurt_less)) - assert_array_almost_equal(stats.kurtosistest(x, alternative='greater'), - (st_kurt, pv_kurt_greater)) + + res = stats.kurtosistest(x_xp) + xp_assert_close(res.statistic, st_kurt) + xp_assert_close(res.pvalue, pv_kurt) + res = stats.kurtosistest(x_xp, alternative='less') + xp_assert_close(res.statistic, st_kurt) + xp_assert_close(res.pvalue, pv_kurt_less) + res = stats.kurtosistest(x_xp, alternative='greater') + xp_assert_close(res.statistic, st_kurt) + xp_assert_close(res.pvalue, pv_kurt_greater) check_named_results(stats.kurtosistest(x), attributes) # some more intuitive tests for kurtosistest and skewtest. @@ -6179,10 +6187,12 @@ def test_normalitytests(xp): 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) + # excess kurtosis of laplace is 3 > 0 a2 = stats.laplace.rvs(size=10000, random_state=123) - pval = stats.kurtosistest(a2, alternative='greater').pvalue - assert_almost_equal(pval, 0.0) + 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) # Test axis=None (equal to axis=0 for 1-D input) assert_array_almost_equal(stats.normaltest(x, axis=None), @@ -6190,16 +6200,29 @@ def test_normalitytests(xp): res = stats.skewtest(x_xp, axis=None) xp_assert_close(res.statistic, st_skew) xp_assert_close(res.pvalue, pv_skew) - assert_array_almost_equal(stats.kurtosistest(x, axis=None), - (st_kurt, pv_kurt)) + + res = stats.kurtosistest(x_xp, axis=None) + xp_assert_close(res.statistic, st_kurt) + xp_assert_close(res.pvalue, pv_kurt) x = xp.arange(10.) - x[9] = xp.nan + NaN = xp.asarray(xp.nan, dtype=x.dtype) + x[9] = NaN with np.errstate(invalid="ignore"): - assert_array_equal(stats.skewtest(x), (xp.nan, xp.nan)) + res = stats.skewtest(x) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + + x = xp.arange(30.) + x[29] = NaN + with np.errstate(all='ignore'): + res = stats.kurtosistest(x) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) # nan_policy only compatible with NumPy arrays - x = np.asarray(x) + x = np.arange(10.) + x[9] = np.nan expected = (1.0184643553962129, 0.30845733195153502) assert_array_almost_equal(stats.skewtest(x, nan_policy='omit'), expected) @@ -6221,9 +6244,8 @@ def test_normalitytests(xp): x = np.arange(30.) x[29] = np.nan - with np.errstate(all='ignore'): - assert_array_equal(stats.kurtosistest(x), (np.nan, np.nan)) + # nan_policy only compatible with NumPy arrays expected = (-2.2683547379505273, 0.023307594135872967) assert_array_almost_equal(stats.kurtosistest(x, nan_policy='omit'), expected) @@ -6258,7 +6280,8 @@ def test_normalitytests(xp): # 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(stats.kurtosistest(x)[1] < 0.01, True) + x = xp.asarray(x, dtype=xp.float64) + assert stats.kurtosistest(x)[1] < 0.01 class TestRankSums: @@ -6347,11 +6370,14 @@ def test_skewtest_too_few_samples(): assert_raises(ValueError, stats.skewtest, x) -def test_kurtosistest_too_few_samples(): +@array_api_compatible +def test_kurtosistest_too_few_samples(xp): # Regression test for ticket #1425. # kurtosistest requires at least 5 samples; 4 should raise a ValueError. - x = np.arange(4.0) - assert_raises(ValueError, stats.kurtosistest, x) + x = xp.arange(4.0) + message = 'kurtosistest requires at least 5 observations...' + with pytest.raises(ValueError, match=message): + stats.kurtosistest(x) class TestMannWhitneyU: From 5d98109d9196c857af8a8c632bbc3cceaa10d309 Mon Sep 17 00:00:00 2001 From: Sheila-nk Date: Mon, 19 Feb 2024 12:01:24 +0300 Subject: [PATCH 145/500] ENH: doctests: add conftest.py filters for pytest based doctesting --- scipy/conftest.py | 104 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/scipy/conftest.py b/scipy/conftest.py index 91b353f53c33..768c4b3f0f95 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -238,3 +238,107 @@ def skip_xp_backends(xp, request): SCIPY_HYPOTHESIS_PROFILE = os.environ.get("SCIPY_HYPOTHESIS_PROFILE", "deterministic") hypothesis.settings.load_profile(SCIPY_HYPOTHESIS_PROFILE) + + +############################################################################ +# doctesting stuff + +from scpdt.conftest import dt_config +from contextlib import contextmanager +import warnings + + +# FIXME: populate the dict once +@contextmanager +def warnings_errors_and_rng(test): + """Temporarily turn (almost) all warnings to errors. + + Filter out known warnings which we allow. + """ + known_warnings = dict() + + # these functions are known to emit "divide by zero" RuntimeWarnings + divide_by_zero = [ + 'scipy.linalg.norm', 'scipy.ndimage.center_of_mass', + ] + for name in divide_by_zero: + known_warnings[name] = dict(category=RuntimeWarning, + message='divide by zero') + + # Deprecated stuff in scipy.signal and elsewhere + deprecated = [ + 'scipy.signal.cwt', 'scipy.signal.morlet', 'scipy.signal.morlet2', + 'scipy.signal.ricker', + 'scipy.integrate.simpson', + 'scipy.interpolate.interp2d', + ] + for name in deprecated: + known_warnings[name] = dict(category=DeprecationWarning) + + from scipy import integrate + # the funcions are known to emit IntergrationWarnings + integration_w = ['scipy.special.ellip_normal', + 'scipy.special.ellip_harm_2', + ] + for name in integration_w: + known_warnings[name] = dict(category=integrate.IntegrationWarning, + message='The occurrence of roundoff') + + # scipy.stats deliberately emits UserWarnings sometimes + user_w = ['scipy.stats.anderson_ksamp', 'scipy.stats.kurtosistest', + 'scipy.stats.normaltest'] + for name in user_w: + known_warnings[name] = dict(category=UserWarning) + + # additional one-off warnings to filter + dct = { + 'scipy.sparse.linalg.norm': + dict(category=UserWarning, message="Exited at iteration"), + # tutorials + 'linalg.rst': + dict(message='the matrix subclass is not', + category=PendingDeprecationWarning), + 'stats.rst': + dict(message='The maximum number of subdivisions', + category=integrate.IntegrationWarning), + } + known_warnings.update(dct) + + # these legitimately emit warnings in examples + from scipy.signal._filter_design import BadCoefficients + legit = set('scipy.signal.normalize') + + # Now, the meat of the matter: filter warnings, + # also control the random seed for each doctest. + + # XXX: this matches the refguide-check behavior, but is a tad strange: + # makes sure that the seed the old-fashioned np.random* methods is *NOT* + # reproducible but the new-style `default_rng()` *IS* repoducible. + # Should these two be either both repro or both not repro? + + from scipy._lib._util import _fixed_default_rng + import numpy as np + with _fixed_default_rng(): + np.random.seed(None) + with warnings.catch_warnings(): + if test.name in known_warnings: + warnings.filterwarnings('ignore', **known_warnings[test.name]) + yield + elif test.name in legit: + yield + else: + warnings.simplefilter('error', Warning) + yield + + +dt_config.user_context_mgr = warnings_errors_and_rng +dt_config.skiplist = set([ + 'scipy.linalg.LinAlgError', # comes from numpy + 'scipy.fftpack.fftshift', # fftpack stuff is also from numpy + 'scipy.fftpack.ifftshift', + 'scipy.fftpack.fftfreq', + 'scipy.special.sinc', # sinc is from numpy + 'scipy.optimize.show_options', # does not have much to doctest + 'scipy.signal.normalize', # manipulates warnings (XXX temp skip) +]) +############################################################################ From cfbab5bae8b682cbadc02d28acf15fcd089099d8 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 17 Feb 2024 15:08:50 +0300 Subject: [PATCH 146/500] MAINT: stats.qmc: add __all__ for the doctester --- scipy/stats/qmc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/stats/qmc.py b/scipy/stats/qmc.py index 4f3c8182857b..48e56471ec46 100644 --- a/scipy/stats/qmc.py +++ b/scipy/stats/qmc.py @@ -233,3 +233,4 @@ """ from ._qmc import * # noqa: F403 +from ._qmc import __all__ # noqa: F403 From fa867b0b622fb6bba530eb6fcfaacfcb49232ec5 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 17 Feb 2024 15:13:46 +0300 Subject: [PATCH 147/500] MAINT: stats.sampling: add __all__ list for the doctester --- scipy/stats/sampling.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scipy/stats/sampling.py b/scipy/stats/sampling.py index a699221ba77b..12174d9dfb3c 100644 --- a/scipy/stats/sampling.py +++ b/scipy/stats/sampling.py @@ -66,3 +66,8 @@ SimpleRatioUniforms, UNURANError ) + +__all__ = ["NumericalInverseHermite", "NumericalInversePolynomial", + "TransformedDensityRejection", "SimpleRatioUniforms", + "RatioUniforms", "DiscreteAliasUrn", "DiscreteGuideTable", + "UNURANError", "FastGeneratorInversion"] From 9c175d183699e885d84651442dabde4874305f1b Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Mon, 19 Feb 2024 18:48:30 +0300 Subject: [PATCH 148/500] MAINT: doctest: add pytest_extra_skips --- scipy/conftest.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scipy/conftest.py b/scipy/conftest.py index 768c4b3f0f95..cb628d25e224 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -341,4 +341,14 @@ def warnings_errors_and_rng(test): 'scipy.optimize.show_options', # does not have much to doctest 'scipy.signal.normalize', # manipulates warnings (XXX temp skip) ]) + +# help pytest collection a bit: these names are either private (distributions) +# or deprecated (misc), or just do not need doctesting +dt_config.pytest_extra_skips = [ + "scipy.stats.distributions", + "scipy.optimize.cython_optimize", + "scipy.test", + "scipy.show_config", + "scipy.misc", +] ############################################################################ From d87d46908bf38bd026e15967043378e2c0702b78 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 17 Feb 2024 13:12:34 +0300 Subject: [PATCH 149/500] DOC: optimize: fix errors in milp and basinhopping docstrings --- scipy/optimize/_basinhopping.py | 5 +++-- scipy/optimize/_milp.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scipy/optimize/_basinhopping.py b/scipy/optimize/_basinhopping.py index d874a708b9a2..d201df179834 100644 --- a/scipy/optimize/_basinhopping.py +++ b/scipy/optimize/_basinhopping.py @@ -580,8 +580,9 @@ def basinhopping(func, x0, niter=100, T=1.0, stepsize=0.5, >>> minimizer_kwargs = {"method": "BFGS"} >>> ret = basinhopping(func, x0, minimizer_kwargs=minimizer_kwargs, ... niter=200) - >>> print("global minimum: x = %.4f, f(x) = %.4f" % (ret.x, ret.fun)) - global minimum: x = -0.1951, f(x) = -1.0009 + >>> # the global minimum is: + >>> ret.x, ret.fun + -0.1951, -1.0009 Next consider a 2-D minimization problem. Also, this time, we will use gradient information to significantly speed up the search. diff --git a/scipy/optimize/_milp.py b/scipy/optimize/_milp.py index c1d74499d5b5..ce50bd237b6d 100644 --- a/scipy/optimize/_milp.py +++ b/scipy/optimize/_milp.py @@ -322,7 +322,7 @@ def milp(c, *, integrality=None, bounds=None, constraints=None, options=None): >>> A = np.array([[-1, 1], [3, 2], [2, 3]]) >>> b_u = np.array([1, 12, 12]) - >>> b_l = np.full_like(b_u, -np.inf) + >>> b_l = np.full_like(b_u, -np.inf, dtype=float) Because there is no lower limit on these constraints, we have defined a variable ``b_l`` full of values representing negative infinity. This may @@ -350,7 +350,7 @@ def milp(c, *, integrality=None, bounds=None, constraints=None, options=None): >>> from scipy.optimize import milp >>> res = milp(c=c, constraints=constraints, integrality=integrality) >>> res.x - [1.0, 2.0] + [2.0, 2.0] Note that had we solved the relaxed problem (without integrality constraints): From f63608b633bc9192da49ca0e88fa8b8c91d4a4a3 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 17 Feb 2024 13:45:13 +0300 Subject: [PATCH 150/500] DOC: sparse: fix the module docstring example --- scipy/sparse/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scipy/sparse/__init__.py b/scipy/sparse/__init__.py index ec3dd05726c9..b710a22325cd 100644 --- a/scipy/sparse/__init__.py +++ b/scipy/sparse/__init__.py @@ -230,7 +230,6 @@ >>> A = lil_array((1000, 1000)) >>> A[0, :100] = rand(100) ->>> A[1, 100:200] = A[0, :100] >>> A.setdiag(rand(1000)) Now convert it to CSR format and solve A x = b for x: From dee57590d19cbd386b7b547997d52d65993d4219 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 17 Feb 2024 13:53:29 +0300 Subject: [PATCH 151/500] MAINT: sparse.linalg: fix docstring examples (warnings, deprecations) --- scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py | 2 +- scipy/sparse/linalg/_isolve/tfqmr.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py b/scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py index 2e4cfc155675..6bf2a77106ab 100644 --- a/scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py +++ b/scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py @@ -400,7 +400,7 @@ def lobpcg( and ``largest=False`` parameter - >>> eigenvalues, _ = lobpcg(A, X, largest=False, maxiter=80) + >>> eigenvalues, _ = lobpcg(A, X, largest=False, maxiter=90) >>> print(eigenvalues) [1. 2. 3.] diff --git a/scipy/sparse/linalg/_isolve/tfqmr.py b/scipy/sparse/linalg/_isolve/tfqmr.py index 19af0400215e..2966dc7bcc81 100644 --- a/scipy/sparse/linalg/_isolve/tfqmr.py +++ b/scipy/sparse/linalg/_isolve/tfqmr.py @@ -82,7 +82,7 @@ def tfqmr(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M=None, >>> from scipy.sparse.linalg import tfqmr >>> A = csc_matrix([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float) >>> b = np.array([2, 4, -1], dtype=float) - >>> x, exitCode = tfqmr(A, b) + >>> x, exitCode = tfqmr(A, b, atol=0.0) >>> print(exitCode) # 0 indicates successful convergence 0 >>> np.allclose(A.dot(x), b) From a8ccd2e58d6bde8c1df8c8b38cf60b377993a0e3 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 17 Feb 2024 13:56:02 +0300 Subject: [PATCH 152/500] DOC: sparse.csgraph: bump lobpcg tolerance to 1e-2 --- scipy/sparse/csgraph/_laplacian.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/sparse/csgraph/_laplacian.py b/scipy/sparse/csgraph/_laplacian.py index 9c3d1f7972d7..8a50cd491069 100644 --- a/scipy/sparse/csgraph/_laplacian.py +++ b/scipy/sparse/csgraph/_laplacian.py @@ -315,7 +315,7 @@ def laplacian( >>> 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-3) # 3. + ... _, 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: From f4b8d8260e99ae12676feea65f336f25d577a131 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Tue, 20 Feb 2024 14:04:22 +0300 Subject: [PATCH 153/500] MAINT: doctests: allow sparse.linalg.norm emit UserWarnings This is `lopbcg` having convergence issues, again. --- scipy/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/conftest.py b/scipy/conftest.py index cb628d25e224..59246bcaaba4 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -286,7 +286,7 @@ def warnings_errors_and_rng(test): # scipy.stats deliberately emits UserWarnings sometimes user_w = ['scipy.stats.anderson_ksamp', 'scipy.stats.kurtosistest', - 'scipy.stats.normaltest'] + 'scipy.stats.normaltest', 'scipy.sparse.linalg.norm'] for name in user_w: known_warnings[name] = dict(category=UserWarning) From e53c4a0d399158d50600a9512b285f0437642f4f Mon Sep 17 00:00:00 2001 From: Sheila-nk Date: Wed, 21 Feb 2024 17:20:42 +0300 Subject: [PATCH 154/500] DEV: add test --doctests option to dev.py --- dev.py | 7 ++++--- scipy/_lib/_testutils.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dev.py b/dev.py index 1809979e48d9..005275fb0af5 100644 --- a/dev.py +++ b/dev.py @@ -670,8 +670,9 @@ class Test(Task): verbose = Option( ['--verbose', '-v'], default=False, is_flag=True, help="more verbosity") - # removed doctests as currently not supported by _lib/_testutils.py - # doctests = Option(['--doctests'], default=False) + doctests = Option(['--doctests'], default=False, is_flag=True, + help="Run doctests" + ) coverage = Option( ['--coverage', '-c'], default=False, is_flag=True, help=("report coverage of project code. " @@ -755,7 +756,7 @@ def scipy_tests(cls, args, pytest_args): args.mode, verbose=verbose, extra_argv=extra_argv, - doctests=False, + doctests=args.doctests, coverage=args.coverage, tests=tests, parallel=args.parallel) diff --git a/scipy/_lib/_testutils.py b/scipy/_lib/_testutils.py index aac70d79c0f2..852522d38b63 100644 --- a/scipy/_lib/_testutils.py +++ b/scipy/_lib/_testutils.py @@ -95,7 +95,7 @@ def __call__(self, label="fast", verbose=1, extra_argv=None, doctests=False, pytest_args = ['--showlocals', '--tb=short'] if doctests: - raise ValueError("Doctests not supported") + pytest_args += ["--doctest-modules", "--ignore=scipy/interpolate/_interpnd_info.py", "--ignore=scipy/_lib/"] if extra_argv: pytest_args += list(extra_argv) From 0c48551711981381746214410cfefcb94338bc98 Mon Sep 17 00:00:00 2001 From: Sheila-nk Date: Wed, 21 Feb 2024 17:22:18 +0300 Subject: [PATCH 155/500] MAINT: more fine-grained ignores in dev.py test --doctests Co-authored-by: Lucas Colley --- scipy/_lib/_testutils.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scipy/_lib/_testutils.py b/scipy/_lib/_testutils.py index 852522d38b63..4a830edcdf0c 100644 --- a/scipy/_lib/_testutils.py +++ b/scipy/_lib/_testutils.py @@ -95,7 +95,16 @@ def __call__(self, label="fast", verbose=1, extra_argv=None, doctests=False, pytest_args = ['--showlocals', '--tb=short'] if doctests: - pytest_args += ["--doctest-modules", "--ignore=scipy/interpolate/_interpnd_info.py", "--ignore=scipy/_lib/"] + pytest_args += [ + "--doctest-modules", + "--ignore=scipy/interpolate/_interpnd_info.py", + "--ignore=scipy/_lib/array_api_compat", + "--ignore=scipy/_lib/highs", + "--ignore=scipy/_lib/unuran", + "--ignore=scipy/_lib/_gcutils.py", + "--ignore=scipy/_lib/doccer.py", + "--ignore=scipy/_lib/_uarray", + ] if extra_argv: pytest_args += list(extra_argv) From d2174ea8cbfa3ba4b77f5fe446e4e537a4c61ea3 Mon Sep 17 00:00:00 2001 From: Sheila-nk Date: Wed, 21 Feb 2024 17:29:48 +0300 Subject: [PATCH 156/500] CI: run doctests via plugin on CircleCI [docs only] --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index de874866e2e1..22e1bec8b9b3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -178,6 +178,9 @@ jobs: sudo apt-get install -y wamerican-small export PYTHONPATH=$PWD/build-install/lib/python3.11/site-packages python dev.py --no-build refguide-check + pip install matplotlib hypothesis + pip install git+https://github.com/ev-br/scpdt.git@main + python dev.py test --doctests # Upload build output to scipy/devdocs repository, using SSH deploy keys. # The keys are only available for builds on main branch. From e7bf290107bcfaa876f817237e62d60e3079f623 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 21 Feb 2024 19:19:55 +0300 Subject: [PATCH 157/500] CI: doctest: skip scipy.sparse.linalg.norm [docs only] --- scipy/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/conftest.py b/scipy/conftest.py index 59246bcaaba4..b9c5f302ec36 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -340,6 +340,7 @@ def warnings_errors_and_rng(test): 'scipy.special.sinc', # sinc is from numpy 'scipy.optimize.show_options', # does not have much to doctest 'scipy.signal.normalize', # manipulates warnings (XXX temp skip) + 'scipy.sparse.linalg.norm', # XXX temp skip ]) # help pytest collection a bit: these names are either private (distributions) From 7b40542888628a96793b3a7391807ed6cc22575c Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Tue, 27 Feb 2024 13:13:37 +0300 Subject: [PATCH 158/500] BUG: smoke-docs: fix dev.py test --doctest -t path/to/file plumb through the doctest collection `strategy`: - dev.py test --doctests : uses strategy=api - dev.py -s submodule : uses strategy=api - dev.py -t scipy/linalg._basic.py : uses strategy=None This sorta makes sense: if you want to smoke-test a package or a subpackage, only look at the public API --- this is not changed. If you smoke-test an _individual file_, you want functions in that file, so the vanilla doctest finder gives you all of them. IOW, given a single implementation file, you have no way to tell which functions from this file end up in the scipy public API. A possible UX glitch is that `dev.py -t` may pick up extra docstrings (from things which remain private in SciPy). It may thus report some extra failures, compared to `dev.py -s submodule/which/has/the/file` --- if the docstrings of these private objects are not perfect. Two options: bend over backwards to invent a way to figure out if an object from a private _module ends up in the public API, or simply document it and move on. I suspect the former will be hacky and unreliable, so am opting for the latter. Yet another way around it is to specify an individual object: ``` $ python dev.py test --doctests -t scipy/linalg/_basic.py::scipy.linalg._basic.det ``` note the need to specify the full dotted path. --- dev.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dev.py b/dev.py index 005275fb0af5..668305340899 100644 --- a/dev.py +++ b/dev.py @@ -742,6 +742,9 @@ def scipy_tests(cls, args, pytest_args): else: tests = None + if args.doctests and not args.tests: + extra_argv += ['--doctest-collect=api'] + if len(args.array_api_backend) != 0: os.environ['SCIPY_ARRAY_API'] = json.dumps(list(args.array_api_backend)) From f80fbaafc845990597404e46d4b5131c688d1c72 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 28 Feb 2024 16:07:08 +0300 Subject: [PATCH 159/500] ENH: docs: add a conftest $ pytest doc/source/tutorial --doctest-glob=*rst --ignore=doc/source/tutorial/examples 1. Add a conftest to scipy/doc/source/tutorials: otherwise the doctesting customization is not found --- it lives under scipy/scipy/conftest.py. 2. We also need to entirely skip the contents of doc/source/tutorial/examples: these files are included into *rst tutorials, doctest them with the rest of *rst. --- doc/source/tutorial/conftest.py | 1 + scipy/conftest.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 doc/source/tutorial/conftest.py diff --git a/doc/source/tutorial/conftest.py b/doc/source/tutorial/conftest.py new file mode 100644 index 000000000000..7a6f439b5fd4 --- /dev/null +++ b/doc/source/tutorial/conftest.py @@ -0,0 +1 @@ +from scipy.conftest import dt_config diff --git a/scipy/conftest.py b/scipy/conftest.py index b9c5f302ec36..e847c5eb1892 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -352,4 +352,5 @@ def warnings_errors_and_rng(test): "scipy.show_config", "scipy.misc", ] + ############################################################################ From 3af1464cb8bcc0e03124c736dc060be6b4e66a3e Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 17 Feb 2024 17:12:44 +0300 Subject: [PATCH 160/500] ENH: doctest: recurse into tutorial subdirs; fix easy erorrs, skip the rest --- doc/source/tutorial/interpolate/1D.rst | 2 +- .../tutorial/interpolate/ND_regular_grid.rst | 5 +++-- .../tutorial/interpolate/ND_unstructured.rst | 5 +++-- .../interpolate/splines_and_polynomials.rst | 3 ++- doc/source/tutorial/stats/continuous_kstwo.rst | 17 +++++++++-------- doc/source/tutorial/stats/resampling.rst | 8 ++++---- doc/source/tutorial/stats/sampling.rst | 8 +++++--- doc/source/tutorial/stats/sampling_dau.rst | 8 ++++---- doc/source/tutorial/stats/sampling_dgt.rst | 9 +++++---- doc/source/tutorial/stats/sampling_hinv.rst | 2 ++ doc/source/tutorial/stats/sampling_pinv.rst | 7 ++++--- doc/source/tutorial/stats/sampling_srou.rst | 3 ++- doc/source/tutorial/stats/sampling_tdr.rst | 1 + 13 files changed, 45 insertions(+), 33 deletions(-) diff --git a/doc/source/tutorial/interpolate/1D.rst b/doc/source/tutorial/interpolate/1D.rst index 804736eb13b6..f30e755775bc 100644 --- a/doc/source/tutorial/interpolate/1D.rst +++ b/doc/source/tutorial/interpolate/1D.rst @@ -135,7 +135,7 @@ B-splines form an alternative (if formally equivalent) representation of piecewi >>> xx = np.linspace(0, 3/2, 51) >>> plt.plot(xx, bspl(xx), '--', label=r'$\sin(\pi x)$ approx') >>> plt.plot(x, y, 'o', label='data') - >>> plt.plot(xx, der(xx)/np.pi, '--', label='$d \sin(\pi x)/dx / \pi$ approx') + >>> plt.plot(xx, der(xx)/np.pi, '--', label=r'$d \sin(\pi x)/dx / \pi$ approx') >>> plt.legend() >>> plt.show() diff --git a/doc/source/tutorial/interpolate/ND_regular_grid.rst b/doc/source/tutorial/interpolate/ND_regular_grid.rst index 57b56d0c9a6f..c98e0e1a836a 100644 --- a/doc/source/tutorial/interpolate/ND_regular_grid.rst +++ b/doc/source/tutorial/interpolate/ND_regular_grid.rst @@ -20,6 +20,7 @@ using each method. .. plot:: + >>> import numpy as np >>> import matplotlib.pyplot as plt >>> from scipy.interpolate import RegularGridInterpolator @@ -90,8 +91,8 @@ controlled by the ``fill_value`` keyword parameter: >>> data = np.array([[0], [5], [10]]) >>> rgi = RegularGridInterpolator((x, y), data, ... bounds_error=False, fill_value=None) - >>> rgi([(2, 0), (2, 1), (2, -1)]) - array([2., 2., 2.])) # extrapolate the value on the axis + >>> rgi([(2, 0), (2, 1), (2, -1)]) # extrapolates the value on the axis + array([2., 2., 2.])) >>> rgi.fill_value = -101 >>> rgi([(2, 0), (2, 1), (2, -1)]) array([2., -101., -101.])) diff --git a/doc/source/tutorial/interpolate/ND_unstructured.rst b/doc/source/tutorial/interpolate/ND_unstructured.rst index 3d86e715cab5..a85d91cf8cdd 100644 --- a/doc/source/tutorial/interpolate/ND_unstructured.rst +++ b/doc/source/tutorial/interpolate/ND_unstructured.rst @@ -15,6 +15,7 @@ that do not form a regular grid. Suppose we want to interpolate the 2-D function + >>> import numpy as np >>> def func(x, y): ... return x*(1-x)*np.cos(4*np.pi*x) * np.sin(4*np.pi*y**2)**2 @@ -114,7 +115,7 @@ classes from the `scipy.interpolate` module. >>> ius = InterpolatedUnivariateSpline(x, y) >>> yi = ius(xi) - >>> plt.subplot(2, 1, 1) + >>> plt.subplot(211) >>> plt.plot(x, y, 'bo') >>> plt.plot(xi, yi, 'g') >>> plt.plot(xi, np.sin(xi), 'r') @@ -124,7 +125,7 @@ classes from the `scipy.interpolate` module. >>> rbf = RBFInterpolator(x, y) >>> fi = rbf(xi) - >>> plt.subplot(2, 1, 2) + >>> plt.subplot(212) >>> plt.plot(x, y, 'bo') >>> plt.plot(xi, fi, 'g') >>> plt.plot(xi, np.sin(xi), 'r') diff --git a/doc/source/tutorial/interpolate/splines_and_polynomials.rst b/doc/source/tutorial/interpolate/splines_and_polynomials.rst index 6f21c5acc41b..f5318b4df2fc 100644 --- a/doc/source/tutorial/interpolate/splines_and_polynomials.rst +++ b/doc/source/tutorial/interpolate/splines_and_polynomials.rst @@ -51,6 +51,7 @@ Manipulating `PPoly` objects and antiderivatives, computing integrals and root-finding. For example, we tabulate the sine function and find the roots of its derivative. + >>> import numpy as np >>> from scipy.interpolate import CubicSpline >>> x = np.linspace(0, 10, 71) >>> y = np.sin(x) @@ -109,7 +110,7 @@ PCHIP interpolant (we could as well used a `CubicSpline`): >>> from scipy.interpolate import PchipInterpolator >>> x = np.linspace(0, np.pi/2, 70) - >>> y = (1 - m*np.sin(x)**2))**(-1/2) + >>> y = (1 - m*np.sin(x)**2)**(-1/2) >>> spl = PchipInterpolator(x, y) and integrate diff --git a/doc/source/tutorial/stats/continuous_kstwo.rst b/doc/source/tutorial/stats/continuous_kstwo.rst index 927be1360109..0660587ea450 100644 --- a/doc/source/tutorial/stats/continuous_kstwo.rst +++ b/doc/source/tutorial/stats/continuous_kstwo.rst @@ -35,6 +35,7 @@ with asymptotic estimates of Li-Chien, Pelz and Good to compute the CDF with 5-1 Examples -------- +>>> import numpy as np >>> from scipy.stats import kstwo Show the probability of a gap at least as big as 0, 0.5 and 1.0 for a sample of size 5 @@ -50,17 +51,17 @@ a target N(0, 1) CDF. >>> gendist = norm(0.5, 1) # Normal distribution, mean 0.5, stddev 1 >>> x = np.sort(gendist.rvs(size=n, random_state=np.random.default_rng())) >>> x -array([-1.59113056, -0.66335147, 0.54791569, 0.78009321, 1.27641365]) +array([-1.59113056, -0.66335147, 0.54791569, 0.78009321, 1.27641365]) # may vary >>> target = norm(0, 1) >>> cdfs = target.cdf(x) >>> cdfs -array([0.0557901 , 0.25355274, 0.7081251 , 0.78233199, 0.89909533]) -# Construct the Empirical CDF and the K-S statistics (Dn+, Dn-, Dn) +array([0.0557901 , 0.25355274, 0.7081251 , 0.78233199, 0.89909533]) # may vary +>>> # Construct the Empirical CDF and the K-S statistics (Dn+, Dn-, Dn) >>> ecdfs = np.arange(n+1, dtype=float)/n >>> cols = np.column_stack([x, ecdfs[1:], cdfs, cdfs - ecdfs[:n], ecdfs[1:] - cdfs]) >>> np.set_printoptions(precision=3) >>> cols -array([[-1.591, 0.2 , 0.056, 0.056, 0.144], +array([[-1.591, 0.2 , 0.056, 0.056, 0.144], # may vary [-0.663, 0.4 , 0.254, 0.054, 0.146], [ 0.548, 0.6 , 0.708, 0.308, -0.108], [ 0.78 , 0.8 , 0.782, 0.182, 0.018], @@ -70,17 +71,17 @@ array([[-1.591, 0.2 , 0.056, 0.056, 0.144], >>> Dn = np.max(Dnpm) >>> iminus, iplus = np.argmax(gaps, axis=0) >>> print('Dn- = %f (at x=%.2f)' % (Dnpm[0], x[iminus])) -Dn- = 0.308125 (at x=0.55) +Dn- = 0.246201 (at x=-0.14) >>> print('Dn+ = %f (at x=%.2f)' % (Dnpm[1], x[iplus])) -Dn+ = 0.146447 (at x=-0.66) +Dn+ = 0.224726 (at x=0.19) >>> print('Dn = %f' % (Dn)) -Dn = 0.308125 +Dn = 0.246201 >>> probs = kstwo.sf(Dn, n) >>> print(chr(10).join(['For a sample of size %d drawn from a N(0, 1) distribution:' % n, ... ' Kolmogorov-Smirnov 2-sided n=%d: Prob(Dn >= %f) = %.4f' % (n, Dn, probs)])) For a sample of size 5 drawn from a N(0, 1) distribution: - Kolmogorov-Smirnov 2-sided n=5: Prob(Dn >= 0.308125) = 0.6319 + Kolmogorov-Smirnov 2-sided n=5: Prob(Dn >= 0.246201) = 0.8562 Plot the Empirical CDF against the target N(0, 1) CDF diff --git a/doc/source/tutorial/stats/resampling.rst b/doc/source/tutorial/stats/resampling.rst index d38e39743078..30954722c7db 100644 --- a/doc/source/tutorial/stats/resampling.rst +++ b/doc/source/tutorial/stats/resampling.rst @@ -48,8 +48,8 @@ Your brother Kyle is the analytical one. He answers: >>> std = math.sqrt(n*p*(1-p)) >>> # CDF of the normal distribution. (Unfortunately, Kyle forgets a continuity correction that would produce a more accurate answer.) >>> prob = 0.5 * (1 + math.erf((x - mean) / (std * math.sqrt(2)))) - >>> print(f"The normal approximation estimates the probability as {prob}") - The normal approximation estimates the probability as 0.15865525393145713 + >>> print(f"The normal approximation estimates the probability as {prob:.3f}") + The normal approximation estimates the probability as 0.159 You are a little more practical, so you decide to take a computational approach (or more precisely, a Monte Carlo approach): just simulate many @@ -63,8 +63,8 @@ count does not exceed 45. >>> simulation = rng.random(size=(n, N)) < p # False for tails, True for heads >>> counts = np.sum(simulation, axis=0) # count the number of heads each trial >>> prob = np.sum(counts <= x) / N # estimate the probability as the observed proportion of cases in which the count did not exceed 45 - >>> print(f"The Monte Carlo approach estimates the probability as {prob}") - The Monte Carlo approach estimates the probability as 0.18348 + >>> print(f"The Monte Carlo approach estimates the probability as {prob:.3f}") + The Monte Carlo approach estimates the probability as 0.187 The demon replies: diff --git a/doc/source/tutorial/stats/sampling.rst b/doc/source/tutorial/stats/sampling.rst index a4e1e7f145e0..1635a980d0e6 100644 --- a/doc/source/tutorial/stats/sampling.rst +++ b/doc/source/tutorial/stats/sampling.rst @@ -147,7 +147,8 @@ An example of this interface is shown below: ... return -x * exp(-0.5 * x*x) ... >>> dist = StandardNormal() - >>> + >>> + >>> import numpy as np >>> urng = np.random.default_rng() >>> rng = TransformedDensityRejection(dist, random_state=urng) @@ -238,7 +239,8 @@ by visualizing the histogram of the samples: :class:`~TransformedDensityRejection` would not be the same even for the same ``random_state``: - >>> from scipy.stats.sampling import norm, TransformedDensityRejection + >>> from scipy.stats import norm + >>> from scipy.stats.sampling import TransformedDensityRejection >>> from copy import copy >>> dist = StandardNormal() >>> urng1 = np.random.default_rng() @@ -253,7 +255,7 @@ We can pass a ``domain`` parameter to truncate the distribution: >>> rng = TransformedDensityRejection(dist, domain=(-1, 1), random_state=urng) >>> rng.rvs((5, 3)) - array([[-0.99865691, 0.38104014, 0.31633526], + array([[-0.99865691, 0.38104014, 0.31633526], # may vary [ 0.88433909, -0.45181849, 0.78574461], [ 0.3337244 , 0.12924307, 0.40499404], [-0.51865761, 0.43252222, -0.6514866 ], diff --git a/doc/source/tutorial/stats/sampling_dau.rst b/doc/source/tutorial/stats/sampling_dau.rst index 354bc17b31fd..b233a7344011 100644 --- a/doc/source/tutorial/stats/sampling_dau.rst +++ b/doc/source/tutorial/stats/sampling_dau.rst @@ -26,7 +26,7 @@ constructing the tables is O(N). >>> urng = np.random.default_rng() >>> rng = DiscreteAliasUrn(pv, random_state=urng) >>> rng.rvs() - 0 + 0 # may vary By default, the probability vector is indexed starting at 0. However, this can be changed by passing a ``domain`` parameter. When ``domain`` is given @@ -36,7 +36,7 @@ distribution from ``(0, len(pv))`` to ``(domain[0]``, ``domain[0] + len(pv))``. >>> rng = DiscreteAliasUrn(pv, domain=(10, 13), random_state=urng) >>> rng.rvs() - 12 + 12 # may vary The method also works when no probability vector but a PMF is given. In that case, a bounded (finite) domain must also be given either by @@ -54,7 +54,7 @@ method in the distribution object: >>> dist = Distribution(2) >>> rng = DiscreteAliasUrn(dist, random_state=urng) >>> rng.rvs() - 10 + 10 # may vary .. plot:: :alt: " " @@ -114,7 +114,7 @@ table which can be changed by passing a ``urn_factor`` parameter. >>> urn_factor = 2 >>> rng = DiscreteAliasUrn(pv, urn_factor=urn_factor, random_state=urng) >>> rng.rvs() - 2 + 2 # may vary .. note:: It is recommended to keep this parameter under 2. diff --git a/doc/source/tutorial/stats/sampling_dgt.rst b/doc/source/tutorial/stats/sampling_dgt.rst index dc0853892c46..55adc576fa51 100644 --- a/doc/source/tutorial/stats/sampling_dgt.rst +++ b/doc/source/tutorial/stats/sampling_dgt.rst @@ -43,7 +43,7 @@ of the given probability vector can be set by the `guide_factor` parameter: >>> urng = np.random.default_rng() >>> rng = DiscreteGuideTable(pv, random_state=urng) >>> rng.rvs() - 2 + 2 # may vary By default, the probability vector is indexed starting at 0. However, this can be changed by passing a ``domain`` parameter. When ``domain`` is given @@ -53,7 +53,7 @@ distribution from ``(0, len(pv))`` to ``(domain[0], domain[0] + len(pv))``. >>> rng = DiscreteGuideTable(pv, random_state=urng, domain=(10, 13)) >>> rng.rvs() - 10 + 10 # may vary The method also works when no probability vector but a PMF is given. In that case, a bounded (finite) domain must also be given either by @@ -71,7 +71,7 @@ method in the distribution object: >>> dist = Distribution(2) >>> rng = DiscreteGuideTable(dist, random_state=urng) >>> rng.rvs() - 9 + 9 # may vary .. note:: As :class:`~DiscreteGuideTable` expects PMF with signature ``def pmf(self, x: float) -> float``, it first vectorizes the @@ -99,7 +99,7 @@ time but require a more expensive setup. >>> guide_factor = 2 >>> rng = DiscreteGuideTable(pv, random_state=urng, guide_factor=guide_factor) >>> rng.rvs() - 2 + 2 # may vary Unfortunately, the PPF is rarely available in closed form or too slow when available. The user only has to provide the probability vector and the @@ -109,6 +109,7 @@ method calculates the (exact) PPF of the given distribution. For example to calculate the PPF of a binomial distribution with :math:`n=4` and :math:`p=0.1`: we can set up a guide table as follows: + >>> import scipy.stats as stats >>> n, p = 4, 0.1 >>> dist = stats.binom(n, p) >>> rng = DiscreteGuideTable(dist, random_state=42) diff --git a/doc/source/tutorial/stats/sampling_hinv.rst b/doc/source/tutorial/stats/sampling_hinv.rst index 36e70aa04e45..dc07ac70010a 100644 --- a/doc/source/tutorial/stats/sampling_hinv.rst +++ b/doc/source/tutorial/stats/sampling_hinv.rst @@ -57,6 +57,7 @@ below the specified tolerance `u_resolution`. Refinement stops when the required tolerance is achieved or when the number of mesh intervals after the next refinement could exceed the maximum allowed number of intervals (100000). + >>> import numpy as np >>> from scipy.stats.sampling import NumericalInverseHermite >>> from scipy.stats import norm, genexpon >>> from scipy.special import ndtr @@ -97,6 +98,7 @@ the same random state. To check that the random variates closely follow the given distribution, we can look at its histogram: + >>> import matplotlib.pyplot as plt >>> dist = StandardNormal() >>> urng = np.random.default_rng() >>> rng = NumericalInverseHermite(dist, random_state=urng) diff --git a/doc/source/tutorial/stats/sampling_pinv.rst b/doc/source/tutorial/stats/sampling_pinv.rst index df4b28c7cad7..cfce82a788c6 100644 --- a/doc/source/tutorial/stats/sampling_pinv.rst +++ b/doc/source/tutorial/stats/sampling_pinv.rst @@ -97,6 +97,7 @@ Following four steps are carried out by the algorithm during setup: To initialize the generator to sample from a standard normal distribution, do: + >>> import numpy as np >>> from scipy.stats.sampling import NumericalInversePolynomial >>> class StandardNormal: ... def pdf(self, x): @@ -173,7 +174,7 @@ PDF evaluations increase during setup for small values of ``u_resolution``. >>> rng = NumericalInversePolynomial(dist, u_resolution=1e-8, ... random_state=urng) >>> dist.callbacks - 4095 + 4095 # may vary >>> dist.callbacks = 0 # reset the number of callbacks >>> # u_resolution = 10^-10 (default) >>> # => more PDF evaluations required @@ -181,14 +182,14 @@ PDF evaluations increase during setup for small values of ``u_resolution``. >>> rng = NumericalInversePolynomial(dist, u_resolution=1e-10, ... random_state=urng) >>> dist.callbacks - 11454 + 11454 # may vary >>> dist.callbacks = 0 # reset the number of callbacks >>> # u_resolution = 10^-12 >>> # => lots of PDF evaluations required >>> # => very slow setup >>> rng = NumericalInversePolynomial(dist, u_resolution=1e-12, ... random_state=urng) - 13902 + 13902 # may vary As we can see, the number of PDF evaluations required is very high and a fast PDF is critical to the algorithm. Though, this helps reduce the number diff --git a/doc/source/tutorial/stats/sampling_srou.rst b/doc/source/tutorial/stats/sampling_srou.rst index f9e0640213eb..ec74a6ba9421 100644 --- a/doc/source/tutorial/stats/sampling_srou.rst +++ b/doc/source/tutorial/stats/sampling_srou.rst @@ -15,6 +15,7 @@ Simple Ratio-of-Uniforms (SROU) SROU is based on the ratio-of-uniforms method that uses universal inequalities for constructing a (universal) bounding rectangle. It works for T-concave distributions with T(x) = -1/sqrt(x). + >>> import numpy as np >>> from scipy.stats.sampling import SimpleRatioUniforms Suppose we have the normal distribution: @@ -101,7 +102,7 @@ given by `np.arange(1.5, 5, 1000)`. ... rng = SimpleRatioUniforms(dist, mode=p[i]-1, ... pdf_area=math.gamma(p[i]), ... random_state=urng) - ... with np.suppress_warnings() as sup: + ... with np.testing.suppress_warnings() as sup: ... sup.filter(RuntimeWarning, "invalid value encountered in double_scalars") ... sup.filter(RuntimeWarning, "overflow encountered in exp") ... res[i] = rng.rvs(100) diff --git a/doc/source/tutorial/stats/sampling_tdr.rst b/doc/source/tutorial/stats/sampling_tdr.rst index 7351ce3b175b..988882ea146e 100644 --- a/doc/source/tutorial/stats/sampling_tdr.rst +++ b/doc/source/tutorial/stats/sampling_tdr.rst @@ -30,6 +30,7 @@ The variant that is implemented uses squeezes proportional to hat function ([1]_ An example of using this method is shown below: + >>> import numpy as np >>> from scipy.stats.sampling import TransformedDensityRejection >>> from scipy.stats import norm >>> From 639971874500308aeb4a12e22f215c4e9c880c4b Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 28 Feb 2024 16:40:44 +0300 Subject: [PATCH 161/500] MAINT: docs: fix small issues in tutorials --- doc/source/tutorial/interpolate/ND_regular_grid.rst | 4 ++-- scipy/conftest.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/source/tutorial/interpolate/ND_regular_grid.rst b/doc/source/tutorial/interpolate/ND_regular_grid.rst index c98e0e1a836a..5add4d5a38f0 100644 --- a/doc/source/tutorial/interpolate/ND_regular_grid.rst +++ b/doc/source/tutorial/interpolate/ND_regular_grid.rst @@ -92,10 +92,10 @@ controlled by the ``fill_value`` keyword parameter: >>> rgi = RegularGridInterpolator((x, y), data, ... bounds_error=False, fill_value=None) >>> rgi([(2, 0), (2, 1), (2, -1)]) # extrapolates the value on the axis - array([2., 2., 2.])) + array([2., 2., 2.]) >>> rgi.fill_value = -101 >>> rgi([(2, 0), (2, 1), (2, -1)]) - array([2., -101., -101.])) + array([2., -101., -101.]) .. note:: diff --git a/scipy/conftest.py b/scipy/conftest.py index e847c5eb1892..39f28840310b 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -353,4 +353,6 @@ def warnings_errors_and_rng(test): "scipy.misc", ] +dt_config.pseudocode = set(['integrate.nquad(func,']) +dt_config.local_resources = {'io.rst': ["octave_a.mat"]} ############################################################################ From 7a9be4d29eb16e09ed05b2a41ce3363823506b6d Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 6 Jul 2022 23:22:57 +0300 Subject: [PATCH 162/500] DOC: doctest tutorial/io.rst The tweaks are: - the Octave version is different (somebody forgotten to check in regenerated files, in 2013) - the dict order is different, at least on python 3.8 --- doc/source/tutorial/io.rst | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/doc/source/tutorial/io.rst b/doc/source/tutorial/io.rst index 1425b1407eb5..7c0977dfe7c7 100644 --- a/doc/source/tutorial/io.rst +++ b/doc/source/tutorial/io.rst @@ -85,13 +85,13 @@ Now, to Python: >>> mat_contents = sio.loadmat('octave_a.mat') >>> mat_contents - {'a': array([[[ 1., 4., 7., 10.], - [ 2., 5., 8., 11.], - [ 3., 6., 9., 12.]]]), + {'__header__': b'MATLAB 5.0 MAT-file, written + by Octave 3.2.3, 2010-05-30 02:13:40 UTC', '__version__': '1.0', - '__header__': 'MATLAB 5.0 MAT-file, written by - Octave 3.6.3, 2013-02-17 21:02:11 UTC', - '__globals__': []} + '__globals__': [], + 'a': array([[[ 1., 4., 7., 10.], + [ 2., 5., 8., 11.], + [ 3., 6., 9., 12.]]])} >>> oct_a = mat_contents['a'] >>> oct_a array([[[ 1., 4., 7., 10.], @@ -156,8 +156,12 @@ We can load this in Python: >>> mat_contents = sio.loadmat('octave_struct.mat') >>> mat_contents - {'my_struct': array([[([[1.0]], [[2.0]])]], - dtype=[('field1', 'O'), ('field2', 'O')]), '__version__': '1.0', '__header__': 'MATLAB 5.0 MAT-file, written by Octave 3.6.3, 2013-02-17 21:23:14 UTC', '__globals__': []} + {'__header__': b'MATLAB 5.0 MAT-file, written by Octave 3.2.3, 2010-05-30 02:00:26 UTC', + '__version__': '1.0', + '__globals__': [], + 'my_struct': array([[(array([[1.]]), array([[2.]]))]], dtype=[('field1', 'O'), ('field2', 'O')]) + } + >>> oct_struct = mat_contents['my_struct'] >>> oct_struct.shape (1, 1) @@ -214,7 +218,7 @@ this, use the ``struct_as_record=False`` parameter setting to ``loadmat``. File "", line 1, in AttributeError: 'mat_struct' object has no attribute 'shape' >>> type(oct_struct) - + >>> oct_struct.field1 1.0 @@ -287,12 +291,12 @@ Back to Python: Saving to a MATLAB cell array just involves making a NumPy object array: - >>> obj_arr = np.zeros((2,), dtype=np.object) + >>> obj_arr = np.zeros((2,), dtype=object) >>> obj_arr[0] = 1 >>> obj_arr[1] = 'a string' >>> obj_arr array([1, 'a string'], dtype=object) - >>> sio.savemat('np_cells.mat', {'obj_arr':obj_arr}) + >>> sio.savemat('np_cells.mat', {'obj_arr': obj_arr}) .. sourcecode:: octave From f254697a70642f39aae7b65b595e17689cac018d Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 28 Feb 2024 17:20:40 +0300 Subject: [PATCH 163/500] MAINT: doctests: skip broken tutorials Some of them are broken (io.rst? sampling_XXX?); others need a tooling update. This way, the output of $ pytest doc/source/tutorial/ --doctest-glob=*rst --ignore=doc/source/tutorial/examples is green. --- scipy/conftest.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scipy/conftest.py b/scipy/conftest.py index 39f28840310b..e93cef34d677 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -351,8 +351,16 @@ def warnings_errors_and_rng(test): "scipy.test", "scipy.show_config", "scipy.misc", + # tutorials: + "io.rst", # broken? + "ND_regular_grid.rst", + "ND_unstructured.rst", # XXX: MPL deprecation? + "extrapolation_examples.rst", + "sampling_pinv.rst", + "sampling_srou.rst", ] -dt_config.pseudocode = set(['integrate.nquad(func,']) +# tutorials +dt_config.pseudocode = set(['integrate.nquad(func,']) #, 'octave_struct.mat']) dt_config.local_resources = {'io.rst': ["octave_a.mat"]} ############################################################################ From 36a11116cfc8c79e312eac6b2e3a8f7d4f89b4d9 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Thu, 29 Feb 2024 15:55:40 +0300 Subject: [PATCH 164/500] MAINT: doctests: a more detailed ignore/skip/xfail doctests --- scipy/conftest.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/scipy/conftest.py b/scipy/conftest.py index e93cef34d677..645e56760ca1 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -343,24 +343,28 @@ def warnings_errors_and_rng(test): 'scipy.sparse.linalg.norm', # XXX temp skip ]) -# help pytest collection a bit: these names are either private (distributions) -# or deprecated (misc), or just do not need doctesting -dt_config.pytest_extra_skips = [ +# help pytest collection a bit: these names are either private (distributions), +# or just do not need doctesting. +dt_config.pytest_extra_ignore = [ "scipy.stats.distributions", "scipy.optimize.cython_optimize", "scipy.test", "scipy.show_config", - "scipy.misc", - # tutorials: - "io.rst", # broken? - "ND_regular_grid.rst", - "ND_unstructured.rst", # XXX: MPL deprecation? - "extrapolation_examples.rst", - "sampling_pinv.rst", - "sampling_srou.rst", ] +dt_config.pytest_extra_xfail = { + # name: reason + "io.rst": "", + "ND_regular_grid.rst": "ReST parser limitation", + "ND_unstructured.rst": "matplotlib deprecation", + "extrapolation_examples.rst": "ReST parser limitation", + "sampling_pinv.rst": "__cinit__ unexpected argument", + "sampling_srou.rst": "nan in scalar_power", +} + # tutorials -dt_config.pseudocode = set(['integrate.nquad(func,']) #, 'octave_struct.mat']) +dt_config.pseudocode = set(['integrate.nquad(func,']) dt_config.local_resources = {'io.rst': ["octave_a.mat"]} + +dt_config.nameerror_after_exception = True ############################################################################ From d40a32b7a4fa4ad2a4d201172a90f359f7080cff Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 1 Mar 2024 14:17:07 +0300 Subject: [PATCH 165/500] DEV: refguide-check: add dev.py smoke-docs command, remove `test --doctests` --- dev.py | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/dev.py b/dev.py index 668305340899..b365d1422429 100644 --- a/dev.py +++ b/dev.py @@ -189,7 +189,7 @@ class EMOJI: }, { "name": "documentation", - "commands": ["doc", "refguide-check"], + "commands": ["doc", "refguide-check", "smoke-docs"], }, { "name": "release", @@ -670,9 +670,8 @@ class Test(Task): verbose = Option( ['--verbose', '-v'], default=False, is_flag=True, help="more verbosity") - doctests = Option(['--doctests'], default=False, is_flag=True, - help="Run doctests" - ) + # removed doctests as currently not supported by _lib/_testutils.py + # doctests = Option(['--doctests'], default=False) coverage = Option( ['--coverage', '-c'], default=False, is_flag=True, help=("report coverage of project code. " @@ -742,9 +741,6 @@ def scipy_tests(cls, args, pytest_args): else: tests = None - if args.doctests and not args.tests: - extra_argv += ['--doctest-collect=api'] - if len(args.array_api_backend) != 0: os.environ['SCIPY_ARRAY_API'] = json.dumps(list(args.array_api_backend)) @@ -759,7 +755,7 @@ def scipy_tests(cls, args, pytest_args): args.mode, verbose=verbose, extra_argv=extra_argv, - doctests=args.doctests, + doctests=False, coverage=args.coverage, tests=tests, parallel=args.parallel) @@ -774,6 +770,90 @@ def run(cls, pytest_args, **kwargs): return cls.scipy_tests(args, pytest_args) +@cli.cls_cmd('smoke-docs') +class SmokeDocs(Task): + # XXX This essntially is a copy-paste of the Task class. Consider de-duplicating. + ctx = CONTEXT + + verbose = Option( + ['--verbose', '-v'], default=False, is_flag=True, + help="more verbosity") + durations = Option( + ['--durations', '-d'], default=None, metavar="NUM_TESTS", + help="Show timing for the given number of slowest tests" + ) + submodule = Option( + ['--submodule', '-s'], default=None, metavar='MODULE_NAME', + help="Submodule whose tests to run (cluster, constants, ...)") + tests = Option( + ['--tests', '-t'], default=None, multiple=True, metavar='TESTS', + help='Specify tests to run') + parallel = Option( + ['--parallel', '-j'], default=1, metavar='N_JOBS', + help="Number of parallel jobs for testing" + ) + # Argument can't have `help=`; used to consume all of `-- arg1 arg2 arg3` + pytest_args = Argument( + ['pytest_args'], nargs=-1, metavar='PYTEST-ARGS', required=False + ) + + TASK_META = { + 'task_dep': ['build'], + } + + @classmethod + def scipy_tests(cls, args, pytest_args): + dirs = Dirs(args) + dirs.add_sys_path() + print(f"SciPy from development installed path at: {dirs.site}") + + # FIXME: support pos-args with doit + extra_argv = list(pytest_args[:]) if pytest_args else [] + if extra_argv and extra_argv[0] == '--': + extra_argv = extra_argv[1:] + + if args.durations: + extra_argv += ['--durations', args.durations] + + # convert options to test selection + if args.submodule: + tests = [PROJECT_MODULE + "." + args.submodule] + elif args.tests: + tests = args.tests + else: + tests = None + + # use strategy=api unless -t path/to/specific/file + if not args.tests: + extra_argv += ['--doctest-collect=api'] + + runner, version, mod_path = get_test_runner(PROJECT_MODULE) + # FIXME: changing CWD is not a good practice + with working_dir(dirs.site): + print("Running tests for {} version:{}, installed at:{}".format( + PROJECT_MODULE, version, mod_path)) + # runner verbosity - convert bool to int + verbose = int(args.verbose) + 1 + result = runner( # scipy._lib._testutils:PytestTester + "fast", + verbose=verbose, + extra_argv=extra_argv, + doctests=True, + coverage=False, + tests=tests, + parallel=args.parallel) + return result + + @classmethod + def run(cls, pytest_args, **kwargs): + """run unit-tests""" + kwargs.update(cls.ctx.get()) + Args = namedtuple('Args', [k for k in kwargs.keys()]) + args = Args(**kwargs) + return cls.scipy_tests(args, pytest_args) + + + @cli.cls_cmd('bench') class Bench(Task): """:wrench: Run benchmarks. From 5366504709aa314da1b053ed96278cc647369ce3 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 1 Mar 2024 15:04:52 +0300 Subject: [PATCH 166/500] DEV: doctests: add dev.py smoke-tutorial command --- dev.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/dev.py b/dev.py index b365d1422429..a5888e55e349 100644 --- a/dev.py +++ b/dev.py @@ -189,7 +189,7 @@ class EMOJI: }, { "name": "documentation", - "commands": ["doc", "refguide-check", "smoke-docs"], + "commands": ["doc", "refguide-check", "smoke-docs", "smoke-tutorial"], }, { "name": "release", @@ -853,6 +853,49 @@ def run(cls, pytest_args, **kwargs): return cls.scipy_tests(args, pytest_args) +@cli.cls_cmd('smoke-tutorial') +class SmokeTutorial(Task): + """:wrench: Run smoke-tests on tutorial files.""" + ctx = CONTEXT + + tests = Option( + ['--tests', '-t'], default=None, multiple=True, metavar='TESTS', + help='Specify *rst files to smoke test') + verbose = Option( + ['--verbose', '-v'], default=False, is_flag=True, help="verbosity") + + pytest_args = Argument( + ['pytest_args'], nargs=-1, metavar='PYTEST-ARGS', required=False + ) + + @classmethod + def task_meta(cls, **kwargs): + kwargs.update(cls.ctx.get()) + Args = namedtuple('Args', [k for k in kwargs.keys()]) + args = Args(**kwargs) + dirs = Dirs(args) + + cmd = ['pytest'] + if args.tests: + cmd += list(args.tests) + else: + cmd += ['doc/source/tutorial', '--doctest-glob=*rst'] + if args.verbose: + cmd += ['-v'] + + pytest_args = kwargs.pop('pytest_args', None) + extra_argv = list(pytest_args[:]) if pytest_args else [] + if extra_argv and extra_argv[0] == '--': + extra_argv = extra_argv[1:] + cmd += extra_argv + + cmd_str = ' '.join(cmd) + return { + 'actions': [f'env PYTHONPATH={dirs.site} {cmd_str}'], + 'task_dep': ['build'], + 'io': {'capture': False}, + } + @cli.cls_cmd('bench') class Bench(Task): From 41fc6e7b85122e2ab8b84f13d1aabd6462f5fd50 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 1 Mar 2024 15:13:44 +0300 Subject: [PATCH 167/500] CI: run smoke-docs on CircleCI [docs only] --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 22e1bec8b9b3..6ff65c5d48b1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -180,7 +180,8 @@ jobs: python dev.py --no-build refguide-check pip install matplotlib hypothesis pip install git+https://github.com/ev-br/scpdt.git@main - python dev.py test --doctests + python dev.py smoke-docs + python dev.py smoke-tutorial # Upload build output to scipy/devdocs repository, using SSH deploy keys. # The keys are only available for builds on main branch. From fc66dccffd797e461444467b0083eea367544a1e Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 1 Mar 2024 16:02:19 +0300 Subject: [PATCH 168/500] MAINT: appease the linter --- doc/source/tutorial/conftest.py | 2 +- scipy/conftest.py | 11 +++-------- scipy/stats/qmc.py | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/doc/source/tutorial/conftest.py b/doc/source/tutorial/conftest.py index 7a6f439b5fd4..0f9109a01527 100644 --- a/doc/source/tutorial/conftest.py +++ b/doc/source/tutorial/conftest.py @@ -1 +1 @@ -from scipy.conftest import dt_config +from scipy.conftest import dt_config # noqa: F401 diff --git a/scipy/conftest.py b/scipy/conftest.py index 645e56760ca1..e6ad730da019 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -13,6 +13,9 @@ from scipy._lib._testutils import FPUModeChangeWarning from scipy._lib._array_api import SCIPY_ARRAY_API, SCIPY_DEVICE +from scpdt.conftest import dt_config +from contextlib import contextmanager + def pytest_configure(config): config.addinivalue_line("markers", @@ -243,11 +246,6 @@ def skip_xp_backends(xp, request): ############################################################################ # doctesting stuff -from scpdt.conftest import dt_config -from contextlib import contextmanager -import warnings - - # FIXME: populate the dict once @contextmanager def warnings_errors_and_rng(test): @@ -305,7 +303,6 @@ def warnings_errors_and_rng(test): known_warnings.update(dct) # these legitimately emit warnings in examples - from scipy.signal._filter_design import BadCoefficients legit = set('scipy.signal.normalize') # Now, the meat of the matter: filter warnings, @@ -365,6 +362,4 @@ def warnings_errors_and_rng(test): # tutorials dt_config.pseudocode = set(['integrate.nquad(func,']) dt_config.local_resources = {'io.rst': ["octave_a.mat"]} - -dt_config.nameerror_after_exception = True ############################################################################ diff --git a/scipy/stats/qmc.py b/scipy/stats/qmc.py index 48e56471ec46..a8a08343cf4c 100644 --- a/scipy/stats/qmc.py +++ b/scipy/stats/qmc.py @@ -233,4 +233,4 @@ """ from ._qmc import * # noqa: F403 -from ._qmc import __all__ # noqa: F403 +from ._qmc import __all__ # noqa: F401 From f16be46c9efc347e66ef1d42fb1f00cc7f3f6f7c Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 2 Mar 2024 14:11:04 +0300 Subject: [PATCH 169/500] DOC: tutorial: fix MPL deprecation warnings --- .../tutorial/interpolate/ND_unstructured.rst | 20 +++++++++---------- scipy/conftest.py | 1 - 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/doc/source/tutorial/interpolate/ND_unstructured.rst b/doc/source/tutorial/interpolate/ND_unstructured.rst index a85d91cf8cdd..8ba905cd4a8d 100644 --- a/doc/source/tutorial/interpolate/ND_unstructured.rst +++ b/doc/source/tutorial/interpolate/ND_unstructured.rst @@ -115,21 +115,21 @@ classes from the `scipy.interpolate` module. >>> ius = InterpolatedUnivariateSpline(x, y) >>> yi = ius(xi) - >>> plt.subplot(211) - >>> plt.plot(x, y, 'bo') - >>> plt.plot(xi, yi, 'g') - >>> plt.plot(xi, np.sin(xi), 'r') - >>> plt.title('Interpolation using univariate spline') + >>> fix, (ax1, ax2) = plt.subplots(2, 1) + >>> ax1.plot(x, y, 'bo') + >>> ax1.plot(xi, yi, 'g') + >>> ax1.plot(xi, np.sin(xi), 'r') + >>> ax1.set_title('Interpolation using univariate spline') >>> # use RBF method >>> rbf = RBFInterpolator(x, y) >>> fi = rbf(xi) - >>> plt.subplot(212) - >>> plt.plot(x, y, 'bo') - >>> plt.plot(xi, fi, 'g') - >>> plt.plot(xi, np.sin(xi), 'r') - >>> plt.title('Interpolation using RBF - multiquadrics') + >>> ax2.plot(x, y, 'bo') + >>> ax2.plot(xi, fi, 'g') + >>> ax2.plot(xi, np.sin(xi), 'r') + >>> ax2.set_title('Interpolation using RBF - multiquadrics') + >>> plt.tight_layout() >>> plt.show() .. :caption: Example of a 1-D RBF interpolation. diff --git a/scipy/conftest.py b/scipy/conftest.py index e6ad730da019..2a2e002bf640 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -353,7 +353,6 @@ def warnings_errors_and_rng(test): # name: reason "io.rst": "", "ND_regular_grid.rst": "ReST parser limitation", - "ND_unstructured.rst": "matplotlib deprecation", "extrapolation_examples.rst": "ReST parser limitation", "sampling_pinv.rst": "__cinit__ unexpected argument", "sampling_srou.rst": "nan in scalar_power", From 2ace6d1d07b095a13d924834afa7353948c78003 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 2 Mar 2024 14:11:16 +0300 Subject: [PATCH 170/500] CI: split smoke-docs CI run into separate rubrics [docs only] --- .circleci/config.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6ff65c5d48b1..ccf2bb8cd849 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -178,9 +178,19 @@ jobs: sudo apt-get install -y wamerican-small export PYTHONPATH=$PWD/build-install/lib/python3.11/site-packages python dev.py --no-build refguide-check + + - run: + name: smoke-docs + no_output_timeout: 35m + command: | pip install matplotlib hypothesis pip install git+https://github.com/ev-br/scpdt.git@main python dev.py smoke-docs + + - run: + name: smoke-tutorials + no_output_timeout: 10m + command: | python dev.py smoke-tutorial # Upload build output to scipy/devdocs repository, using SSH deploy keys. From 1cbe9955da29e39674676da3baa9201a2380bd8b Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 5 Apr 2024 13:21:47 +0300 Subject: [PATCH 171/500] DOC: io: adapt to NumPy 2.0 scalar repr While at it, use csr_array not csr_matrix (cosmetics). --- scipy/io/_harwell_boeing/hb.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/scipy/io/_harwell_boeing/hb.py b/scipy/io/_harwell_boeing/hb.py index 9e88f28c881f..ff280ea3ab31 100644 --- a/scipy/io/_harwell_boeing/hb.py +++ b/scipy/io/_harwell_boeing/hb.py @@ -495,17 +495,13 @@ def hb_read(path_or_open_file): We can read and write a harwell-boeing format file: >>> from scipy.io import hb_read, hb_write - >>> from scipy.sparse import csr_matrix, eye - >>> data = csr_matrix(eye(3)) # create a sparse matrix + >>> from scipy.sparse import csr_array, eye + >>> data = csr_array(eye(3)) # create a sparse array >>> hb_write("data.hb", data) # write a hb file >>> print(hb_read("data.hb")) # read a hb file - - Coords Values - (0, 0) 1.0 - (1, 1) 1.0 - (2, 2) 1.0 - + (np.int32(0), np.int32(0)) 1.0 + (np.int32(1), np.int32(1)) 1.0 + (np.int32(2), np.int32(2)) 1.0 """ def _get_matrix(fid): hb = HBFile(fid) @@ -549,17 +545,13 @@ def hb_write(path_or_open_file, m, hb_info=None): We can read and write a harwell-boeing format file: >>> from scipy.io import hb_read, hb_write - >>> from scipy.sparse import csr_matrix, eye - >>> data = csr_matrix(eye(3)) # create a sparse matrix + >>> from scipy.sparse import csr_array, eye + >>> data = csr_array(eye(3)) # create a sparse array >>> hb_write("data.hb", data) # write a hb file >>> print(hb_read("data.hb")) # read a hb file - - Coords Values - (0, 0) 1.0 - (1, 1) 1.0 - (2, 2) 1.0 - + (np.int32(0), np.int32(0)) 1.0 + (np.int32(1), np.int32(1)) 1.0 + (np.int32(2), np.int32(2)) 1.0 """ m = m.tocsc(copy=False) From 87c03738b2237a2f16203f5ad59ea6c49b367baf Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 5 Apr 2024 14:05:24 +0300 Subject: [PATCH 172/500] DOC: sparse.csgraph: adjust docstrings to NumPy 2.0 scalar repr --- scipy/conftest.py | 20 +++++++ scipy/sparse/csgraph/_reordering.pyx | 32 +++++----- scipy/sparse/csgraph/_shortest_path.pyx | 77 ++++++++++--------------- scipy/sparse/csgraph/_tools.pyx | 22 +++---- scipy/sparse/csgraph/_traversal.pyx | 28 ++++++--- 5 files changed, 90 insertions(+), 89 deletions(-) diff --git a/scipy/conftest.py b/scipy/conftest.py index 2a2e002bf640..85c92fb7f110 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -340,6 +340,26 @@ def warnings_errors_and_rng(test): 'scipy.sparse.linalg.norm', # XXX temp skip ]) +# these are affected by NumPy 2.0 scalar repr: they rely on string comparison +if np.__version__ < "2": + dt_config.skiplist += set([ + 'scipy.io.hb_read', + 'scipy.io.hb_write', + 'scipy.sparse.csgraph.connected_components', + 'scipy.sparse.csgraph.depth_first_order', + 'scipy.sparse.csgraph.shortest_path', + 'scipy.sparse.csgraph.floyd_warshall', + 'scipy.sparse.csgraph.dijkstra', + 'scipy.sparse.csgraph.bellman_ford', + 'scipy.sparse.csgraph.johnson', + 'scipy.sparse.csgraph.yen', + 'scipy.sparse.csgraph.breadth_first_order', + 'scipy.sparse.csgraph.reverse_cuthill_mckee', + 'scipy.sparse.csgraph.structural_rank', + 'scipy.sparse.csgraph.construct_dist_matrix', + 'scipy.sparse.csgraph.reconstruct_path', +]) + # help pytest collection a bit: these names are either private (distributions), # or just do not need doctesting. dt_config.pytest_extra_ignore = [ diff --git a/scipy/sparse/csgraph/_reordering.pyx b/scipy/sparse/csgraph/_reordering.pyx index db57d9a4d16c..f67990f0f057 100644 --- a/scipy/sparse/csgraph/_reordering.pyx +++ b/scipy/sparse/csgraph/_reordering.pyx @@ -59,14 +59,11 @@ def reverse_cuthill_mckee(graph, symmetric_mode=False): ... ] >>> graph = csr_matrix(graph) >>> print(graph) - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 3) 1 - (2, 0) 2 - (2, 3) 3 + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(0)) 2 + (np.int32(2), np.int32(3)) 3 >>> reverse_cuthill_mckee(graph) array([3, 2, 1, 0], dtype=int32) @@ -218,17 +215,14 @@ def structural_rank(graph): ... ] >>> graph = csr_matrix(graph) >>> print(graph) - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 0) 1 - (1, 3) 1 - (2, 0) 2 - (2, 3) 3 - (3, 1) 1 - (3, 2) 3 + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(0)) 1 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(0)) 2 + (np.int32(2), np.int32(3)) 3 + (np.int32(3), np.int32(1)) 1 + (np.int32(3), np.int32(2)) 3 >>> structural_rank(graph) 4 diff --git a/scipy/sparse/csgraph/_shortest_path.pyx b/scipy/sparse/csgraph/_shortest_path.pyx index d8d90f816fc8..40ab24e1c6e8 100644 --- a/scipy/sparse/csgraph/_shortest_path.pyx +++ b/scipy/sparse/csgraph/_shortest_path.pyx @@ -148,14 +148,11 @@ def shortest_path(csgraph, method='auto', ... ] >>> graph = csr_matrix(graph) >>> print(graph) - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 3) 1 - (2, 0) 2 - (2, 3) 3 + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(0)) 2 + (np.int32(2), np.int32(3)) 3 >>> dist_matrix, predecessors = shortest_path(csgraph=graph, directed=False, indices=0, return_predecessors=True) >>> dist_matrix @@ -297,15 +294,11 @@ def floyd_warshall(csgraph, directed=True, ... ] >>> graph = csr_matrix(graph) >>> print(graph) - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 3) 1 - (2, 0) 2 - (2, 3) 3 - + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(0)) 2 + (np.int32(2), np.int32(3)) 3 >>> dist_matrix, predecessors = floyd_warshall(csgraph=graph, directed=False, return_predecessors=True) >>> dist_matrix @@ -525,13 +518,10 @@ def dijkstra(csgraph, directed=True, indices=None, ... ] >>> graph = csr_matrix(graph) >>> print(graph) - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 3) 1 - (2, 3) 3 + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(3)) 3 >>> dist_matrix, predecessors = dijkstra(csgraph=graph, directed=False, indices=0, return_predecessors=True) >>> dist_matrix @@ -1013,14 +1003,11 @@ def bellman_ford(csgraph, directed=True, indices=None, ... ] >>> graph = csr_matrix(graph) >>> print(graph) - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 3) 1 - (2, 0) 2 - (2, 3) 3 + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(0)) 2 + (np.int32(2), np.int32(3)) 3 >>> dist_matrix, predecessors = bellman_ford(csgraph=graph, directed=False, indices=0, return_predecessors=True) >>> dist_matrix @@ -1253,14 +1240,11 @@ def johnson(csgraph, directed=True, indices=None, ... ] >>> graph = csr_matrix(graph) >>> print(graph) - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 3) 1 - (2, 0) 2 - (2, 3) 3 + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(0)) 2 + (np.int32(2), np.int32(3)) 3 >>> dist_matrix, predecessors = johnson(csgraph=graph, directed=False, indices=0, return_predecessors=True) >>> dist_matrix @@ -1786,14 +1770,11 @@ def yen( ... ] >>> graph = csr_matrix(graph) >>> print(graph) - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 3) 1 - (2, 0) 2 - (2, 3) 3 + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(0)) 2 + (np.int32(2), np.int32(3)) 3 >>> dist_array, predecessors = yen(csgraph=graph, source=0, sink=3, K=2, ... directed=False, return_predecessors=True) diff --git a/scipy/sparse/csgraph/_tools.pyx b/scipy/sparse/csgraph/_tools.pyx index a1f804864437..50dc61356fe3 100644 --- a/scipy/sparse/csgraph/_tools.pyx +++ b/scipy/sparse/csgraph/_tools.pyx @@ -453,13 +453,10 @@ def reconstruct_path(csgraph, predecessors, directed=True): ... ] >>> graph = csr_matrix(graph) >>> print(graph) - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 3) 1 - (2, 3) 3 + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(3)) 3 >>> pred = np.array([-9999, 0, 0, 1], dtype=np.int32) @@ -565,13 +562,10 @@ def construct_dist_matrix(graph, ... ] >>> graph = csr_matrix(graph) >>> print(graph) - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 3) 1 - (2, 3) 3 + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(3)) 3 >>> pred = np.array([[-9999, 0, 0, 2], ... [1, -9999, 0, 1], diff --git a/scipy/sparse/csgraph/_traversal.pyx b/scipy/sparse/csgraph/_traversal.pyx index 149559b7e447..715f65af2da9 100644 --- a/scipy/sparse/csgraph/_traversal.pyx +++ b/scipy/sparse/csgraph/_traversal.pyx @@ -75,6 +75,7 @@ def connected_components(csgraph, directed=True, connection='weak', ... ] >>> graph = csr_matrix(graph) >>> print(graph) +<<<<<<< HEAD Coords Values @@ -82,6 +83,12 @@ def connected_components(csgraph, directed=True, connection='weak', (0, 2) 1 (1, 2) 1 (3, 4) 1 +======= + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 1 + (np.int32(1), np.int32(2)) 1 + (np.int32(3), np.int32(4)) 1 +>>>>>>> DOC: sparse.csgraph: adjust docstrings to NumPy 2.0 scalar repr >>> n_components, labels = connected_components(csgraph=graph, directed=False, return_labels=True) >>> n_components @@ -335,14 +342,11 @@ cpdef breadth_first_order(csgraph, i_start, ... ] >>> graph = csr_matrix(graph) >>> print(graph) - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 3) 1 - (2, 0) 2 - (2, 3) 3 + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(0)) 2 + (np.int32(2), np.int32(3)) 3 >>> breadth_first_order(graph,0) (array([0, 1, 2, 3], dtype=int32), array([-9999, 0, 0, 1], dtype=int32)) @@ -544,6 +548,7 @@ cpdef depth_first_order(csgraph, i_start, ... ] >>> graph = csr_matrix(graph) >>> print(graph) +<<<<<<< HEAD Coords Values @@ -552,6 +557,13 @@ cpdef depth_first_order(csgraph, i_start, (1, 3) 1 (2, 0) 2 (2, 3) 3 +======= + (np.int32(0), np.int32(1)) 1 + (np.int32(0), np.int32(2)) 2 + (np.int32(1), np.int32(3)) 1 + (np.int32(2), np.int32(0)) 2 + (np.int32(2), np.int32(3)) 3 +>>>>>>> DOC: sparse.csgraph: adjust docstrings to NumPy 2.0 scalar repr >>> depth_first_order(graph,0) (array([0, 1, 3, 2], dtype=int32), array([-9999, 0, 0, 1], dtype=int32)) From a812c2dc148de918c313043a56a7a4f94c431188 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 5 Apr 2024 14:11:46 +0300 Subject: [PATCH 173/500] DOC: ndimage: adjust docstring to NumPy 2.0 scalar repr --- scipy/conftest.py | 1 + scipy/ndimage/_measurements.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scipy/conftest.py b/scipy/conftest.py index 85c92fb7f110..8b2583bb8556 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -358,6 +358,7 @@ def warnings_errors_and_rng(test): 'scipy.sparse.csgraph.structural_rank', 'scipy.sparse.csgraph.construct_dist_matrix', 'scipy.sparse.csgraph.reconstruct_path', + 'scipy.ndimage.value_indices', ]) # help pytest collection a bit: these names are either private (distributions), diff --git a/scipy/ndimage/_measurements.py b/scipy/ndimage/_measurements.py index e999914ab296..bcd83df42be3 100644 --- a/scipy/ndimage/_measurements.py +++ b/scipy/ndimage/_measurements.py @@ -387,7 +387,7 @@ def value_indices(arr, *, ignore_value=None): value in the input array. >>> val_indices.keys() - dict_keys([0, 1, 2, 3]) + dict_keys([np.int64(0), np.int64(1), np.int64(2), np.int64(3)]) The entry for each value is an index tuple, locating the elements with that value. @@ -407,7 +407,7 @@ def value_indices(arr, *, ignore_value=None): >>> val_indices = ndimage.value_indices(a, ignore_value=0) >>> val_indices.keys() - dict_keys([1, 2, 3]) + dict_keys([np.int64(1), np.int64(2), np.int64(3)]) """ # Cope with ignore_value being None, without too much extra complexity From 335f0ade38c8e77a5a9dff820839abfcfa49daaf Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 5 Apr 2024 16:00:14 +0300 Subject: [PATCH 174/500] DOC: stats: adjust docstring to NumPy 2.0 scalar repr Had to remove Ellipsis matching on numeric values in several places. [docs only] --- scipy/conftest.py | 1 + scipy/stats/_hypotests.py | 10 +++++----- scipy/stats/_morestats.py | 4 ++-- scipy/stats/_mstats_basic.py | 6 +++--- scipy/stats/_survival.py | 4 ++-- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/scipy/conftest.py b/scipy/conftest.py index 8b2583bb8556..038cf60be779 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -359,6 +359,7 @@ def warnings_errors_and_rng(test): 'scipy.sparse.csgraph.construct_dist_matrix', 'scipy.sparse.csgraph.reconstruct_path', 'scipy.ndimage.value_indices', + 'scipy.stats.describe', ]) # help pytest collection a bit: these names are either private (distributions), diff --git a/scipy/stats/_hypotests.py b/scipy/stats/_hypotests.py index ebf8cdb4f971..0f201403943e 100644 --- a/scipy/stats/_hypotests.py +++ b/scipy/stats/_hypotests.py @@ -1083,9 +1083,9 @@ def barnard_exact(table, alternative="two-sided", pooled=True, n=32): >>> import scipy.stats as stats >>> res = stats.barnard_exact([[7, 12], [8, 3]], alternative="less") >>> res.statistic - -1.894... + -1.894 >>> res.pvalue - 0.03407... + 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 @@ -1097,7 +1097,7 @@ def barnard_exact(table, alternative="two-sided", pooled=True, n=32): >>> _, pvalue = stats.fisher_exact([[7, 12], [8, 3]], alternative="less") >>> pvalue - 0.0640... + 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 @@ -1307,9 +1307,9 @@ def boschloo_exact(table, alternative="two-sided", n=32): >>> import scipy.stats as stats >>> res = stats.boschloo_exact([[74, 31], [43, 32]], alternative="greater") >>> res.statistic - 0.0483... + 0.0483 >>> res.pvalue - 0.0355... + 0.0355 Under the null hypothesis that scientists are happier in their work than college professors, the probability of obtaining test diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index dfaa0faeda8e..133119820e40 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -99,7 +99,7 @@ def bayes_mvs(data, alpha=0.90): >>> mean Mean(statistic=9.0, minmax=(7.103650222612533, 10.896349777387467)) >>> var - Variance(statistic=10.0, minmax=(3.176724206..., 24.45910382...)) + Variance(statistic=10.0, minmax=(3.176724206, 24.45910382)) >>> std Std_dev(statistic=2.9724954732045084, minmax=(1.7823367265645143, 4.945614605014631)) @@ -1261,7 +1261,7 @@ def boxcox_normmax( ... return optimize.minimize_scalar(fun, bounds=(6, 7), ... method="bounded", options=options) >>> stats.boxcox_normmax(x, optimizer=optimizer) - 6.000... + 6.000000000 """ x = np.asarray(x) diff --git a/scipy/stats/_mstats_basic.py b/scipy/stats/_mstats_basic.py index c18c1b066d1b..63f1108b0a05 100644 --- a/scipy/stats/_mstats_basic.py +++ b/scipy/stats/_mstats_basic.py @@ -2971,13 +2971,13 @@ def describe(a, axis=0, ddof=0, bias=True): >>> from scipy.stats.mstats import describe >>> ma = np.ma.array(range(6), mask=[0, 0, 0, 1, 1, 1]) >>> describe(ma) - DescribeResult(nobs=3, minmax=(masked_array(data=0, + 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=1.0, variance=0.6666666666666666, + 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=-1.5) + kurtosis=np.float64(-1.5)) """ a, axis = _chk_asarray(a, axis) diff --git a/scipy/stats/_survival.py b/scipy/stats/_survival.py index 82aad05ab8e0..f467f23522c5 100644 --- a/scipy/stats/_survival.py +++ b/scipy/stats/_survival.py @@ -629,9 +629,9 @@ def logrank( >>> res = stats.logrank(x=x, y=y) >>> res.statistic - -2.73799... + -2.73799 >>> res.pvalue - 0.00618... + 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 From ba82736af37ca546498cd5149d313e3561751936 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 5 Apr 2024 16:25:05 +0300 Subject: [PATCH 175/500] DOC: tutorial: fix dev.py smoke-tutorial [docs only] --- doc/source/tutorial/stats/analysing_one_sample.rst | 2 ++ doc/source/tutorial/stats/comparing_two_samples.rst | 1 + doc/source/tutorial/stats/kernel_density_estimation.rst | 1 + scipy/conftest.py | 1 + 4 files changed, 5 insertions(+) diff --git a/doc/source/tutorial/stats/analysing_one_sample.rst b/doc/source/tutorial/stats/analysing_one_sample.rst index 841f1f65ec7b..40935a7f8545 100644 --- a/doc/source/tutorial/stats/analysing_one_sample.rst +++ b/doc/source/tutorial/stats/analysing_one_sample.rst @@ -5,6 +5,8 @@ First, we create some random variables. We set a seed so that in each run we get identical results to look at. As an example we take a sample from the Student t distribution: + >>> import numpy as np + >>> import scipy.stats as stats >>> x = stats.t.rvs(10, size=1000) Here, we set the required shape parameter of the t distribution, which diff --git a/doc/source/tutorial/stats/comparing_two_samples.rst b/doc/source/tutorial/stats/comparing_two_samples.rst index d27a16177b70..311371494b0a 100644 --- a/doc/source/tutorial/stats/comparing_two_samples.rst +++ b/doc/source/tutorial/stats/comparing_two_samples.rst @@ -11,6 +11,7 @@ Comparing means Test with sample with identical means: + >>> import scipy.stats as stats >>> rvs1 = stats.norm.rvs(loc=5, scale=10, size=500) >>> rvs2 = stats.norm.rvs(loc=5, scale=10, size=500) >>> stats.ttest_ind(rvs1, rvs2) diff --git a/doc/source/tutorial/stats/kernel_density_estimation.rst b/doc/source/tutorial/stats/kernel_density_estimation.rst index 05dd168eace1..f7e0c28dd800 100644 --- a/doc/source/tutorial/stats/kernel_density_estimation.rst +++ b/doc/source/tutorial/stats/kernel_density_estimation.rst @@ -22,6 +22,7 @@ at the bottom of the figure (this is called a rug plot): .. plot:: :alt: " " + >>> import numpy as np >>> from scipy import stats >>> import matplotlib.pyplot as plt diff --git a/scipy/conftest.py b/scipy/conftest.py index 038cf60be779..c0491ada4f32 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -378,6 +378,7 @@ def warnings_errors_and_rng(test): "extrapolation_examples.rst": "ReST parser limitation", "sampling_pinv.rst": "__cinit__ unexpected argument", "sampling_srou.rst": "nan in scalar_power", + "probability_distributions.rst": "integration warning", } # tutorials From ca6c1c753c588e49811b061cdfb0080a62219e41 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 5 Apr 2024 20:58:51 +0300 Subject: [PATCH 176/500] MAINT: stats: fix doctesting infra for NumPy < 2 --- scipy/conftest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/conftest.py b/scipy/conftest.py index c0491ada4f32..8c237c4a8719 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -342,7 +342,7 @@ def warnings_errors_and_rng(test): # these are affected by NumPy 2.0 scalar repr: they rely on string comparison if np.__version__ < "2": - dt_config.skiplist += set([ + dt_config.skiplist.update(set([ 'scipy.io.hb_read', 'scipy.io.hb_write', 'scipy.sparse.csgraph.connected_components', @@ -359,8 +359,8 @@ def warnings_errors_and_rng(test): 'scipy.sparse.csgraph.construct_dist_matrix', 'scipy.sparse.csgraph.reconstruct_path', 'scipy.ndimage.value_indices', - 'scipy.stats.describe', -]) + 'scipy.stats.mstats.describe', +])) # help pytest collection a bit: these names are either private (distributions), # or just do not need doctesting. From 210e813bdfdbb6e808afe2942dd75d9d32ce8ce6 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 5 Apr 2024 22:09:50 +0300 Subject: [PATCH 177/500] MAINT: refguide-check: add skips to make it run under numpy < 2 Doctestrings were updated for NumPy 2.0, scalar reprs have changed. This is a minimal fix, just to avoid `dev.py refguide-check` erroring out. [docs only] --- tools/refguide_check.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tools/refguide_check.py b/tools/refguide_check.py index b8bb2a56b4a0..77371ba706ee 100755 --- a/tools/refguide_check.py +++ b/tools/refguide_check.py @@ -117,6 +117,26 @@ 'scipy.linalg.LinAlgError', 'scipy.optimize.show_options', 'io.rst', # XXX: need to figure out how to deal w/ mat files + # XXX: skips to match smoke-docs + 'scipy.stats.mstats.describe', + 'scipy.io.hb_read', + 'scipy.io.hb_write', + 'scipy.sparse.csgraph.connected_components', + 'scipy.sparse.csgraph.depth_first_order', + 'scipy.sparse.csgraph.shortest_path', + 'scipy.sparse.csgraph.floyd_warshall', + 'scipy.sparse.csgraph.dijkstra', + 'scipy.sparse.csgraph.bellman_ford', + 'scipy.sparse.csgraph.johnson', + 'scipy.sparse.csgraph.yen', + 'scipy.sparse.csgraph.breadth_first_order', + 'scipy.sparse.csgraph.reverse_cuthill_mckee', + 'scipy.sparse.csgraph.structural_rank', + 'scipy.sparse.csgraph.construct_dist_matrix', + 'scipy.sparse.csgraph.reconstruct_path', + 'scipy.ndimage.value_indices', + 'scipy.special.jve', + 'scipy.special.yve', ]) # these names are not required to be present in ALL despite being in @@ -676,7 +696,7 @@ def _do_check(self, want, got): return True except Exception: pass - return np.allclose(want, got, atol=self.atol, rtol=self.rtol) + return np.allclose(want, got, atol=self.atol, rtol=self.rtol, equal_nan=True) def _run_doctests(tests, full_name, verbose, doctest_warnings): From 0a065ed475b50cd377bd5e7342efef9520ed557b Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 6 Apr 2024 12:23:52 +0300 Subject: [PATCH 178/500] lint --- dev.py | 4 ++-- scipy/optimize/_basinhopping.py | 7 +++---- scipy/stats/_mstats_basic.py | 3 ++- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dev.py b/dev.py index a5888e55e349..227dbd6ae9a7 100644 --- a/dev.py +++ b/dev.py @@ -830,8 +830,8 @@ def scipy_tests(cls, args, pytest_args): runner, version, mod_path = get_test_runner(PROJECT_MODULE) # FIXME: changing CWD is not a good practice with working_dir(dirs.site): - print("Running tests for {} version:{}, installed at:{}".format( - PROJECT_MODULE, version, mod_path)) + print(f"Running tests for {PROJECT_MODULE} version:{version}, " + f"installed at:{mod_path}") # runner verbosity - convert bool to int verbose = int(args.verbose) + 1 result = runner( # scipy._lib._testutils:PytestTester diff --git a/scipy/optimize/_basinhopping.py b/scipy/optimize/_basinhopping.py index d201df179834..333a7af410de 100644 --- a/scipy/optimize/_basinhopping.py +++ b/scipy/optimize/_basinhopping.py @@ -238,10 +238,9 @@ def _adjust_step_size(self): # We're not accepting enough steps. Take smaller steps. self.takestep.stepsize *= self.factor if self.verbose: - print("adaptive stepsize: acceptance rate {:f} target {:f} new " - "stepsize {:g} old stepsize {:g}".format(accept_rate, - self.target_accept_rate, self.takestep.stepsize, - old_stepsize)) + print(f"adaptive stepsize: acceptance rate {accept_rate:f} target " + f"{self.target_accept_rate:f} new stepsize " + f"{self.takestep.stepsize:g} old stepsize {old_stepsize:g}") def take_step(self, x): self.nstep += 1 diff --git a/scipy/stats/_mstats_basic.py b/scipy/stats/_mstats_basic.py index 63f1108b0a05..0ce0d9abb955 100644 --- a/scipy/stats/_mstats_basic.py +++ b/scipy/stats/_mstats_basic.py @@ -2975,7 +2975,8 @@ def describe(a, axis=0, ddof=0, bias=True): mask=False, fill_value=999999), masked_array(data=2, mask=False, - fill_value=999999)), mean=np.float64(1.0), variance=np.float64(0.6666666666666666), + 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)) From 649efafbb097abed386671e7278ea347e5109d42 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Tue, 9 Apr 2024 20:24:34 +0300 Subject: [PATCH 179/500] TST: do not fail if scpdt is not available --- scipy/conftest.py | 281 ++++++++++++++++++++++++---------------------- 1 file changed, 144 insertions(+), 137 deletions(-) diff --git a/scipy/conftest.py b/scipy/conftest.py index 8c237c4a8719..132991d89432 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -3,6 +3,7 @@ import os import warnings import tempfile +from contextlib import contextmanager import numpy as np import numpy.testing as npt @@ -13,8 +14,11 @@ from scipy._lib._testutils import FPUModeChangeWarning from scipy._lib._array_api import SCIPY_ARRAY_API, SCIPY_DEVICE -from scpdt.conftest import dt_config -from contextlib import contextmanager +try: + from scpdt.conftest import dt_config + HAVE_SCPDT = True +except ModuleNotFoundError: + HAVE_SCPDT = False def pytest_configure(config): @@ -246,142 +250,145 @@ def skip_xp_backends(xp, request): ############################################################################ # doctesting stuff -# FIXME: populate the dict once -@contextmanager -def warnings_errors_and_rng(test): - """Temporarily turn (almost) all warnings to errors. - - Filter out known warnings which we allow. - """ - known_warnings = dict() - - # these functions are known to emit "divide by zero" RuntimeWarnings - divide_by_zero = [ - 'scipy.linalg.norm', 'scipy.ndimage.center_of_mass', - ] - for name in divide_by_zero: - known_warnings[name] = dict(category=RuntimeWarning, - message='divide by zero') - - # Deprecated stuff in scipy.signal and elsewhere - deprecated = [ - 'scipy.signal.cwt', 'scipy.signal.morlet', 'scipy.signal.morlet2', - 'scipy.signal.ricker', - 'scipy.integrate.simpson', - 'scipy.interpolate.interp2d', +if HAVE_SCPDT: + + # FIXME: populate the dict once + @contextmanager + def warnings_errors_and_rng(test): + """Temporarily turn (almost) all warnings to errors. + + Filter out known warnings which we allow. + """ + known_warnings = dict() + + # these functions are known to emit "divide by zero" RuntimeWarnings + divide_by_zero = [ + 'scipy.linalg.norm', 'scipy.ndimage.center_of_mass', + ] + for name in divide_by_zero: + known_warnings[name] = dict(category=RuntimeWarning, + message='divide by zero') + + # Deprecated stuff in scipy.signal and elsewhere + deprecated = [ + 'scipy.signal.cwt', 'scipy.signal.morlet', 'scipy.signal.morlet2', + 'scipy.signal.ricker', + 'scipy.integrate.simpson', + 'scipy.interpolate.interp2d', + ] + for name in deprecated: + known_warnings[name] = dict(category=DeprecationWarning) + + from scipy import integrate + # the funcions are known to emit IntergrationWarnings + integration_w = ['scipy.special.ellip_normal', + 'scipy.special.ellip_harm_2', + ] + for name in integration_w: + known_warnings[name] = dict(category=integrate.IntegrationWarning, + message='The occurrence of roundoff') + + # scipy.stats deliberately emits UserWarnings sometimes + user_w = ['scipy.stats.anderson_ksamp', 'scipy.stats.kurtosistest', + 'scipy.stats.normaltest', 'scipy.sparse.linalg.norm'] + for name in user_w: + known_warnings[name] = dict(category=UserWarning) + + # additional one-off warnings to filter + dct = { + 'scipy.sparse.linalg.norm': + dict(category=UserWarning, message="Exited at iteration"), + # tutorials + 'linalg.rst': + dict(message='the matrix subclass is not', + category=PendingDeprecationWarning), + 'stats.rst': + dict(message='The maximum number of subdivisions', + category=integrate.IntegrationWarning), + } + known_warnings.update(dct) + + # these legitimately emit warnings in examples + legit = set('scipy.signal.normalize') + + # Now, the meat of the matter: filter warnings, + # also control the random seed for each doctest. + + # XXX: this matches the refguide-check behavior, but is a tad strange: + # makes sure that the seed the old-fashioned np.random* methods is + # *NOT* reproducible but the new-style `default_rng()` *IS* repoducible. + # Should these two be either both repro or both not repro? + + from scipy._lib._util import _fixed_default_rng + import numpy as np + with _fixed_default_rng(): + np.random.seed(None) + with warnings.catch_warnings(): + if test.name in known_warnings: + warnings.filterwarnings('ignore', + **known_warnings[test.name]) + yield + elif test.name in legit: + yield + else: + warnings.simplefilter('error', Warning) + yield + + + dt_config.user_context_mgr = warnings_errors_and_rng + dt_config.skiplist = set([ + 'scipy.linalg.LinAlgError', # comes from numpy + 'scipy.fftpack.fftshift', # fftpack stuff is also from numpy + 'scipy.fftpack.ifftshift', + 'scipy.fftpack.fftfreq', + 'scipy.special.sinc', # sinc is from numpy + 'scipy.optimize.show_options', # does not have much to doctest + 'scipy.signal.normalize', # manipulates warnings (XXX temp skip) + 'scipy.sparse.linalg.norm', # XXX temp skip + ]) + + # these are affected by NumPy 2.0 scalar repr: rely on string comparison + if np.__version__ < "2": + dt_config.skiplist.update(set([ + 'scipy.io.hb_read', + 'scipy.io.hb_write', + 'scipy.sparse.csgraph.connected_components', + 'scipy.sparse.csgraph.depth_first_order', + 'scipy.sparse.csgraph.shortest_path', + 'scipy.sparse.csgraph.floyd_warshall', + 'scipy.sparse.csgraph.dijkstra', + 'scipy.sparse.csgraph.bellman_ford', + 'scipy.sparse.csgraph.johnson', + 'scipy.sparse.csgraph.yen', + 'scipy.sparse.csgraph.breadth_first_order', + 'scipy.sparse.csgraph.reverse_cuthill_mckee', + 'scipy.sparse.csgraph.structural_rank', + 'scipy.sparse.csgraph.construct_dist_matrix', + 'scipy.sparse.csgraph.reconstruct_path', + 'scipy.ndimage.value_indices', + 'scipy.stats.mstats.describe', + ])) + + # help pytest collection a bit: these names are either private + # (distributions), or just do not need doctesting. + dt_config.pytest_extra_ignore = [ + "scipy.stats.distributions", + "scipy.optimize.cython_optimize", + "scipy.test", + "scipy.show_config", ] - for name in deprecated: - known_warnings[name] = dict(category=DeprecationWarning) - from scipy import integrate - # the funcions are known to emit IntergrationWarnings - integration_w = ['scipy.special.ellip_normal', - 'scipy.special.ellip_harm_2', - ] - for name in integration_w: - known_warnings[name] = dict(category=integrate.IntegrationWarning, - message='The occurrence of roundoff') - - # scipy.stats deliberately emits UserWarnings sometimes - user_w = ['scipy.stats.anderson_ksamp', 'scipy.stats.kurtosistest', - 'scipy.stats.normaltest', 'scipy.sparse.linalg.norm'] - for name in user_w: - known_warnings[name] = dict(category=UserWarning) - - # additional one-off warnings to filter - dct = { - 'scipy.sparse.linalg.norm': - dict(category=UserWarning, message="Exited at iteration"), - # tutorials - 'linalg.rst': - dict(message='the matrix subclass is not', - category=PendingDeprecationWarning), - 'stats.rst': - dict(message='The maximum number of subdivisions', - category=integrate.IntegrationWarning), + dt_config.pytest_extra_xfail = { + # name: reason + "io.rst": "", + "ND_regular_grid.rst": "ReST parser limitation", + "extrapolation_examples.rst": "ReST parser limitation", + "sampling_pinv.rst": "__cinit__ unexpected argument", + "sampling_srou.rst": "nan in scalar_power", + "probability_distributions.rst": "integration warning", } - known_warnings.update(dct) - - # these legitimately emit warnings in examples - legit = set('scipy.signal.normalize') - - # Now, the meat of the matter: filter warnings, - # also control the random seed for each doctest. - - # XXX: this matches the refguide-check behavior, but is a tad strange: - # makes sure that the seed the old-fashioned np.random* methods is *NOT* - # reproducible but the new-style `default_rng()` *IS* repoducible. - # Should these two be either both repro or both not repro? - - from scipy._lib._util import _fixed_default_rng - import numpy as np - with _fixed_default_rng(): - np.random.seed(None) - with warnings.catch_warnings(): - if test.name in known_warnings: - warnings.filterwarnings('ignore', **known_warnings[test.name]) - yield - elif test.name in legit: - yield - else: - warnings.simplefilter('error', Warning) - yield - - -dt_config.user_context_mgr = warnings_errors_and_rng -dt_config.skiplist = set([ - 'scipy.linalg.LinAlgError', # comes from numpy - 'scipy.fftpack.fftshift', # fftpack stuff is also from numpy - 'scipy.fftpack.ifftshift', - 'scipy.fftpack.fftfreq', - 'scipy.special.sinc', # sinc is from numpy - 'scipy.optimize.show_options', # does not have much to doctest - 'scipy.signal.normalize', # manipulates warnings (XXX temp skip) - 'scipy.sparse.linalg.norm', # XXX temp skip -]) - -# these are affected by NumPy 2.0 scalar repr: they rely on string comparison -if np.__version__ < "2": - dt_config.skiplist.update(set([ - 'scipy.io.hb_read', - 'scipy.io.hb_write', - 'scipy.sparse.csgraph.connected_components', - 'scipy.sparse.csgraph.depth_first_order', - 'scipy.sparse.csgraph.shortest_path', - 'scipy.sparse.csgraph.floyd_warshall', - 'scipy.sparse.csgraph.dijkstra', - 'scipy.sparse.csgraph.bellman_ford', - 'scipy.sparse.csgraph.johnson', - 'scipy.sparse.csgraph.yen', - 'scipy.sparse.csgraph.breadth_first_order', - 'scipy.sparse.csgraph.reverse_cuthill_mckee', - 'scipy.sparse.csgraph.structural_rank', - 'scipy.sparse.csgraph.construct_dist_matrix', - 'scipy.sparse.csgraph.reconstruct_path', - 'scipy.ndimage.value_indices', - 'scipy.stats.mstats.describe', -])) - -# help pytest collection a bit: these names are either private (distributions), -# or just do not need doctesting. -dt_config.pytest_extra_ignore = [ - "scipy.stats.distributions", - "scipy.optimize.cython_optimize", - "scipy.test", - "scipy.show_config", -] - -dt_config.pytest_extra_xfail = { - # name: reason - "io.rst": "", - "ND_regular_grid.rst": "ReST parser limitation", - "extrapolation_examples.rst": "ReST parser limitation", - "sampling_pinv.rst": "__cinit__ unexpected argument", - "sampling_srou.rst": "nan in scalar_power", - "probability_distributions.rst": "integration warning", -} - -# tutorials -dt_config.pseudocode = set(['integrate.nquad(func,']) -dt_config.local_resources = {'io.rst': ["octave_a.mat"]} + + # tutorials + dt_config.pseudocode = set(['integrate.nquad(func,']) + dt_config.local_resources = {'io.rst': ["octave_a.mat"]} ############################################################################ From 6f4126d622f8c859755292571c618d617b038024 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 13 Apr 2024 15:12:57 +0300 Subject: [PATCH 180/500] TST: _lib: smoke-docs may use warnings --- scipy/_lib/tests/test_warnings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/_lib/tests/test_warnings.py b/scipy/_lib/tests/test_warnings.py index 8730d16e1ac0..aad199d4bc10 100644 --- a/scipy/_lib/tests/test_warnings.py +++ b/scipy/_lib/tests/test_warnings.py @@ -119,6 +119,7 @@ def test_warning_calls_filters(warning_calls): os.path.join('stats', '_continuous_distns.py'), os.path.join('stats', '_binned_statistic.py'), # gh-19345 os.path.join('_lib', '_util.py'), # gh-19341 + "conftest.py", ) bad_filters = [item for item in bad_filters if item.split(':')[0] not in allowed_filters] From 8f9f0d43bff54038a89a7f9a964eaae7650208c2 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 13 Apr 2024 15:21:56 +0300 Subject: [PATCH 181/500] MAINT: appease mypy for smoke-docs --- mypy.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy.ini b/mypy.ini index 943daa4d4742..4417af39dca8 100644 --- a/mypy.ini +++ b/mypy.ini @@ -222,6 +222,8 @@ ignore_missing_imports = True # Files with various errors. Likely some false positives, but likely # some real bugs too. # +[mypy-scipy.conftest] +ignore_errors = True [mypy-scipy.__config__] ignore_errors = True From 89bee46752f80812f9e115d66e13e22fb1d62140 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 13 Apr 2024 17:23:28 +0300 Subject: [PATCH 182/500] DOC: smoke-docs: adjust the plugin repo path/name --- .circleci/config.yml | 2 +- scipy/conftest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ccf2bb8cd849..717e4293b3e2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -184,7 +184,7 @@ jobs: no_output_timeout: 35m command: | pip install matplotlib hypothesis - pip install git+https://github.com/ev-br/scpdt.git@main + pip install git+https://github.com/scipy/scipy_doctest.git@main python dev.py smoke-docs - run: diff --git a/scipy/conftest.py b/scipy/conftest.py index 132991d89432..1c02bdd45dee 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -15,7 +15,7 @@ from scipy._lib._array_api import SCIPY_ARRAY_API, SCIPY_DEVICE try: - from scpdt.conftest import dt_config + from scipy_doctest.conftest import dt_config HAVE_SCPDT = True except ModuleNotFoundError: HAVE_SCPDT = False From 9c8e8c904e0865696a585381eaa2d88bbde96c3e Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 14 Apr 2024 10:28:23 +0300 Subject: [PATCH 183/500] MAINT: smoke-tutorials (plural) [docs only] --- .circleci/config.yml | 2 +- dev.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 717e4293b3e2..dfaeb4303323 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -191,7 +191,7 @@ jobs: name: smoke-tutorials no_output_timeout: 10m command: | - python dev.py smoke-tutorial + python dev.py smoke-tutorials # Upload build output to scipy/devdocs repository, using SSH deploy keys. # The keys are only available for builds on main branch. diff --git a/dev.py b/dev.py index 227dbd6ae9a7..a315d0821bb9 100644 --- a/dev.py +++ b/dev.py @@ -853,8 +853,8 @@ def run(cls, pytest_args, **kwargs): return cls.scipy_tests(args, pytest_args) -@cli.cls_cmd('smoke-tutorial') -class SmokeTutorial(Task): +@cli.cls_cmd('smoke-tutorials') +class SmokeTutorials(Task): """:wrench: Run smoke-tests on tutorial files.""" ctx = CONTEXT From 9044c1a3893cae09daf9f86249857180adb3d268 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 15 May 2024 11:53:57 +0300 Subject: [PATCH 184/500] DOC: update to the sparse repr change --- .../interpolate/splines_and_polynomials.rst | 4 ++-- scipy/sparse/csgraph/_traversal.pyx | 21 ------------------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/doc/source/tutorial/interpolate/splines_and_polynomials.rst b/doc/source/tutorial/interpolate/splines_and_polynomials.rst index f5318b4df2fc..d79cc534118a 100644 --- a/doc/source/tutorial/interpolate/splines_and_polynomials.rst +++ b/doc/source/tutorial/interpolate/splines_and_polynomials.rst @@ -273,8 +273,8 @@ and construct the design matrix in the sparse CSR format >>> from scipy.interpolate import BSpline >>> mat = BSpline.design_matrix(xnew, t, k=3) >>> mat -<3x7 sparse array of type '' - with 12 stored elements in Compressed Sparse Row format> + Here each row of the design matrix corresponds to a value in the ``xnew`` array, and a row has no more than ``k+1 = 4`` non-zero elements; row ``j`` diff --git a/scipy/sparse/csgraph/_traversal.pyx b/scipy/sparse/csgraph/_traversal.pyx index 715f65af2da9..32b77a1af801 100644 --- a/scipy/sparse/csgraph/_traversal.pyx +++ b/scipy/sparse/csgraph/_traversal.pyx @@ -75,20 +75,10 @@ def connected_components(csgraph, directed=True, connection='weak', ... ] >>> graph = csr_matrix(graph) >>> print(graph) -<<<<<<< HEAD - - Coords Values - (0, 1) 1 - (0, 2) 1 - (1, 2) 1 - (3, 4) 1 -======= (np.int32(0), np.int32(1)) 1 (np.int32(0), np.int32(2)) 1 (np.int32(1), np.int32(2)) 1 (np.int32(3), np.int32(4)) 1 ->>>>>>> DOC: sparse.csgraph: adjust docstrings to NumPy 2.0 scalar repr >>> n_components, labels = connected_components(csgraph=graph, directed=False, return_labels=True) >>> n_components @@ -548,22 +538,11 @@ cpdef depth_first_order(csgraph, i_start, ... ] >>> graph = csr_matrix(graph) >>> print(graph) -<<<<<<< HEAD - - Coords Values - (0, 1) 1 - (0, 2) 2 - (1, 3) 1 - (2, 0) 2 - (2, 3) 3 -======= (np.int32(0), np.int32(1)) 1 (np.int32(0), np.int32(2)) 2 (np.int32(1), np.int32(3)) 1 (np.int32(2), np.int32(0)) 2 (np.int32(2), np.int32(3)) 3 ->>>>>>> DOC: sparse.csgraph: adjust docstrings to NumPy 2.0 scalar repr >>> depth_first_order(graph,0) (array([0, 1, 3, 2], dtype=int32), array([-9999, 0, 0, 1], dtype=int32)) From 1b291ca06fbe9f7f7a95736af56587ee0acbdad1 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 15 May 2024 12:59:59 +0300 Subject: [PATCH 185/500] CI: install the released scipy-doctest [docs only] --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index dfaeb4303323..96cba7e6dc5d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -184,7 +184,7 @@ jobs: no_output_timeout: 35m command: | pip install matplotlib hypothesis - pip install git+https://github.com/scipy/scipy_doctest.git@main + pip install scipy-doctest python dev.py smoke-docs - run: From 18485a7a5b002188f17b219dc0c06520e319b1e7 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 13 Apr 2024 18:19:13 +0300 Subject: [PATCH 186/500] MAINT: remove doctesting from refguide-check Use $ python dev.py smoke-docs and dev.py smoke-tutorial instead Both ways of running doctests, smoke-docs and refguide-check, are avaiable in https://github.com/scipy/scipy/pull/20127. [docs only] --- dev.py | 3 +- tools/refguide_check.py | 495 +--------------------------------------- 2 files changed, 4 insertions(+), 494 deletions(-) diff --git a/dev.py b/dev.py index a315d0821bb9..6e72b95f3620 100644 --- a/dev.py +++ b/dev.py @@ -1243,8 +1243,7 @@ def task_meta(cls, **kwargs): dirs = Dirs(args) cmd = [f'{sys.executable}', - str(dirs.root / 'tools' / 'refguide_check.py'), - '--doctests'] + str(dirs.root / 'tools' / 'refguide_check.py')] if args.verbose: cmd += ['-vvv'] if args.submodule: diff --git a/tools/refguide_check.py b/tools/refguide_check.py index 77371ba706ee..c895a30dbf32 100755 --- a/tools/refguide_check.py +++ b/tools/refguide_check.py @@ -14,31 +14,17 @@ objects are left out of the refguide for a good reason (it's an alias of another function, or deprecated, or ...) -Another use of this helper script is to check validity of code samples -in docstrings. This is different from doctesting [we do not aim to have -scipy docstrings doctestable!], this is just to make sure that code in -docstrings is valid python:: - - $ python3 refguide_check.py --doctests optimize - """ import copy -import doctest -import glob import inspect import io import os import re -import shutil import sys -import tempfile import warnings from argparse import ArgumentParser -from contextlib import contextmanager, redirect_stderr -from doctest import NORMALIZE_WHITESPACE, ELLIPSIS, IGNORE_EXCEPTION_DETAIL import docutils.core -import numpy as np from docutils.parsers.rst import directives from numpydoc.docscrape_sphinx import get_doc_object @@ -100,45 +86,6 @@ 'io.arff': 'io', } -# these names are known to fail doctesting and we like to keep it that way -# e.g. sometimes pseudocode is acceptable etc -DOCTEST_SKIPLIST = set([ - 'scipy.stats.kstwobign', # inaccurate cdf or ppf - 'scipy.stats.levy_stable', - 'scipy.special.sinc', # comes from numpy - 'scipy.fft.fftfreq', - 'scipy.fft.rfftfreq', - 'scipy.fft.fftshift', - 'scipy.fft.ifftshift', - 'scipy.fftpack.fftfreq', - 'scipy.fftpack.fftshift', - 'scipy.fftpack.ifftshift', - 'scipy.integrate.trapezoid', - 'scipy.linalg.LinAlgError', - 'scipy.optimize.show_options', - 'io.rst', # XXX: need to figure out how to deal w/ mat files - # XXX: skips to match smoke-docs - 'scipy.stats.mstats.describe', - 'scipy.io.hb_read', - 'scipy.io.hb_write', - 'scipy.sparse.csgraph.connected_components', - 'scipy.sparse.csgraph.depth_first_order', - 'scipy.sparse.csgraph.shortest_path', - 'scipy.sparse.csgraph.floyd_warshall', - 'scipy.sparse.csgraph.dijkstra', - 'scipy.sparse.csgraph.bellman_ford', - 'scipy.sparse.csgraph.johnson', - 'scipy.sparse.csgraph.yen', - 'scipy.sparse.csgraph.breadth_first_order', - 'scipy.sparse.csgraph.reverse_cuthill_mckee', - 'scipy.sparse.csgraph.structural_rank', - 'scipy.sparse.csgraph.construct_dist_matrix', - 'scipy.sparse.csgraph.reconstruct_path', - 'scipy.ndimage.value_indices', - 'scipy.special.jve', - 'scipy.special.yve', -]) - # these names are not required to be present in ALL despite being in # autosummary:: listing REFGUIDE_ALL_SKIPLIST = [ @@ -171,8 +118,6 @@ 'kaiser', 'nuttall', 'parzen', 'triang', 'tukey'): REFGUIDE_AUTOSUMMARY_SKIPLIST.append(r'scipy\.signal\.' + name) -HAVE_MATPLOTLIB = False - def short_path(path, cwd=None): """ @@ -499,405 +444,6 @@ def check_rest(module, names, dots=True): return results -### Doctest helpers #### - -# the namespace to run examples in -DEFAULT_NAMESPACE = {} - -# the namespace to do checks in -CHECK_NAMESPACE = { - 'np': np, - 'assert_allclose': np.testing.assert_allclose, - 'assert_equal': np.testing.assert_equal, - # recognize numpy repr's - 'array': np.array, - 'matrix': np.matrix, - 'masked_array': np.ma.masked_array, - 'int64': np.int64, - 'uint64': np.uint64, - 'int8': np.int8, - 'int32': np.int32, - 'float32': np.float32, - 'float64': np.float64, - 'dtype': np.dtype, - 'nan': np.nan, - 'NaN': np.nan, - 'inf': np.inf, - 'Inf': np.inf,} - - -def try_convert_namedtuple(got): - # suppose that "got" is smth like MoodResult(statistic=10, pvalue=0.1). - # Then convert it to the tuple (10, 0.1), so that can later compare tuples. - num = got.count('=') - if num == 0: - # not a nameduple, bail out - return got - regex = (r'[\w\d_]+\(' + - ', '.join([r'[\w\d_]+=(.+)']*num) + - r'\)') - grp = re.findall(regex, " ".join(got.split())) - # fold it back to a tuple - got_again = '(' + ', '.join(grp[0]) + ')' - return got_again - - -class DTRunner(doctest.DocTestRunner): - DIVIDER = "\n" - - def __init__(self, item_name, checker=None, verbose=None, optionflags=0): - self._item_name = item_name - self._had_unexpected_error = False - doctest.DocTestRunner.__init__(self, checker=checker, verbose=verbose, - optionflags=optionflags) - - def _report_item_name(self, out, new_line=False): - if self._item_name is not None: - if new_line: - out("\n") - self._item_name = None - - def report_start(self, out, test, example): - self._checker._source = example.source - return doctest.DocTestRunner.report_start(self, out, test, example) - - def report_success(self, out, test, example, got): - if self._verbose: - self._report_item_name(out, new_line=True) - return doctest.DocTestRunner.report_success(self, out, test, example, got) - - def report_unexpected_exception(self, out, test, example, exc_info): - # Ignore name errors after failing due to an unexpected exception - exception_type = exc_info[0] - if self._had_unexpected_error and exception_type is NameError: - return - self._had_unexpected_error = True - - self._report_item_name(out) - return super().report_unexpected_exception( - out, test, example, exc_info) - - def report_failure(self, out, test, example, got): - self._report_item_name(out) - return doctest.DocTestRunner.report_failure(self, out, test, - example, got) - - -class Checker(doctest.OutputChecker): - obj_pattern = re.compile(r'at 0x[0-9a-fA-F]+>') - vanilla = doctest.OutputChecker() - rndm_markers = {'# random', '# Random', '#random', '#Random', "# may vary"} - stopwords = {'plt.', '.hist', '.show', '.ylim', '.subplot(', - 'set_title', 'imshow', 'plt.show', '.axis(', '.plot(', - '.bar(', '.title', '.ylabel', '.xlabel', 'set_ylim', 'set_xlim', - '# reformatted', '.set_xlabel(', '.set_ylabel(', '.set_zlabel(', - '.set(xlim=', '.set(ylim=', '.set(xlabel=', '.set(ylabel='} - - def __init__(self, parse_namedtuples=True, ns=None, atol=1e-8, rtol=1e-2): - self.parse_namedtuples = parse_namedtuples - self.atol, self.rtol = atol, rtol - if ns is None: - self.ns = dict(CHECK_NAMESPACE) - else: - self.ns = ns - - def check_output(self, want, got, optionflags): - # cut it short if they are equal - if want == got: - return True - - # skip stopwords in source - if any(word in self._source for word in self.stopwords): - return True - - # skip random stuff - if any(word in want for word in self.rndm_markers): - return True - - # skip function/object addresses - if self.obj_pattern.search(got): - return True - - # ignore comments (e.g. signal.freqresp) - if want.lstrip().startswith("#"): - return True - - # try the standard doctest - try: - if self.vanilla.check_output(want, got, optionflags): - return True - except Exception: - pass - - # OK then, convert strings to objects - try: - a_want = eval(want, dict(self.ns)) - a_got = eval(got, dict(self.ns)) - except Exception: - # Maybe we're printing a numpy array? This produces invalid python - # code: `print(np.arange(3))` produces "[0 1 2]" w/o commas between - # values. So, reinsert commas and retry. - # TODO: handle (1) abbreviation (`print(np.arange(10000))`), and - # (2) n-dim arrays with n > 1 - s_want = want.strip() - s_got = got.strip() - cond = (s_want.startswith("[") and s_want.endswith("]") and - s_got.startswith("[") and s_got.endswith("]")) - if cond: - s_want = ", ".join(s_want[1:-1].split()) - s_got = ", ".join(s_got[1:-1].split()) - return self.check_output(s_want, s_got, optionflags) - - # maybe we are dealing with masked arrays? - # their repr uses '--' for masked values and this is invalid syntax - # If so, replace '--' by nans (they are masked anyway) and retry - if 'masked_array' in want or 'masked_array' in got: - s_want = want.replace('--', 'nan') - s_got = got.replace('--', 'nan') - return self.check_output(s_want, s_got, optionflags) - - if "=" not in want and "=" not in got: - # if we're here, want and got cannot be eval-ed (hence cannot - # be converted to numpy objects), they are not namedtuples - # (those must have at least one '=' sign). - # Thus they should have compared equal with vanilla doctest. - # Since they did not, it's an error. - return False - - if not self.parse_namedtuples: - return False - # suppose that "want" is a tuple, and "got" is smth like - # MoodResult(statistic=10, pvalue=0.1). - # Then convert the latter to the tuple (10, 0.1), - # and then compare the tuples. - try: - got_again = try_convert_namedtuple(got) - want_again = try_convert_namedtuple(want) - except Exception: - return False - else: - return self.check_output(want_again, got_again, optionflags) - - # ... and defer to numpy - try: - return self._do_check(a_want, a_got) - except Exception: - # heterog tuple, eg (1, np.array([1., 2.])) - try: - return all(self._do_check(w, g) for w, g in zip(a_want, a_got)) - except (TypeError, ValueError): - return False - - def _do_check(self, want, got): - # This should be done exactly as written to correctly handle all of - # numpy-comparable objects, strings, and heterogeneous tuples - try: - if want == got: - return True - except Exception: - pass - return np.allclose(want, got, atol=self.atol, rtol=self.rtol, equal_nan=True) - - -def _run_doctests(tests, full_name, verbose, doctest_warnings): - """Run modified doctests for the set of `tests`. - - Returns: list of [(success_flag, output), ...] - """ - flags = NORMALIZE_WHITESPACE | ELLIPSIS | IGNORE_EXCEPTION_DETAIL - runner = DTRunner(full_name, checker=Checker(), optionflags=flags, - verbose=verbose) - - output = io.StringIO(newline='') - success = True - # Redirect stderr to the stdout or output - tmp_stderr = sys.stdout if doctest_warnings else output - from scipy._lib._util import _fixed_default_rng - - @contextmanager - def temp_cwd(): - cwd = os.getcwd() - tmpdir = tempfile.mkdtemp() - try: - os.chdir(tmpdir) - yield tmpdir - finally: - os.chdir(cwd) - shutil.rmtree(tmpdir) - - # Run tests, trying to restore global state afterward - cwd = os.getcwd() - with np.errstate(), np.printoptions(), temp_cwd(), \ - redirect_stderr(tmp_stderr), \ - _fixed_default_rng(): - # try to ensure random seed is NOT reproducible - np.random.seed(None) - - for t in tests: - t.filename = short_path(t.filename, cwd) - fails, successes = runner.run(t, out=output.write) - if fails > 0: - success = False - - output.seek(0) - return success, output.read() - - -def check_doctests(module, verbose, ns=None, - dots=True, doctest_warnings=False): - """Check code in docstrings of the module's public symbols. - - Returns: list of [(item_name, success_flag, output), ...] - """ - if ns is None: - ns = dict(DEFAULT_NAMESPACE) - - # Loop over non-deprecated items - results = [] - - for name in get_all_dict(module)[0]: - full_name = module.__name__ + '.' + name - - if full_name in DOCTEST_SKIPLIST: - continue - - try: - obj = getattr(module, name) - except AttributeError: - import traceback - results.append((full_name, False, - "Missing item!\n" + - traceback.format_exc())) - continue - - finder = doctest.DocTestFinder() - try: - tests = finder.find(obj, name, globs=dict(ns)) - except Exception: - import traceback - results.append((full_name, False, - "Failed to get doctests!\n" + - traceback.format_exc())) - continue - - success, output = _run_doctests(tests, full_name, verbose, - doctest_warnings) - - if dots: - output_dot('.' if success else 'F') - - results.append((full_name, success, output)) - - if HAVE_MATPLOTLIB: - import matplotlib.pyplot as plt - plt.close('all') - - return results - - -def check_doctests_testfile(fname, verbose, ns=None, - dots=True, doctest_warnings=False): - """Check code in a text file. - - Mimic `check_doctests` above, differing mostly in test discovery. - (which is borrowed from stdlib's doctest.testfile here, - https://github.com/python-git/python/blob/master/Lib/doctest.py) - - Returns: list of [(item_name, success_flag, output), ...] - - Notes - ----- - - refguide can be signalled to skip testing code by adding - ``#doctest: +SKIP`` to the end of the line. If the output varies or is - random, add ``# may vary`` or ``# random`` to the comment. for example - - >>> plt.plot(...) # doctest: +SKIP - >>> random.randint(0,10) - 5 # random - - We also try to weed out pseudocode: - * We maintain a list of exceptions which signal pseudocode, - * We split the text file into "blocks" of code separated by empty lines - and/or intervening text. - * If a block contains a marker, the whole block is then assumed to be - pseudocode. It is then not being doctested. - - The rationale is that typically, the text looks like this: - - blah - - >>> from numpy import some_module # pseudocode! - >>> func = some_module.some_function - >>> func(42) # still pseudocode - 146 - - blah - - >>> 2 + 3 # real code, doctest it - 5 - - """ - results = [] - - if ns is None: - ns = dict(DEFAULT_NAMESPACE) - - _, short_name = os.path.split(fname) - if short_name in DOCTEST_SKIPLIST: - return results - - full_name = fname - with open(fname, encoding='utf-8') as f: - text = f.read() - - PSEUDOCODE = set(['some_function', 'some_module', 'import example', - 'ctypes.CDLL', # likely need compiling, skip it - 'integrate.nquad(func,' # ctypes integrate tutotial - ]) - - # split the text into "blocks" and try to detect and omit pseudocode blocks. - parser = doctest.DocTestParser() - good_parts = [] - for part in text.split('\n\n'): - tests = parser.get_doctest(part, ns, fname, fname, 0) - if any(word in ex.source for word in PSEUDOCODE - for ex in tests.examples): - # omit it - pass - else: - # `part` looks like a good code, let's doctest it - good_parts += [part] - - # Reassemble the good bits and doctest them: - good_text = '\n\n'.join(good_parts) - tests = parser.get_doctest(good_text, ns, fname, fname, 0) - success, output = _run_doctests([tests], full_name, verbose, - doctest_warnings) - - if dots: - output_dot('.' if success else 'F') - - results.append((full_name, success, output)) - - if HAVE_MATPLOTLIB: - import matplotlib.pyplot as plt - plt.close('all') - - return results - - -def init_matplotlib(): - global HAVE_MATPLOTLIB - - try: - import matplotlib - matplotlib.use('Agg') - HAVE_MATPLOTLIB = True - except ImportError: - HAVE_MATPLOTLIB = False - - def check_dist_keyword_names(): # Look for collisions between names of distribution shape parameters and # keywords of distribution methods. See gh-5982. @@ -954,20 +500,13 @@ def main(argv): parser = ArgumentParser(usage=__doc__.lstrip()) parser.add_argument("module_names", metavar="SUBMODULES", default=[], nargs='*', help="Submodules to check (default: all public)") - parser.add_argument("--doctests", action="store_true", help="Run also doctests") parser.add_argument("-v", "--verbose", action="count", default=0) - parser.add_argument("--doctest-warnings", action="store_true", - help="Enforce warning checking for doctests") - parser.add_argument("--skip-tutorial", action="store_true", - help="Skip running doctests in the tutorial.") args = parser.parse_args(argv) modules = [] names_dict = {} - if args.module_names: - args.skip_tutorial = True - else: + if not args.module_names: args.module_names = list(PUBLIC_SUBMODULES) os.environ['SCIPY_PIL_IMAGE_VIEWER'] = 'true' @@ -1001,9 +540,6 @@ def main(argv): print("Running checks for %d modules:" % (len(modules),)) - if args.doctests or not args.skip_tutorial: - init_matplotlib() - for module in modules: if dots: if module is not modules[0]: @@ -1018,9 +554,6 @@ def main(argv): mod_results += check_items(all_dict, names, deprecated, others, module.__name__) mod_results += check_rest(module, set(names).difference(deprecated), dots=dots) - if args.doctests: - mod_results += check_doctests(module, (args.verbose >= 2), dots=dots, - doctest_warnings=args.doctest_warnings) if module.__name__ == 'scipy.stats': mod_results += check_dist_keyword_names() @@ -1033,28 +566,6 @@ def main(argv): sys.stderr.write("\n") sys.stderr.flush() - if not args.skip_tutorial: - base_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..') - tut_path = os.path.join(base_dir, 'doc', 'source', 'tutorial', '*.rst') - print(f'\nChecking tutorial files at {os.path.relpath(tut_path, os.getcwd())}:') - for filename in sorted(glob.glob(tut_path)): - if dots: - sys.stderr.write('\n') - sys.stderr.write(os.path.split(filename)[1] + ' ') - sys.stderr.flush() - - tut_results = check_doctests_testfile(filename, (args.verbose >= 2), - dots=dots, doctest_warnings=args.doctest_warnings) - - def scratch(): - pass # stub out a "module", see below - scratch.__name__ = filename - results.append((scratch, tut_results)) - - if dots: - sys.stderr.write("\n") - sys.stderr.flush() - # Report results all_success = True @@ -1084,10 +595,10 @@ def scratch(): print("") if all_success: - print("\nOK: refguide and doctests checks passed!") + print("\nOK: refguide checks passed!") sys.exit(0) else: - print("\nERROR: refguide or doctests have errors") + print("\nERROR: refguide have errors") sys.exit(1) From 8ffb59f2e3e75cf04c83edf7b3d4136bc176b038 Mon Sep 17 00:00:00 2001 From: Zhibing Sun Date: Wed, 15 May 2024 20:09:10 -0400 Subject: [PATCH 187/500] Issue #20697 resolved. --- scipy/special/special/cephes/polevl.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scipy/special/special/cephes/polevl.h b/scipy/special/special/cephes/polevl.h index 07a591cad5fd..8410262e1e88 100644 --- a/scipy/special/special/cephes/polevl.h +++ b/scipy/special/special/cephes/polevl.h @@ -60,6 +60,7 @@ /* Scipy changes: * - 06-23-2016: add code for evaluating rational functions */ + #pragma once #include "../config.h" @@ -115,6 +116,7 @@ namespace cephes { /* Evaluate a rational function. See [1]. */ + /* The function ratevl is only used once in cephes/lanczos.h. */ SPECFUN_HOST_DEVICE inline double ratevl(double x, const double num[], int M, const double denom[], int N) { int i, dir; double y, num_ans, denom_ans; @@ -155,7 +157,7 @@ namespace cephes { } if (absx > 1) { - i = N - M; + i = M - N; return std::pow(x, i) * num_ans / denom_ans; } else { return num_ans / denom_ans; From f2d4775e7762fad984f8f0acd8227c725ff21630 Mon Sep 17 00:00:00 2001 From: Daniel Schmitz <40656107+dschmitz89@users.noreply.github.com> Date: Thu, 16 May 2024 06:28:44 +0200 Subject: [PATCH 188/500] TST: fix CI failures in `test_all_nograd_minimizers` (#20699) * TST: reduce `niter`for COBYQA * TST: optimize: make test duration exception --- scipy/optimize/tests/test__basinhopping.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scipy/optimize/tests/test__basinhopping.py b/scipy/optimize/tests/test__basinhopping.py index 88dbda3dc1d6..9350d533c680 100644 --- a/scipy/optimize/tests/test__basinhopping.py +++ b/scipy/optimize/tests/test__basinhopping.py @@ -212,6 +212,7 @@ def test_all_minimizers(self): niter=self.niter, disp=self.disp) assert_almost_equal(res.x, self.sol[i], self.tol) + @pytest.mark.fail_slow(10) def test_all_nograd_minimizers(self): # Test 2-D minimizations without gradient. Newton-CG requires jac=True, # so not included here. @@ -220,10 +221,12 @@ def test_all_nograd_minimizers(self): 'Nelder-Mead', 'Powell', 'COBYLA', 'COBYQA'] minimizer_kwargs = copy.copy(self.kwargs_nograd) for method in methods: + # COBYQA takes extensive amount of time on this problem + niter = 10 if method == 'COBYQA' else self.niter minimizer_kwargs["method"] = method res = basinhopping(func2d_nograd, self.x0[i], minimizer_kwargs=minimizer_kwargs, - niter=self.niter, disp=self.disp) + niter=niter, disp=self.disp) tol = self.tol if method == 'COBYLA': tol = 2 From e5400cb888349403f0e6d4a5dced1de5f1bb52c4 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 16 May 2024 10:49:15 -0600 Subject: [PATCH 189/500] CI, MAINT: pin Python for MacOS conda * We're seeing CI failures related to an undesirable bump to Python `3.12` in this job, when the intention was clearly to respect the Python version specific in the GHA matrix. I didn't check too closely why exactly it suddenly started happening, but some packages weren't ready for `3.12` yet on this job (`scikit-umfpack` in particular) and I don't see too much harm in adding an extra pin to respect the intention for the Python version. [skip cirrus] [skip circle] --- .github/workflows/macos.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 3007356aef5b..5c7ae992f287 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -110,6 +110,7 @@ jobs: shell: bash -l {0} run: | conda activate scipy-dev + mamba install python=${{ matrix.python-version}} # optional test dependencies mamba install scikit-umfpack scikit-sparse From 224dea59b25fe65a7be9022ec0dd257da70d2fc1 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 16 May 2024 14:46:20 -0400 Subject: [PATCH 190/500] MAINT: stats.wilcoxon: fix bug with Ndim>1, shape[axis]>50, NaN, 'auto' (#20592) --- scipy/stats/_wilcoxon.py | 7 ++++--- scipy/stats/tests/test_morestats.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/scipy/stats/_wilcoxon.py b/scipy/stats/_wilcoxon.py index 555496461c1c..2fbdfe148031 100644 --- a/scipy/stats/_wilcoxon.py +++ b/scipy/stats/_wilcoxon.py @@ -102,6 +102,7 @@ def _wilcoxon_iv(x, y, zero_method, correction, alternative, method, axis): "an instance of `stats.PermutationMethod`.") if method not in methods: raise ValueError(message) + output_z = True if method == 'approx' else False # logic unchanged here for backward compatibility n_zero = np.sum(d == 0, axis=-1) @@ -127,7 +128,7 @@ def _wilcoxon_iv(x, y, zero_method, correction, alternative, method, axis): if 0 < d.shape[-1] < 10 and method == "approx": warnings.warn("Sample size too small for normal approximation.", stacklevel=2) - return d, zero_method, correction, alternative, method, axis + return d, zero_method, correction, alternative, method, axis, output_z def _wilcoxon_statistic(d, zero_method='wilcox'): @@ -196,7 +197,7 @@ 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 = temp + d, zero_method, correction, alternative, method, axis, output_z = temp if d.size == 0: NaN = _get_nan(d) @@ -232,6 +233,6 @@ def _wilcoxon_nd(x, y=None, zero_method='wilcox', correction=True, z = -np.abs(z) if (alternative == 'two-sided' and method == 'approx') else z res = _morestats.WilcoxonResult(statistic=statistic, pvalue=p[()]) - if method == 'approx': + if output_z: res.zstatistic = z[()] return res diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 04e3c35bab8a..699f930b01db 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -1664,6 +1664,22 @@ def test_permutation_method(self, size): assert_equal(np.round(res.pvalue, 2), res.pvalue) # n_resamples used assert_equal(res.pvalue, ref.pvalue) # random_state used + def test_method_auto_nan_propagate_ND_length_gt_50_gh20591(self): + # When method!='approx', 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 + res = stats.wilcoxon(A) + ref = stats.wilcoxon(A, method='approx') + assert_allclose(res, ref) + assert hasattr(ref, 'zstatistic') + assert not hasattr(res, 'zstatistic') + # data for k-statistics tests from # https://cran.r-project.org/web/packages/kStatistics/kStatistics.pdf From a3fd88e9356a96714a3d5648a0217f49555175b4 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 17 May 2024 16:31:39 -0400 Subject: [PATCH 191/500] ENH: stats.normaltest: add array-API support --- scipy/stats/_stats_py.py | 11 +++++++++-- scipy/stats/tests/test_stats.py | 28 +++++++++++++++------------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 2c9215c4c689..cd9d1715da78 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -2001,9 +2001,16 @@ def normaltest(a, axis=0, nan_policy='propagate'): """ s, _ = skewtest(a, axis) k, _ = kurtosistest(a, axis) - k2 = s*s + k*k + Z = s*s + k*k - return NormaltestResult(k2, distributions.chi2.sf(k2, 2)) + xp = array_namespace(Z) + Z_np = np.asarray(Z) + pvalue = distributions.chi2.sf(Z_np, 2) + pvalue = xp.asarray(pvalue, dtype=Z.dtype) + Z = Z[()] if Z.ndim == 0 else Z + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue + + return NormaltestResult(Z, pvalue) @_axis_nan_policy_factory(SignificanceResult, default_axis=None) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 719c1a5513e8..5936888bb118 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6143,10 +6143,10 @@ def test_normalitytests(xp): # test_result <- dagoTest(x) # test_result@test$statistic # test_result@test$p.value - st_normal, st_skew, st_kurt = (3.92371918158185551, + st_normal, st_skew, st_kurt = (xp.asarray(3.92371918158185551), xp.asarray(1.98078826090875881), xp.asarray(-0.01403734404759738)) - pv_normal, pv_skew, pv_kurt = (0.14059672529747502, + pv_normal, pv_skew, pv_kurt = (xp.asarray(0.14059672529747502), xp.asarray(0.04761502382843208), xp.asarray(0.98880018772590561)) pv_skew_less, pv_kurt_less = 1 - pv_skew / 2, pv_kurt / 2 @@ -6155,7 +6155,9 @@ def test_normalitytests(xp): x_xp = xp.asarray((-2, -1, 0, 1, 2, 3.)*4)**2 attributes = ('statistic', 'pvalue') - assert_array_almost_equal(stats.normaltest(x), (st_normal, pv_normal)) + res = stats.normaltest(x_xp) + xp_assert_close(res.statistic, st_normal) + xp_assert_close(res.pvalue, pv_normal) check_named_results(stats.normaltest(x), attributes) res = stats.skewtest(x_xp) @@ -6195,8 +6197,10 @@ def test_normalitytests(xp): xp_assert_close(pval, xp.asarray(0.0, dtype=a2_xp.dtype), atol=1e-15) # Test axis=None (equal to axis=0 for 1-D input) - assert_array_almost_equal(stats.normaltest(x, axis=None), - (st_normal, pv_normal)) + res = stats.normaltest(x_xp, axis=None) + xp_assert_close(res.statistic, st_normal) + xp_assert_close(res.pvalue, pv_normal) + res = stats.skewtest(x_xp, axis=None) xp_assert_close(res.statistic, st_skew) xp_assert_close(res.pvalue, pv_skew) @@ -6205,21 +6209,22 @@ def test_normalitytests(xp): xp_assert_close(res.statistic, st_kurt) xp_assert_close(res.pvalue, pv_kurt) - x = xp.arange(10.) + x = xp.arange(30.) NaN = xp.asarray(xp.nan, dtype=x.dtype) - x[9] = NaN + x[29] = NaN with np.errstate(invalid="ignore"): res = stats.skewtest(x) xp_assert_equal(res.statistic, NaN) xp_assert_equal(res.pvalue, NaN) - x = xp.arange(30.) - x[29] = NaN - with np.errstate(all='ignore'): res = stats.kurtosistest(x) xp_assert_equal(res.statistic, NaN) xp_assert_equal(res.pvalue, NaN) + res = stats.normaltest(x) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + # nan_policy only compatible with NumPy arrays x = np.arange(10.) x[9] = np.nan @@ -6267,9 +6272,6 @@ def test_normalitytests(xp): assert_raises(ValueError, stats.kurtosistest, list(range(20)), alternative='foobar') - with np.errstate(all='ignore'): - assert_array_equal(stats.normaltest(x), (np.nan, np.nan)) - expected = (6.2260409514287449, 0.04446644248650191) assert_array_almost_equal(stats.normaltest(x, nan_policy='omit'), expected) From 4fd1c0894bd681c1f266bf0edddedabc51e58151 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 17 May 2024 21:46:54 -0400 Subject: [PATCH 192/500] ENH: stats.jarque_bera: add array API support --- scipy/stats/_stats_py.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index cd9d1715da78..ec0c2c6436d0 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -2158,23 +2158,29 @@ def jarque_bera(x, *, axis=None): hypothesis [4]_. """ - x = np.asarray(x) + xp = array_namespace(x) + x = xp.asarray(x) if axis is None: - x = x.ravel() + x = xp.reshape(x, (-1,)) axis = 0 n = x.shape[axis] if n == 0: raise ValueError('At least one observation is required.') - mu = x.mean(axis=axis, keepdims=True) + 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) - statistic = n / 6 * (s**2 + k**2 / 4) - pvalue = distributions.chi2.sf(statistic, df=2) + k2 = n / 6 * (s**2 + k**2 / 4) + + k2_np = np.asarray(k2) + pvalue = distributions.chi2.sf(k2_np, df=2) + pvalue = xp.asarray(pvalue, dtype=k2.dtype) + k2 = k2[()] if k2.ndim == 0 else k2 + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue - return SignificanceResult(statistic, pvalue) + return SignificanceResult(k2, pvalue) ##################################### From 0f5fcb91ead2e03b716f3b07d5fb04d029d7fdda Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 17 May 2024 22:19:10 -0400 Subject: [PATCH 193/500] TST: stats.jarque_bera: convert tests to array API --- scipy/stats/tests/test_stats.py | 63 ++++++++++++++++----------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 5936888bb118..64edb5997712 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6310,31 +6310,22 @@ def test_input_validation(self): class TestJarqueBera: - def test_jarque_bera_stats(self): - np.random.seed(987654321) - x = np.random.normal(0, 1, 100000) - y = np.random.chisquare(10000, 100000) - z = np.random.rayleigh(1, 100000) - - assert_equal(stats.jarque_bera(x)[0], stats.jarque_bera(x).statistic) - assert_equal(stats.jarque_bera(x)[1], stats.jarque_bera(x).pvalue) - - assert_equal(stats.jarque_bera(y)[0], stats.jarque_bera(y).statistic) - assert_equal(stats.jarque_bera(y)[1], stats.jarque_bera(y).pvalue) - - assert_equal(stats.jarque_bera(z)[0], stats.jarque_bera(z).statistic) - assert_equal(stats.jarque_bera(z)[1], stats.jarque_bera(z).pvalue) - - assert_(stats.jarque_bera(x)[1] > stats.jarque_bera(y)[1]) - assert_(stats.jarque_bera(x).pvalue > stats.jarque_bera(y).pvalue) - - assert_(stats.jarque_bera(x)[1] > stats.jarque_bera(z)[1]) - assert_(stats.jarque_bera(x).pvalue > stats.jarque_bera(z).pvalue) - - assert_(stats.jarque_bera(y)[1] > stats.jarque_bera(z)[1]) - assert_(stats.jarque_bera(y).pvalue > stats.jarque_bera(z).pvalue) + @array_api_compatible + 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]) def test_jarque_bera_array_like(self): + # array-like only relevant for NumPy np.random.seed(987654321) x = np.random.normal(0, 1, 100000) @@ -6345,24 +6336,32 @@ def test_jarque_bera_array_like(self): 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_size(self): - assert_raises(ValueError, stats.jarque_bera, []) + @array_api_compatible + def test_jarque_bera_size(self, xp): + x = xp.asarray([]) + message = "At least one observation is required." + with pytest.raises(ValueError, match=message): + stats.jarque_bera(x) - def test_axis(self): + @array_api_compatible + def test_axis(self, xp): rng = np.random.RandomState(seed=122398129) - x = rng.random(size=(2, 45)) + x = xp.asarray(rng.random(size=(2, 45))) - assert_equal(stats.jarque_bera(x, axis=None), - stats.jarque_bera(x.ravel())) + 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, :]) - assert_allclose(res.statistic, [s0, s1]) - assert_allclose(res.pvalue, [p0, p1]) + xp_assert_close(res.statistic, xp.asarray([s0, s1])) + xp_assert_close(res.pvalue, xp.asarray([p0, p1])) resT = stats.jarque_bera(x.T, axis=0) - assert_allclose(res, resT) + xp_assert_close(res.statistic, resT.statistic) + xp_assert_close(res.pvalue, resT.pvalue) def test_skewtest_too_few_samples(): From abf320c88232fc927b10fff96f4a71b536006fc6 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 17 May 2024 22:24:01 -0400 Subject: [PATCH 194/500] TST: stats.skewtest: convert remaining test to array API --- scipy/stats/tests/test_stats.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 64edb5997712..2e60cb532dc8 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6364,11 +6364,14 @@ def test_axis(self, xp): xp_assert_close(res.pvalue, resT.pvalue) -def test_skewtest_too_few_samples(): +@array_api_compatible +def test_skewtest_too_few_samples(xp): # Regression test for ticket #1492. # skewtest requires at least 8 samples; 7 should raise a ValueError. - x = np.arange(7.0) - assert_raises(ValueError, stats.skewtest, x) + x = xp.arange(7.0) + message = 'skewtest is not valid with less than 8 samples...' + with pytest.raises(ValueError, match=message): + stats.skewtest(x) @array_api_compatible From 232c1c9346d7d761bbc7ddbab225cdcb8ef299ca Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 17 May 2024 22:26:38 -0400 Subject: [PATCH 195/500] MAINT: stats.normaltest: use original variable name --- scipy/stats/_stats_py.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index ec0c2c6436d0..9f24afd7bb18 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -2001,16 +2001,16 @@ def normaltest(a, axis=0, nan_policy='propagate'): """ s, _ = skewtest(a, axis) k, _ = kurtosistest(a, axis) - Z = s*s + k*k + k2 = s*s + k*k - xp = array_namespace(Z) - Z_np = np.asarray(Z) - pvalue = distributions.chi2.sf(Z_np, 2) - pvalue = xp.asarray(pvalue, dtype=Z.dtype) - Z = Z[()] if Z.ndim == 0 else Z + xp = array_namespace(k2) + k2_np = np.asarray(k2) + pvalue = distributions.chi2.sf(k2_np, 2) + pvalue = xp.asarray(pvalue, dtype=k2.dtype) + k2 = k2[()] if k2.ndim == 0 else k2 pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue - return NormaltestResult(Z, pvalue) + return NormaltestResult(k2, pvalue) @_axis_nan_policy_factory(SignificanceResult, default_axis=None) From 42b28059cd843ebf746134f4613c974cd1817283 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 18 May 2024 03:19:57 -0600 Subject: [PATCH 196/500] TST, MAINT: run optimize array API tests and fix `chandrupatla` (#20737) Addresses some of my points at: https://github.com/scipy/scipy/pull/20085#pullrequestreview-2064596756 and seems to fix about 55 GPU-based array API test failures Co-authored-by: Matt Haberland --- .github/workflows/array_api.yml | 1 + scipy/optimize/_chandrupatla.py | 2 +- scipy/optimize/_tstutils.py | 10 +++++++--- scipy/optimize/tests/test_chandrupatla.py | 10 +++++++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 389dad418070..601706e86369 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -100,3 +100,4 @@ jobs: python dev.py --no-build test -b all -t scipy.stats.tests.test_morestats -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.stats.tests.test_variation -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.stats.tests.test_resampling -- --durations 3 --timeout=60 + python dev.py --no-build test -b all -t scipy.optimize.tests.test_chandrupatla -- --durations 3 --timeout=60 diff --git a/scipy/optimize/_chandrupatla.py b/scipy/optimize/_chandrupatla.py index 9a7ed1952db0..f1759d6191db 100644 --- a/scipy/optimize/_chandrupatla.py +++ b/scipy/optimize/_chandrupatla.py @@ -187,7 +187,7 @@ def check_termination(work): # If the bracket is no longer valid, report failure (unless a function # tolerance is met, as detected above). i = (xp_sign(work.f1) == xp_sign(work.f2)) & ~stop - NaN = xp.asarray(xp.nan) + NaN = xp.asarray(xp.nan, dtype=work.xmin.dtype) work.xmin[i], work.fmin[i], work.status[i] = NaN, NaN, eim._ESIGNERR stop[i] = True diff --git a/scipy/optimize/_tstutils.py b/scipy/optimize/_tstutils.py index 344c6764a374..f56e835e345d 100644 --- a/scipy/optimize/_tstutils.py +++ b/scipy/optimize/_tstutils.py @@ -44,6 +44,7 @@ import numpy as np from scipy.optimize import _zeros_py as cc +from scipy._lib._array_api import array_namespace # "description" refers to the original functions description = """ @@ -887,18 +888,21 @@ def fun6(x): def fun7(x): - return 0 if abs(x) < 3.8e-4 else x*np.exp(-x**(-2)) + xp = array_namespace(x) + return 0 if xp.abs(x) < 3.8e-4 else x*xp.exp(-x**(-2)) fun7.root = 0 def fun8(x): + xp = array_namespace(x) xi = 0.61489 - return -(3062*(1-xi)*np.exp(-x))/(xi + (1-xi)*np.exp(-x)) - 1013 + 1628/x + return -(3062*(1-xi)*xp.exp(-x))/(xi + (1-xi)*xp.exp(-x)) - 1013 + 1628/x fun8.root = 1.0375360332870405 def fun9(x): - return np.exp(x) - 2 - 0.01/x**2 + .000002/x**3 + xp = array_namespace(x) + return xp.exp(x) - 2 - 0.01/x**2 + .000002/x**3 fun9.root = 0.7032048403631358 # Each "chandropatla" test case has diff --git a/scipy/optimize/tests/test_chandrupatla.py b/scipy/optimize/tests/test_chandrupatla.py index c36346947348..97b3894b4453 100644 --- a/scipy/optimize/tests/test_chandrupatla.py +++ b/scipy/optimize/tests/test_chandrupatla.py @@ -655,11 +655,11 @@ def test_convergence(self, xp): x1, x2 = bracket f0 = xp_minimum(xp.abs(self.f(x1, *args)), xp.abs(self.f(x2, *args))) res1 = _chandrupatla_root(self.f, *bracket, **kwargs) - xp_assert_less(np.abs(res1.fun), 1e-3*f0) + xp_assert_less(xp.abs(res1.fun), 1e-3*f0) kwargs['frtol'] = 1e-6 res2 = _chandrupatla_root(self.f, *bracket, **kwargs) - xp_assert_less(np.abs(res2.fun), 1e-6*f0) - xp_assert_less(np.abs(res2.fun), np.abs(res1.fun)) + xp_assert_less(xp.abs(res2.fun), 1e-6*f0) + xp_assert_less(xp.abs(res2.fun), xp.abs(res1.fun)) def test_maxiter_callback(self, xp): # Test behavior of `maxiter` parameter and `callback` interface @@ -741,6 +741,10 @@ def test_nit_expected(self, case, xp): @pytest.mark.parametrize("dtype", ('float16', 'float32', 'float64')) def test_dtype(self, root, dtype, xp): # Test that dtypes are preserved + not_numpy = not is_numpy(xp) + if not_numpy and dtype == 'float16': + pytest.skip("`float16` dtype only supported for NumPy arrays.") + dtype = getattr(xp, dtype, None) if dtype is None: pytest.skip(f"{xp} does not support {dtype}") From 7192a1c0ebdf461c5bed598395aa4cbf56305c22 Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Sat, 18 May 2024 11:20:50 +0100 Subject: [PATCH 197/500] ENH: array types: add JAX support (#20085) Co-authored-by: Ralf Gommers --- .github/workflows/array_api.yml | 8 +- dev.py | 3 +- doc/source/dev/api-dev/array_api.rst | 4 +- pytest.ini | 1 + scipy/_lib/_array_api.py | 25 ++++++ scipy/_lib/tests/test__util.py | 8 ++ scipy/_lib/tests/test_array_api.py | 5 ++ scipy/cluster/_hierarchy.pyx | 28 +++---- scipy/cluster/hierarchy.py | 18 ++--- scipy/cluster/tests/test_hierarchy.py | 49 ++++++++++-- scipy/cluster/tests/test_vq.py | 14 ++++ scipy/conftest.py | 15 +++- scipy/fft/tests/test_basic.py | 46 ++++++----- scipy/fft/tests/test_fftlog.py | 2 +- scipy/fft/tests/test_helper.py | 24 +++--- scipy/fft/tests/test_real_transforms.py | 12 ++- scipy/optimize/tests/test_chandrupatla.py | 5 +- .../special/_support_alternative_backends.py | 13 ++-- .../test_support_alternative_backends.py | 13 +++- scipy/stats/_morestats.py | 5 +- scipy/stats/tests/test_morestats.py | 4 +- scipy/stats/tests/test_stats.py | 76 ++++++++++++++++--- 22 files changed, 283 insertions(+), 95 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 601706e86369..5e3875e6272c 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -26,7 +26,7 @@ jobs: uses: ./.github/workflows/commit_message.yml pytorch_cpu: - name: Linux PyTorch CPU + name: Linux PyTorch/JAX/xp-strict CPU needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -64,6 +64,10 @@ jobs: run: | python -m pip install "torch<2.1" --index-url https://download.pytorch.org/whl/cpu + - name: Install JAX + run: | + python -m pip install "jax[cpu]" + - name: Prepare compiler cache id: prep-ccache shell: bash @@ -94,7 +98,7 @@ jobs: python dev.py --no-build test -b all -s constants -- --durations 3 --timeout=60 python dev.py --no-build test -b all -s fft -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.special.tests.test_support_alternative_backends -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -t scipy._lib.tests.test_array_api + python dev.py --no-build test -b all -t scipy._lib.tests.test_array_api -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy._lib.tests.test__util -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.stats.tests.test_stats -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.stats.tests.test_morestats -- --durations 3 --timeout=60 diff --git a/dev.py b/dev.py index 6e72b95f3620..950e2147fd9f 100644 --- a/dev.py +++ b/dev.py @@ -698,7 +698,8 @@ class Test(Task): ['--array-api-backend', '-b'], default=None, metavar='ARRAY_BACKEND', multiple=True, help=( - "Array API backend ('all', 'numpy', 'pytorch', 'cupy', 'array_api_strict')." + "Array API backend " + "('all', 'numpy', 'pytorch', 'cupy', 'array_api_strict', 'jax.numpy')." ) ) # Argument can't have `help=`; used to consume all of `-- arg1 arg2 arg3` diff --git a/doc/source/dev/api-dev/array_api.rst b/doc/source/dev/api-dev/array_api.rst index 0a437c5f1a10..f46cf3e92550 100644 --- a/doc/source/dev/api-dev/array_api.rst +++ b/doc/source/dev/api-dev/array_api.rst @@ -120,8 +120,8 @@ This package is included in the SciPy code base via a git submodule (under ``array-api-compat`` provides generic utility functions and adds aliases such as ``xp.concat`` (which, for numpy, maps to ``np.concatenate``). This allows -using a uniform API across NumPy, PyTorch and CuPy (as of right now; support -for other libraries like JAX is expected to be added in the future). +using a uniform API across NumPy, PyTorch, CuPy and JAX (with other libraries, +such as Dask, coming in the future). When the environment variable isn't set and hence array API standard support in SciPy is disabled, we still use the "augmented" version of the NumPy namespace, diff --git a/pytest.ini b/pytest.ini index 4bf655cb63b0..669ab8939e1b 100644 --- a/pytest.ini +++ b/pytest.ini @@ -16,3 +16,4 @@ filterwarnings = ignore:\s*.*numpy.distutils.*:DeprecationWarning ignore:.*`numpy.core` has been made officially private.*:DeprecationWarning ignore:.*In the future `np.long` will be defined as.*:FutureWarning + ignore:.*JAX is multithreaded.*:RuntimeWarning diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 0e0d4382a332..0505305be62f 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -228,6 +228,9 @@ def is_cupy(xp: ModuleType) -> bool: def is_torch(xp: ModuleType) -> bool: return xp.__name__ in ('torch', 'scipy._lib.array_api_compat.torch') +def is_jax(xp): + return xp.__name__ in ('jax.numpy', 'jax.experimental.array_api') + def _strict_check(actual, desired, xp, check_namespace=True, check_dtype=True, check_shape=True): @@ -302,6 +305,7 @@ def xp_assert_equal(actual, desired, check_namespace=True, check_dtype=True, err_msg = None if err_msg == '' else err_msg return xp.testing.assert_close(actual, desired, rtol=0, atol=0, equal_nan=True, check_dtype=False, msg=err_msg) + # JAX uses `np.testing` return np.testing.assert_array_equal(actual, desired, err_msg=err_msg) @@ -329,6 +333,7 @@ def xp_assert_close(actual, desired, rtol=None, atol=0, check_namespace=True, err_msg = None if err_msg == '' else err_msg return xp.testing.assert_close(actual, desired, rtol=rtol, atol=atol, equal_nan=True, check_dtype=False, msg=err_msg) + # JAX uses `np.testing` return np.testing.assert_allclose(actual, desired, rtol=rtol, atol=atol, err_msg=err_msg) @@ -348,6 +353,7 @@ def xp_assert_less(actual, desired, check_namespace=True, check_dtype=True, actual = actual.cpu() if desired.device.type != 'cpu': desired = desired.cpu() + # JAX uses `np.testing` return np.testing.assert_array_less(actual, desired, err_msg=err_msg, verbose=verbose) @@ -387,6 +393,25 @@ def xp_unsupported_param_msg(param: Any) -> str: def is_complex(x: Array, xp: ModuleType) -> bool: return xp.isdtype(x.dtype, 'complex floating') +def scipy_namespace_for(xp): + """ + Return the `scipy` namespace for alternative backends, where it exists, + such as `cupyx.scipy` and `jax.scipy`. Useful for ad hoc dispatching. + + Default: return `scipy` (this package). + """ + + + if is_cupy(xp): + import cupyx # type: ignore[import-not-found] + return cupyx.scipy + + if is_jax(xp): + import jax # type: ignore[import-not-found] + return jax.scipy + + import scipy + return scipy # temporary substitute for xp.minimum, which is not yet in all backends # or covered by array_api_compat. diff --git a/scipy/_lib/tests/test__util.py b/scipy/_lib/tests/test__util.py index 835ba23395e2..1456139f331a 100644 --- a/scipy/_lib/tests/test__util.py +++ b/scipy/_lib/tests/test__util.py @@ -19,6 +19,8 @@ rng_integers, _validate_int, _rename_parameter, _contains_nan, _rng_html_rewrite, _lazywhere) +skip_xp_backends = pytest.mark.skip_xp_backends + def test__aligned_zeros(): niter = 10 @@ -333,6 +335,9 @@ def test_contains_nan_with_strings(self): data4 = np.array([["1", 2], [3, np.nan]], dtype='object') assert _contains_nan(data4)[0] + @skip_xp_backends('jax.numpy', + reasons=["JAX arrays do not support item assignment"]) + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible @pytest.mark.parametrize("nan_policy", ['propagate', 'omit', 'raise']) def test_array_api(self, xp, nan_policy): @@ -390,6 +395,9 @@ class TestLazywhere: data = strategies.data() @pytest.mark.filterwarnings('ignore::RuntimeWarning') # overflows, etc. + @skip_xp_backends('jax.numpy', + reasons=["JAX arrays do not support item assignment"]) + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible @given(n_arrays=n_arrays, rng_seed=rng_seed, dtype=dtype, p=p, data=data) def test_basic(self, n_arrays, rng_seed, dtype, p, data, xp): diff --git a/scipy/_lib/tests/test_array_api.py b/scipy/_lib/tests/test_array_api.py index defe238d5847..3ca0ae36f030 100644 --- a/scipy/_lib/tests/test_array_api.py +++ b/scipy/_lib/tests/test_array_api.py @@ -7,6 +7,8 @@ ) import scipy._lib.array_api_compat.numpy as np_compat +skip_xp_backends = pytest.mark.skip_xp_backends + @pytest.mark.skipif(not _GLOBAL_CONFIG["SCIPY_ARRAY_API"], reason="Array API test; set environment variable SCIPY_ARRAY_API=1 to run it") @@ -51,6 +53,9 @@ def test_array_likes(self): array_namespace(1, 2, 3) array_namespace(1) + @skip_xp_backends('jax.numpy', + reasons=["JAX arrays do not support item assignment"]) + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_copy(self, xp): for _xp in [xp, None]: diff --git a/scipy/cluster/_hierarchy.pyx b/scipy/cluster/_hierarchy.pyx index dc728200921d..5cc3bdb72b84 100644 --- a/scipy/cluster/_hierarchy.pyx +++ b/scipy/cluster/_hierarchy.pyx @@ -74,7 +74,7 @@ cpdef void calculate_cluster_sizes(double[:, :] Z, double[:] cs, int n) noexcept cs[i] += 1 -def cluster_dist(double[:, :] Z, int[:] T, double cutoff, int n): +def cluster_dist(const double[:, :] Z, int[:] T, double cutoff, int n): """ Form flat clusters by distance criterion. @@ -95,7 +95,7 @@ def cluster_dist(double[:, :] Z, int[:] T, double cutoff, int n): cluster_monocrit(Z, max_dists, T, cutoff, n) -def cluster_in(double[:, :] Z, double[:, :] R, int[:] T, double cutoff, int n): +def cluster_in(const double[:, :] Z, const double[:, :] R, int[:] T, double cutoff, int n): """ Form flat clusters by inconsistent criterion. @@ -119,7 +119,7 @@ def cluster_in(double[:, :] Z, double[:, :] R, int[:] T, double cutoff, int n): cluster_monocrit(Z, max_inconsists, T, cutoff, n) -def cluster_maxclust_dist(double[:, :] Z, int[:] T, int n, int mc): +def cluster_maxclust_dist(const double[:, :] Z, int[:] T, int n, int mc): """ Form flat clusters by maxclust criterion. @@ -141,7 +141,7 @@ def cluster_maxclust_dist(double[:, :] Z, int[:] T, int n, int mc): cluster_maxclust_monocrit(Z, max_dists, T, n, mc) -cpdef void cluster_maxclust_monocrit(double[:, :] Z, double[:] MC, int[:] T, +cpdef void cluster_maxclust_monocrit(const double[:, :] Z, const double[:] MC, int[:] T, int n, int max_nc) noexcept: """ Form flat clusters by maxclust_monocrit criterion. @@ -227,7 +227,7 @@ cpdef void cluster_maxclust_monocrit(double[:, :] Z, double[:] MC, int[:] T, cluster_monocrit(Z, MC, T, MC[upper_idx], n) -cpdef void cluster_monocrit(double[:, :] Z, double[:] MC, int[:] T, +cpdef void cluster_monocrit(const double[:, :] Z, const double[:] MC, int[:] T, double cutoff, int n) noexcept: """ Form flat clusters by monocrit criterion. @@ -296,7 +296,7 @@ cpdef void cluster_monocrit(double[:, :] Z, double[:] MC, int[:] T, PyMem_Free(visited) -def cophenetic_distances(double[:, :] Z, double[:] d, int n): +def cophenetic_distances(const double[:, :] Z, double[:] d, int n): """ Calculate the cophenetic distances between each observation @@ -367,7 +367,7 @@ def cophenetic_distances(double[:, :] Z, double[:] d, int n): PyMem_Free(visited) -cpdef void get_max_Rfield_for_each_cluster(double[:, :] Z, double[:, :] R, +cpdef void get_max_Rfield_for_each_cluster(const double[:, :] Z, const double[:, :] R, double[:] max_rfs, int n, int rf) noexcept: """ Get the maximum statistic for each non-singleton cluster. For the i'th @@ -432,7 +432,7 @@ cpdef void get_max_Rfield_for_each_cluster(double[:, :] Z, double[:, :] R, PyMem_Free(visited) -cpdef get_max_dist_for_each_cluster(double[:, :] Z, double[:] MD, int n): +cpdef get_max_dist_for_each_cluster(const double[:, :] Z, double[:] MD, int n): """ Get the maximum inconsistency coefficient for each non-singleton cluster. @@ -491,7 +491,7 @@ cpdef get_max_dist_for_each_cluster(double[:, :] Z, double[:] MD, int n): PyMem_Free(visited) -def inconsistent(double[:, :] Z, double[:, :] R, int n, int d): +def inconsistent(const double[:, :] Z, double[:, :] R, int n, int d): """ Calculate the inconsistency statistics. @@ -574,7 +574,7 @@ def inconsistent(double[:, :] Z, double[:, :] R, int n, int d): PyMem_Free(visited) -def leaders(double[:, :] Z, int[:] T, int[:] L, int[:] M, int nc, int n): +def leaders(const double[:, :] Z, const int[:] T, int[:] L, int[:] M, int nc, int n): """ Find the leader (root) of each flat cluster. @@ -772,7 +772,7 @@ cdef Pair find_min_dist(int n, double[:] D, int[:] size, int x): return Pair(y, current_min) -def fast_linkage(double[:] dists, int n, int method): +def fast_linkage(const double[:] dists, int n, int method): """Perform hierarchy clustering. It implements "Generic Clustering Algorithm" from [1]. The worst case @@ -904,7 +904,7 @@ def fast_linkage(double[:] dists, int n, int method): return Z.base -def nn_chain(double[:] dists, int n, int method): +def nn_chain(const double[:] dists, int n, int method): """Perform hierarchy clustering using nearest-neighbor chain algorithm. Parameters @@ -1012,7 +1012,7 @@ def nn_chain(double[:] dists, int n, int method): return Z_arr -def mst_single_linkage(double[:] dists, int n): +def mst_single_linkage(const double[:] dists, int n): """Perform hierarchy clustering using MST algorithm for single linkage. Parameters @@ -1116,7 +1116,7 @@ cdef label(double[:, :] Z, int n): Z[i, 3] = uf.merge(x_root, y_root) -def prelist(double[:, :] Z, int[:] members, int n): +def prelist(const double[:, :] Z, int[:] members, int n): """ Perform a pre-order traversal on the linkage tree and get a list of ids of the leaves. diff --git a/scipy/cluster/hierarchy.py b/scipy/cluster/hierarchy.py index 6bdafcc7d576..321e8873d453 100644 --- a/scipy/cluster/hierarchy.py +++ b/scipy/cluster/hierarchy.py @@ -134,7 +134,7 @@ import numpy as np from . import _hierarchy, _optimal_leaf_ordering import scipy.spatial.distance as distance -from scipy._lib._array_api import array_namespace, _asarray, copy +from scipy._lib._array_api import array_namespace, _asarray, copy, is_jax from scipy._lib._disjoint_set import DisjointSet @@ -1831,13 +1831,16 @@ def from_mlab_linkage(Z): if Zs[0] == 0: return copy(Z, xp=xp) - Zpart = copy(Z, xp=xp) - if xp.min(Zpart[:, 0:2]) != 1.0 and xp.max(Zpart[:, 0:2]) != 2 * Zs[0]: + if xp.min(Z[:, 0:2]) != 1.0 and xp.max(Z[:, 0:2]) != 2 * Zs[0]: raise ValueError('The format of the indices is not 1..N') - Zpart[:, 0:2] -= 1.0 + Zpart = xp.concat((Z[:, 0:2] - 1.0, Z[:, 2:]), axis=1) CS = np.zeros((Zs[0],), dtype=np.float64) - Zpart = np.asarray(Zpart) + if is_jax(xp): + # calculate_cluster_sizes doesn't accept read-only arrays + Zpart = np.array(Zpart, copy=True) + else: + Zpart = np.asarray(Zpart) _hierarchy.calculate_cluster_sizes(Zpart, CS, int(Zs[0]) + 1) res = np.hstack([Zpart, CS.reshape(Zs[0], 1)]) return xp.asarray(res) @@ -1925,10 +1928,7 @@ def to_mlab_linkage(Z): return copy(Z, xp=xp) is_valid_linkage(Z, throw=True, name='Z') - ZP = copy(Z[:, 0:3], xp=xp) - ZP[:, 0:2] += 1.0 - - return ZP + return xp.concat((Z[:, :2] + 1.0, Z[:, 2:3]), axis=1) def is_monotonic(Z): diff --git a/scipy/cluster/tests/test_hierarchy.py b/scipy/cluster/tests/test_hierarchy.py index fa517bad787d..d80991f7f5f9 100644 --- a/scipy/cluster/tests/test_hierarchy.py +++ b/scipy/cluster/tests/test_hierarchy.py @@ -76,8 +76,7 @@ class TestLinkage: def test_linkage_non_finite_elements_in_distance_matrix(self, xp): # Tests linkage(Y) where Y contains a non-finite element (e.g. NaN or Inf). # Exception expected. - y = xp.zeros((6,)) - y[0] = xp.nan + y = xp.asarray([xp.nan] + [0.0]*5) assert_raises(ValueError, linkage, y) @skip_xp_backends(cpu_only=True) @@ -431,6 +430,9 @@ def test_is_valid_linkage_4_and_up(self, xp): Z = linkage(y) assert_(is_valid_linkage(Z) is True) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_is_valid_linkage_4_and_up_neg_index_left(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative indices (left). @@ -442,6 +444,9 @@ def test_is_valid_linkage_4_and_up_neg_index_left(self, xp): assert_(is_valid_linkage(Z) is False) assert_raises(ValueError, is_valid_linkage, Z, throw=True) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_is_valid_linkage_4_and_up_neg_index_right(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative indices (right). @@ -453,6 +458,9 @@ def test_is_valid_linkage_4_and_up_neg_index_right(self, xp): assert_(is_valid_linkage(Z) is False) assert_raises(ValueError, is_valid_linkage, Z, throw=True) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_is_valid_linkage_4_and_up_neg_dist(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative distances. @@ -464,6 +472,9 @@ def test_is_valid_linkage_4_and_up_neg_dist(self, xp): assert_(is_valid_linkage(Z) is False) assert_raises(ValueError, is_valid_linkage, Z, throw=True) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_is_valid_linkage_4_and_up_neg_counts(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative counts. @@ -516,6 +527,9 @@ def test_is_valid_im_4_and_up(self, xp): R = inconsistent(Z) assert_(is_valid_im(R) is True) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_is_valid_im_4_and_up_neg_index_left(self, xp): # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15 # (step size 3) with negative link height means. @@ -528,6 +542,9 @@ def test_is_valid_im_4_and_up_neg_index_left(self, xp): assert_(is_valid_im(R) is False) assert_raises(ValueError, is_valid_im, R, throw=True) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_is_valid_im_4_and_up_neg_index_right(self, xp): # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15 # (step size 3) with negative link height standard deviations. @@ -540,6 +557,9 @@ def test_is_valid_im_4_and_up_neg_index_right(self, xp): assert_(is_valid_im(R) is False) assert_raises(ValueError, is_valid_im, R, throw=True) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_is_valid_im_4_and_up_neg_dist(self, xp): # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15 # (step size 3) with negative link counts. @@ -741,6 +761,9 @@ def test_is_monotonic_tdist_linkage1(self, xp): Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') assert is_monotonic(Z) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_is_monotonic_tdist_linkage2(self, xp): # Tests is_monotonic(Z) on clustering generated by single linkage on # tdist data set. Perturbing. Expecting False. @@ -764,6 +787,9 @@ def test_maxdists_empty_linkage(self, xp): Z = xp.zeros((0, 4), dtype=xp.float64) assert_raises(ValueError, maxdists, Z) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_maxdists_one_cluster_linkage(self, xp): # Tests maxdists(Z) on linkage with one cluster. Z = xp.asarray([[0, 1, 0.3, 4]], dtype=xp.float64) @@ -771,6 +797,9 @@ def test_maxdists_one_cluster_linkage(self, xp): expectedMD = calculate_maximum_distances(Z, xp) xp_assert_close(MD, expectedMD, atol=1e-15) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_maxdists_Q_linkage(self, xp): for method in ['single', 'complete', 'ward', 'centroid', 'median']: self.check_maxdists_Q_linkage(method, xp) @@ -801,7 +830,9 @@ def test_maxinconsts_difrow_linkage(self, xp): R = xp.asarray(R) assert_raises(ValueError, maxinconsts, Z, R) - @skip_xp_backends(cpu_only=True) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_maxinconsts_one_cluster_linkage(self, xp): # Tests maxinconsts(Z, R) on linkage with one cluster. Z = xp.asarray([[0, 1, 0.3, 4]], dtype=xp.float64) @@ -810,7 +841,9 @@ def test_maxinconsts_one_cluster_linkage(self, xp): expectedMD = calculate_maximum_inconsistencies(Z, R, xp=xp) xp_assert_close(MD, expectedMD, atol=1e-15) - @skip_xp_backends(cpu_only=True) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_maxinconsts_Q_linkage(self, xp): for method in ['single', 'complete', 'ward', 'centroid', 'median']: self.check_maxinconsts_Q_linkage(method, xp) @@ -863,7 +896,9 @@ def check_maxRstat_difrow_linkage(self, i, xp): R = xp.asarray(R) assert_raises(ValueError, maxRstat, Z, R, i) - @skip_xp_backends(cpu_only=True) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_maxRstat_one_cluster_linkage(self, xp): for i in range(4): self.check_maxRstat_one_cluster_linkage(i, xp) @@ -876,7 +911,9 @@ def check_maxRstat_one_cluster_linkage(self, i, xp): expectedMD = calculate_maximum_inconsistencies(Z, R, 1, xp) xp_assert_close(MD, expectedMD, atol=1e-15) - @skip_xp_backends(cpu_only=True) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_maxRstat_Q_linkage(self, xp): for method in ['single', 'complete', 'ward', 'centroid', 'median']: for i in range(4): diff --git a/scipy/cluster/tests/test_vq.py b/scipy/cluster/tests/test_vq.py index 8d32933e67a3..2f015f04d023 100644 --- a/scipy/cluster/tests/test_vq.py +++ b/scipy/cluster/tests/test_vq.py @@ -94,6 +94,8 @@ def test_whiten(self, xp): [0.45067590, 0.45464607]]) xp_assert_close(whiten(obs), desired, rtol=1e-5) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment']) def test_whiten_zero_std(self, xp): desired = xp.asarray([[0., 1.0, 2.86666544], [0., 1.0, 1.32460034], @@ -321,6 +323,9 @@ def test_kmeans2_high_dim(self, xp): data = xp.reshape(data, (20, 20))[:10, :] kmeans2(data, 2) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_kmeans2_init(self, xp): np.random.seed(12345) data = xp.asarray(TESTDATA_2D) @@ -369,6 +374,9 @@ def test_kmeans_large_thres(self, xp): xp_assert_close(res[0], xp.asarray([4.], dtype=xp.float64)) xp_assert_close(res[1], xp.asarray(2.3999999999999999, dtype=xp.float64)[()]) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_kmeans2_kpp_low_dim(self, xp): # Regression test for gh-11462 prev_res = xp.asarray([[-1.95266667, 0.898], @@ -377,6 +385,9 @@ def test_kmeans2_kpp_low_dim(self, xp): res, _ = kmeans2(xp.asarray(TESTDATA_2D), 2, minit='++') xp_assert_close(res, prev_res) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_kmeans2_kpp_high_dim(self, xp): # Regression test for gh-11462 n_dim = 100 @@ -400,6 +411,9 @@ def test_kmeans_diff_convergence(self, xp): xp_assert_close(res[0], xp.asarray([-0.4, 8.], dtype=xp.float64)) xp_assert_close(res[1], xp.asarray(1.0666666666666667, dtype=xp.float64)[()]) + @skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) def test_kmeans_and_kmeans2_random_seed(self, xp): seed_list = [ diff --git a/scipy/conftest.py b/scipy/conftest.py index 1c02bdd45dee..1f17618ec551 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -134,6 +134,14 @@ def check_fpu_mode(request): except ImportError: pass + try: + import jax.numpy # type: ignore[import-not-found] + xp_available_backends.update({'jax.numpy': jax.numpy}) + jax.config.update("jax_enable_x64", True) + jax.config.update("jax_default_device", jax.devices(SCIPY_DEVICE)[0]) + except ImportError: + pass + # by default, use all available backends if SCIPY_ARRAY_API.lower() not in ("1", "true"): SCIPY_ARRAY_API_ = json.loads(SCIPY_ARRAY_API) @@ -207,8 +215,13 @@ def skip_xp_backends(xp, request): if xp.__name__ == 'cupy': pytest.skip(reason=reason) elif xp.__name__ == 'torch': - if 'cpu' not in torch.empty(0).device.type: + if 'cpu' not in xp.empty(0).device.type: pytest.skip(reason=reason) + elif xp.__name__ == 'jax.numpy': + for d in xp.empty(0).devices(): + if 'cpu' not in d.device_kind: + pytest.skip(reason=reason) + if backends is not None: reasons = kwargs.get("reasons", False) for i, backend in enumerate(backends): diff --git a/scipy/fft/tests/test_basic.py b/scipy/fft/tests/test_basic.py index 394811ba3aef..f4c795c0e3b8 100644 --- a/scipy/fft/tests/test_basic.py +++ b/scipy/fft/tests/test_basic.py @@ -40,26 +40,25 @@ def fft1(x): phase = np.arange(L).reshape(-1, 1) * phase return np.sum(x*np.exp(phase), axis=1) - -class TestFFTShift: - - def test_fft_n(self, xp): - x = xp.asarray([1, 2, 3], dtype=xp.complex128) - if xp.__name__ == 'torch': - assert_raises(RuntimeError, fft.fft, x, 0) - else: - assert_raises(ValueError, fft.fft, x, 0) - - class TestFFT1D: def test_identity(self, xp): + maxlen = 512 + x = xp.asarray(random(maxlen) + 1j*random(maxlen)) + xr = xp.asarray(random(maxlen)) + # Check some powers of 2 and some primes + for i in [1, 2, 16, 128, 512, 53, 149, 281, 397]: + xp_assert_close(fft.ifft(fft.fft(x[0:i])), x[0:i]) + xp_assert_close(fft.irfft(fft.rfft(xr[0:i]), i), xr[0:i]) + + @skip_xp_backends(np_only=True, reasons=['significant overhead for some backends']) + def test_identity_extensive(self, xp): maxlen = 512 x = xp.asarray(random(maxlen) + 1j*random(maxlen)) xr = xp.asarray(random(maxlen)) for i in range(1, maxlen): - xp_assert_close(fft.ifft(fft.fft(x[0:i])), x[0:i], rtol=1e-9, atol=0) - xp_assert_close(fft.irfft(fft.rfft(xr[0:i]), i), xr[0:i], rtol=1e-9, atol=0) + xp_assert_close(fft.ifft(fft.fft(x[0:i])), x[0:i]) + xp_assert_close(fft.irfft(fft.rfft(xr[0:i]), i), xr[0:i]) def test_fft(self, xp): x = random(30) + 1j*random(30) @@ -71,6 +70,11 @@ def test_fft(self, xp): expect / xp.sqrt(xp.asarray(30, dtype=xp.float64)),) xp_assert_close(fft.fft(x, norm="forward"), expect / 30) + @skip_xp_backends(np_only=True, reasons=['some backends allow `n=0`']) + def test_fft_n(self, xp): + x = xp.asarray([1, 2, 3], dtype=xp.complex128) + assert_raises(ValueError, fft.fft, x, 0) + def test_ifft(self, xp): x = xp.asarray(random(30) + 1j*random(30)) xp_assert_close(fft.ifft(fft.fft(x)), x) @@ -115,13 +119,15 @@ def test_fftn(self, xp): def test_ifftn(self, xp): x = xp.asarray(random((30, 20, 10)) + 1j*random((30, 20, 10))) expect = fft.ifft(fft.ifft(fft.ifft(x, axis=2), axis=1), axis=0) - xp_assert_close(fft.ifftn(x), expect) - xp_assert_close(fft.ifftn(x, norm="backward"), expect) + xp_assert_close(fft.ifftn(x), expect, rtol=1e-7) + xp_assert_close(fft.ifftn(x, norm="backward"), expect, rtol=1e-7) xp_assert_close( fft.ifftn(x, norm="ortho"), fft.ifftn(x) * xp.sqrt(xp.asarray(30 * 20 * 10, dtype=xp.float64)) ) - xp_assert_close(fft.ifftn(x, norm="forward"), expect * (30 * 20 * 10)) + xp_assert_close(fft.ifftn(x, norm="forward"), + expect * (30 * 20 * 10), + rtol=1e-7) def test_rfft(self, xp): x = xp.asarray(random(29), dtype=xp.float64) @@ -354,9 +360,8 @@ def test_dtypes_real(self, dtype, xp): res_rfft = fft.irfft(fft.rfft(x)) res_hfft = fft.hfft(fft.ihfft(x), x.shape[0]) # Check both numerical results and exact dtype matches - rtol = {"float32": 1.2e-4, "float64": 1e-8}[dtype] - xp_assert_close(res_rfft, x, rtol=rtol, atol=0) - xp_assert_close(res_hfft, x, rtol=rtol, atol=0) + xp_assert_close(res_rfft, x) + xp_assert_close(res_hfft, x) @pytest.mark.parametrize("dtype", ["complex64", "complex128"]) def test_dtypes_complex(self, dtype, xp): @@ -364,8 +369,7 @@ def test_dtypes_complex(self, dtype, xp): res_fft = fft.ifft(fft.fft(x)) # Check both numerical results and exact dtype matches - rtol = {"complex64": 1.2e-4, "complex128": 1e-8}[dtype] - xp_assert_close(res_fft, x, rtol=rtol, atol=0) + xp_assert_close(res_fft, x) @skip_xp_backends(np_only=True) @pytest.mark.parametrize( diff --git a/scipy/fft/tests/test_fftlog.py b/scipy/fft/tests/test_fftlog.py index d9652facb776..146ee458827f 100644 --- a/scipy/fft/tests/test_fftlog.py +++ b/scipy/fft/tests/test_fftlog.py @@ -104,7 +104,7 @@ def test_fht_identity(n, bias, offset, optimal, xp): A = fht(a, dln, mu, offset=offset, bias=bias) a_ = ifht(A, dln, mu, offset=offset, bias=bias) - xp_assert_close(a_, a) + xp_assert_close(a_, a, rtol=1.5e-7) def test_fht_special_cases(xp): diff --git a/scipy/fft/tests/test_helper.py b/scipy/fft/tests/test_helper.py index 64041a5038ea..3c7326f54e47 100644 --- a/scipy/fft/tests/test_helper.py +++ b/scipy/fft/tests/test_helper.py @@ -11,7 +11,7 @@ import numpy as np import sys from scipy.conftest import array_api_compatible -from scipy._lib._array_api import xp_assert_close, SCIPY_DEVICE +from scipy._lib._array_api import xp_assert_close, SCIPY_DEVICE, array_namespace from scipy import fft pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends")] @@ -398,6 +398,7 @@ class TestFFTFreq: def test_definition(self, xp): device = SCIPY_DEVICE + xp_test = array_namespace(xp.empty(0)) try: x = xp.asarray([0, 1, 2, 3, 4, -4, -3, -2, -1], dtype=xp.float64, device=device) @@ -408,14 +409,16 @@ def test_definition(self, xp): x2 = xp.asarray([0, 1, 2, 3, 4, -5, -4, -3, -2, -1], dtype=xp.float64) - y = xp.asarray(9 * fft.fftfreq(9, xp=xp), dtype=xp.float64) + y = xp.asarray(9 * fft.fftfreq(9, xp=xp_test), dtype=xp.float64) xp_assert_close(y, x) - y = xp.asarray(9 * xp.pi * fft.fftfreq(9, xp.pi, xp=xp), dtype=xp.float64) + y = xp.asarray(9 * xp.pi * fft.fftfreq(9, xp.pi, xp=xp_test), dtype=xp.float64) xp_assert_close(y, x) - y = xp.asarray(10 * fft.fftfreq(10, xp=xp), dtype=xp.float64) + y = xp.asarray(10 * fft.fftfreq(10, xp=xp_test), + dtype=xp.float64) xp_assert_close(y, x2) - y = xp.asarray(10 * xp.pi * fft.fftfreq(10, xp.pi, xp=xp), dtype=xp.float64) + y = xp.asarray(10 * xp.pi * fft.fftfreq(10, xp.pi, xp=xp_test), + dtype=xp.float64) xp_assert_close(y, x2) @@ -426,6 +429,7 @@ class TestRFFTFreq: def test_definition(self, xp): device = SCIPY_DEVICE + xp_test = array_namespace(xp.empty(0)) try: x = xp.asarray([0, 1, 2, 3, 4], dtype=xp.float64, device=device) x2 = xp.asarray([0, 1, 2, 3, 4, 5], dtype=xp.float64, device=device) @@ -434,12 +438,14 @@ def test_definition(self, xp): x = xp.asarray([0, 1, 2, 3, 4], dtype=xp.float64) x2 = xp.asarray([0, 1, 2, 3, 4, 5], dtype=xp.float64) - y = xp.asarray(9 * fft.rfftfreq(9, xp=xp), dtype=xp.float64) + y = xp.asarray(9 * fft.rfftfreq(9, xp=xp_test), dtype=xp.float64) xp_assert_close(y, x) - y = xp.asarray(9 * xp.pi * fft.rfftfreq(9, xp.pi, xp=xp), dtype=xp.float64) + y = xp.asarray(9 * xp.pi * fft.rfftfreq(9, xp.pi, xp=xp_test), dtype=xp.float64) xp_assert_close(y, x) - y = xp.asarray(10 * fft.rfftfreq(10, xp=xp), dtype=xp.float64) + y = xp.asarray(10 * fft.rfftfreq(10, xp=xp_test), + dtype=xp.float64) xp_assert_close(y, x2) - y = xp.asarray(10 * xp.pi * fft.rfftfreq(10, xp.pi, xp=xp), dtype=xp.float64) + y = xp.asarray(10 * xp.pi * fft.rfftfreq(10, xp.pi, xp=xp_test), + dtype=xp.float64) xp_assert_close(y, x2) diff --git a/scipy/fft/tests/test_real_transforms.py b/scipy/fft/tests/test_real_transforms.py index 8a6981f8ebec..0bacb08350f3 100644 --- a/scipy/fft/tests/test_real_transforms.py +++ b/scipy/fft/tests/test_real_transforms.py @@ -192,7 +192,9 @@ def test_orthogonalize_noop(func, type, norm, xp): xp_assert_close(y1, y2) -@skip_xp_backends(cpu_only=True) +@skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) @pytest.mark.parametrize("norm", ["backward", "ortho", "forward"]) def test_orthogonalize_dct1(norm, xp): x = xp.asarray(np.random.rand(100)) @@ -209,7 +211,9 @@ def test_orthogonalize_dct1(norm, xp): xp_assert_close(y1, y2) -@skip_xp_backends(cpu_only=True) +@skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) @pytest.mark.parametrize("norm", ["backward", "ortho", "forward"]) @pytest.mark.parametrize("func", [dct, dst]) def test_orthogonalize_dcst2(func, norm, xp): @@ -221,7 +225,9 @@ def test_orthogonalize_dcst2(func, norm, xp): xp_assert_close(y1, y2) -@skip_xp_backends(cpu_only=True) +@skip_xp_backends('jax.numpy', + reasons=['jax arrays do not support item assignment'], + cpu_only=True) @pytest.mark.parametrize("norm", ["backward", "ortho", "forward"]) @pytest.mark.parametrize("func", [dct, dst]) def test_orthogonalize_dcst3(func, norm, xp): diff --git a/scipy/optimize/tests/test_chandrupatla.py b/scipy/optimize/tests/test_chandrupatla.py index 97b3894b4453..5fb836435dfd 100644 --- a/scipy/optimize/tests/test_chandrupatla.py +++ b/scipy/optimize/tests/test_chandrupatla.py @@ -480,8 +480,9 @@ def f(x): @array_api_compatible @pytest.mark.usefixtures("skip_xp_backends") -@pytest.mark.skip_xp_backends('array_api_strict', - reason=['Currently uses fancy indexing assignment.']) +@pytest.mark.skip_xp_backends('array_api_strict', 'jax.numpy', + reasons=['Currently uses fancy indexing assignment.', + 'JAX arrays do not support item assignment.']) class TestChandrupatla(TestScalarRootFinders): def f(self, q, p): diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index c3ab21f3e175..1e688a83824d 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -3,7 +3,10 @@ import functools import numpy as np -from scipy._lib._array_api import array_namespace, is_cupy, is_torch, is_numpy +import scipy +from scipy._lib._array_api import ( + array_namespace, scipy_namespace_for, is_numpy, is_torch +) from . import _ufuncs # These don't really need to be imported, but otherwise IDEs might not realize # that these are defined in this file / report an error in __init__.py @@ -16,16 +19,14 @@ def get_array_special_func(f_name, xp, n_array_args): + spx = scipy_namespace_for(xp) f = None if is_numpy(xp): f = getattr(_ufuncs, f_name, None) elif is_torch(xp): f = getattr(xp.special, f_name, None) - elif is_cupy(xp): - import cupyx # type: ignore[import-not-found] - f = getattr(cupyx.scipy.special, f_name, None) - elif xp.__name__ == f"{array_api_compat_prefix}.jax": - f = getattr(xp.scipy.special, f_name, None) + elif spx is not scipy: + f = getattr(spx.special, f_name, None) if f is not None: return f diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index bda52d9884bd..742c3313ce34 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -6,7 +6,7 @@ array_special_func_map) from scipy.conftest import array_api_compatible from scipy import special -from scipy._lib._array_api import xp_assert_close +from scipy._lib._array_api import xp_assert_close, is_jax from scipy._lib.array_api_compat import numpy as np try: @@ -52,6 +52,13 @@ def test_rel_entr_generic(dtype): @pytest.mark.parametrize('f_name_n_args', array_special_func_map.items()) def test_support_alternative_backends(xp, data, f_name_n_args): f_name, n_args = f_name_n_args + + if is_jax(xp): + if f_name in ['gammainc', 'gammaincc']: + pytest.skip("google/jax#20507") + if f_name == 'rel_entr': + pytest.skip("google/jax#21265") + f = getattr(special, f_name) mbs = npst.mutually_broadcastable_shapes(num_shapes=n_args) @@ -80,8 +87,8 @@ def test_support_alternative_backends(xp, data, f_name_n_args): # To compensate, we also check that the root-mean-square error is # less than eps**0.5. ref = xp.asarray(ref, dtype=dtype_xp) - xp_assert_close(res, ref, rtol=eps**0.2, atol=eps*10, + xp_assert_close(res, ref, rtol=eps**0.2, atol=eps*20, check_namespace=True, check_shape=True, check_dtype=True,) xp_assert_close(xp.sqrt(xp.mean(res**2)), xp.sqrt(xp.mean(ref**2)), - rtol=eps**0.5, atol=eps*10, + rtol=eps**0.5, atol=eps*20, check_namespace=False, check_shape=False, check_dtype=False,) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 133119820e40..c2ddc40e6d40 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -4424,12 +4424,9 @@ def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): samples, sin_samp, cos_samp = _circfuncs_common(samples, high, low, 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 = xp.atan2(sin_sum, cos_sum) % (2*xp.pi) - res = xp.asarray(res) - res[res < 0] += 2*xp.pi res = res[()] if res.ndim == 0 else res - return res*(high - low)/2.0/xp.pi + low diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 699f930b01db..af33aee9eb49 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -1713,7 +1713,7 @@ def test_empty_input(self, xp): def test_nan_input(self, xp): data = xp.arange(10.) - data[6] = xp.nan + data = xp.where(data == 6, xp.asarray(xp.nan), data) xp_assert_equal(stats.kstat(data), xp.asarray(xp.nan)) @@ -1753,7 +1753,7 @@ def test_empty_input(self, xp): def test_nan_input(self, xp): data = xp.arange(10.) - data[6] = xp.nan + data = xp.where(data == 6, xp.asarray(xp.nan), data) xp_assert_equal(stats.kstat(data), xp.asarray(xp.nan)) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 719c1a5513e8..1b32f0477140 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -352,15 +352,18 @@ def test_pROUNDROUND(self): @array_api_compatible @pytest.mark.usefixtures("skip_xp_backends") -@pytest.mark.skip_xp_backends(cpu_only=True) +@skip_xp_backends(cpu_only=True) class TestPearsonr: - @pytest.mark.skip_xp_backends(np_only=True) + @skip_xp_backends(np_only=True) def test_pearsonr_result_attributes(self): res = stats.pearsonr(X, X) attributes = ('correlation', 'pvalue') check_named_results(res, attributes) assert_equal(res.correlation, res.statistic) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) def test_r_almost_exactly_pos1(self, xp): a = xp.arange(3.0) r, prob = stats.pearsonr(a, a) @@ -370,6 +373,9 @@ def test_r_almost_exactly_pos1(self, xp): # square root of the error in r. xp_assert_close(prob, xp.asarray(0.0), atol=np.sqrt(2*np.spacing(1.0))) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) def test_r_almost_exactly_neg1(self, xp): a = xp.arange(3.0) r, prob = stats.pearsonr(a, -a) @@ -379,6 +385,9 @@ def test_r_almost_exactly_neg1(self, xp): # square root of the error in r. xp_assert_close(prob, xp.asarray(0.0), atol=np.sqrt(2*np.spacing(1.0))) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) def test_basic(self, xp): # A basic test, with a correlation coefficient # that is not 1 or -1. @@ -388,6 +397,9 @@ def test_basic(self, xp): xp_assert_close(r, xp.asarray(3**0.5/2)) xp_assert_close(prob, xp.asarray(1/3)) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) def test_constant_input(self, xp): # Zero variance input # See https://github.com/scipy/scipy/issues/3728 @@ -399,6 +411,9 @@ def test_constant_input(self, xp): xp_assert_close(r, xp.asarray(xp.nan)) xp_assert_close(p, xp.asarray(xp.nan)) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) @pytest.mark.parametrize('dtype', ['float32', 'float64']) def test_near_constant_input(self, xp, dtype): npdtype = getattr(np, dtype) @@ -412,6 +427,9 @@ def test_near_constant_input(self, xp, dtype): # (The exact value of r would be 1.) stats.pearsonr(x, y) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) def test_very_small_input_values(self, xp): # Very small values in an input. A naive implementation will # suffer from underflow. @@ -427,6 +445,9 @@ def test_very_small_input_values(self, xp): xp_assert_close(r, xp.asarray(0.7272930540750450, dtype=xp.float64)) xp_assert_close(p, xp.asarray(0.1637805429533202, dtype=xp.float64)) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) def test_very_large_input_values(self, xp): # Very large values in an input. A naive implementation will # suffer from overflow. @@ -441,6 +462,9 @@ def test_very_large_input_values(self, xp): xp_assert_close(r, xp.asarray(0.8660254037844386, dtype=xp.float64)) xp_assert_close(p, xp.asarray(0.011724811003954638, dtype=xp.float64)) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) 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 @@ -506,6 +530,9 @@ def test_basic_example(self, alternative, pval, rlow, rhigh, sign): ci = result.confidence_interval() assert_allclose(ci, (rlow, rhigh), rtol=1e-6) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) def test_negative_correlation_pvalue_gh17795(self, xp): x = xp.arange(10.) y = -x @@ -514,6 +541,9 @@ def test_negative_correlation_pvalue_gh17795(self, xp): xp_assert_close(test_greater.pvalue, xp.asarray(1.)) xp_assert_close(test_less.pvalue, xp.asarray(0.), atol=1e-20) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) def test_length3_r_exactly_negative_one(self, xp): x = xp.asarray([1., 2., 3.]) y = xp.asarray([5., -4., -13.]) @@ -642,6 +672,9 @@ def test_nd_input_validation(self, xp): with pytest.raises(ValueError, match=message): stats.pearsonr(x, x, method=stats.PermutationMethod()) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) def test_nd_special_cases(self, xp): rng = np.random.default_rng(34989235492245) x0 = xp.asarray(rng.random((3, 5))) @@ -679,6 +712,9 @@ def test_nd_special_cases(self, xp): xp_assert_close(ci.low, -ones) xp_assert_close(ci.high, ones) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment'], + cpu_only=True) @pytest.mark.parametrize('axis', [0, 1, None]) @pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided']) def test_array_api(self, xp, axis, alternative): @@ -2654,7 +2690,7 @@ def test_sem(self, xp): stats.sem(testcase, ddof=2)) x = xp.arange(10.) - x[9] = xp.nan + x = xp.where(x == 9, xp.asarray(xp.nan), x) xp_assert_equal(stats.sem(x), xp.asarray(xp.nan)) @skip_xp_backends(np_only=True, @@ -3343,6 +3379,9 @@ def test_constant_moments(self, dtype, expect, order, xp): order=order) xp_assert_equal(y, xp.full((), expect, dtype=dtype)) + @skip_xp_backends('jax.numpy', + reasons=["JAX arrays do not support item assignment"]) + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_moment_propagate_nan(self, xp): # Check that the shape of the result is the same for inputs @@ -3403,6 +3442,9 @@ def test_empty_1d(self): with pytest.warns(RuntimeWarning, match=message): stats.kurtosis([]) + @skip_xp_backends('jax.numpy', + reasons=["JAX arrays do not support item assignment"]) + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_skewness(self, xp): # Scalar test case @@ -3431,6 +3473,9 @@ 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) + @skip_xp_backends('jax.numpy', + reasons=["JAX arrays do not support item assignment"]) + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_skew_propagate_nan(self, xp): # Check that the shape of the result is the same for inputs @@ -3458,6 +3503,9 @@ def test_skew_constant_value(self, xp): a = 1. + xp.arange(-3., 4)*1e-16 xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) + @skip_xp_backends('jax.numpy', + reasons=["JAX arrays do not support item assignment"]) + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_precision_loss_gh15554(self, xp): # gh-15554 was one of several issues that have reported problems with @@ -3469,6 +3517,9 @@ def test_precision_loss_gh15554(self, xp): a[:, 0] = 1.01 stats.skew(a) + @skip_xp_backends('jax.numpy', + reasons=["JAX arrays do not support item assignment"]) + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible @pytest.mark.parametrize('axis', [-1, 0, 2, None]) @pytest.mark.parametrize('bias', [False, True]) @@ -3499,6 +3550,10 @@ def skewness(a, axis, bias): class TestKurtosis(SkewKurtosisTest): + + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment']) + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_kurtosis(self, xp): # Scalar test case @@ -3562,6 +3617,9 @@ def test_kurtosis_constant_value(self, xp): assert xp.isnan(stats.kurtosis(a / float(2**50), fisher=False)) assert xp.isnan(stats.kurtosis(a, fisher=False, bias=False)) + @skip_xp_backends('jax.numpy', + reasons=['JAX arrays do not support item assignment']) + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible @pytest.mark.parametrize('axis', [-1, 0, 2, None]) @pytest.mark.parametrize('bias', [False, True]) @@ -6009,8 +6067,8 @@ def test_describe_numbers(self, xp): assert n == nc xp_assert_equal(mm[0], mmc[0]) xp_assert_equal(mm[1], mmc[1]) - xp_assert_equal(m, mc) - xp_assert_equal(v, vc) + 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) @@ -6018,8 +6076,8 @@ def test_describe_numbers(self, xp): assert n == nc xp_assert_equal(mm[0], mmc[0]) xp_assert_equal(mm[1], mmc[1]) - xp_assert_equal(m, mc) - xp_assert_equal(v, vc) + 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) @@ -6207,14 +6265,14 @@ def test_normalitytests(xp): x = xp.arange(10.) NaN = xp.asarray(xp.nan, dtype=x.dtype) - x[9] = NaN + x = xp.where(x == 9, NaN, x) with np.errstate(invalid="ignore"): res = stats.skewtest(x) xp_assert_equal(res.statistic, NaN) xp_assert_equal(res.pvalue, NaN) x = xp.arange(30.) - x[29] = NaN + x = xp.where(x == 29, NaN, x) with np.errstate(all='ignore'): res = stats.kurtosistest(x) xp_assert_equal(res.statistic, NaN) From c3cb9af43896a2cc52d97c642f783219e098822a Mon Sep 17 00:00:00 2001 From: h-vetinari Date: Sat, 18 May 2024 22:47:53 +1100 Subject: [PATCH 198/500] MAINT: remove incidental imports from private modules (#19904) * MAINT: remove incidental imports from private modules * ensure "correct import" pointed to in warning exists * reinstate varmats_from_mat to scipy.io.matlab.mio5 namespace * put varmats_from_mat into scipy.io.matlab namespace * Revert "put varmats_from_mat into scipy.io.matlab namespace" This reverts f01933c1014026231eab0fa38821ac123bd53806 * skip varmats_from_mat in test_public_api * remove yet more imports * undo submodule change --- scipy/_lib/tests/test_public_api.py | 15 ++-- scipy/constants/codata.py | 5 +- scipy/fftpack/pseudo_diffs.py | 2 +- scipy/integrate/_quadpack_py.py | 2 - scipy/integrate/dop.py | 5 +- scipy/integrate/quadpack.py | 1 - scipy/integrate/vode.py | 5 +- scipy/interpolate/_fitpack2.py | 2 +- scipy/interpolate/fitpack.py | 1 - scipy/interpolate/fitpack2.py | 9 -- scipy/interpolate/interpolate.py | 14 ---- scipy/interpolate/ndgriddata.py | 2 - scipy/interpolate/polyint.py | 2 - scipy/interpolate/rbf.py | 9 +- scipy/io/_harwell_boeing/__init__.py | 14 +--- scipy/io/_harwell_boeing/hb.py | 3 +- .../tests/test_fortran_format.py | 2 +- scipy/io/arff/arffread.py | 9 -- scipy/io/harwell_boeing.py | 6 +- scipy/io/idl.py | 5 +- scipy/io/matlab/_mio.py | 2 +- scipy/io/matlab/_mio5.py | 12 +-- scipy/io/matlab/_miobase.py | 6 +- scipy/io/matlab/byteordercodes.py | 5 +- scipy/io/matlab/mio.py | 6 +- scipy/io/matlab/mio4.py | 11 +-- scipy/io/matlab/mio5.py | 13 +-- scipy/io/matlab/mio5_params.py | 12 +-- scipy/io/matlab/mio5_utils.py | 6 +- scipy/io/matlab/mio_utils.py | 2 +- scipy/io/matlab/miobase.py | 8 +- scipy/io/matlab/streams.py | 4 +- scipy/io/mmio.py | 5 +- scipy/io/netcdf.py | 10 +-- scipy/linalg/_matfuncs.py | 3 - scipy/linalg/basic.py | 3 +- scipy/linalg/decomp.py | 2 - scipy/linalg/decomp_cholesky.py | 3 +- scipy/linalg/decomp_lu.py | 2 +- scipy/linalg/decomp_qr.py | 2 +- scipy/linalg/decomp_schur.py | 3 +- scipy/linalg/matfuncs.py | 4 +- scipy/linalg/special_matrices.py | 2 +- scipy/misc/common.py | 2 +- scipy/ndimage/interpolation.py | 1 - scipy/optimize/_linesearch.py | 1 - scipy/optimize/_minpack_py.py | 6 -- scipy/optimize/_slsqp_py.py | 3 - scipy/optimize/cobyla.py | 4 - scipy/optimize/lbfgsb.py | 6 -- scipy/optimize/linesearch.py | 14 +--- scipy/optimize/minpack.py | 25 ------ scipy/optimize/minpack2.py | 6 +- scipy/optimize/nonlin.py | 28 ------- scipy/optimize/optimize.py | 20 ----- scipy/optimize/slsqp.py | 14 ---- scipy/optimize/tnc.py | 22 ----- scipy/optimize/zeros.py | 10 --- scipy/signal/bsplines.py | 2 - scipy/signal/filter_design.py | 6 -- scipy/signal/fir_filter_design.py | 2 - scipy/signal/lti_conversion.py | 3 +- scipy/signal/ltisys.py | 9 +- scipy/signal/signaltools.py | 4 +- scipy/signal/spectral.py | 3 +- scipy/signal/waveforms.py | 3 +- scipy/signal/wavelets.py | 2 +- scipy/signal/windows/windows.py | 1 - scipy/sparse/lil.py | 11 +-- scipy/sparse/linalg/dsolve.py | 4 +- scipy/sparse/linalg/eigen.py | 4 +- scipy/sparse/linalg/interface.py | 2 - scipy/sparse/linalg/isolve.py | 2 +- scipy/sparse/linalg/matfuncs.py | 6 +- scipy/sparse/sparsetools.py | 83 +------------------ scipy/sparse/spfuncs.py | 7 +- scipy/sparse/sputils.py | 29 +------ scipy/spatial/ckdtree.py | 11 +-- scipy/spatial/kdtree.py | 1 - scipy/spatial/transform/rotation.py | 3 - scipy/special/add_newdocs.py | 2 +- scipy/special/orthogonal.py | 4 +- scipy/special/spfun_stats.py | 2 +- scipy/stats/_kde.py | 3 - scipy/stats/_morestats.py | 5 +- scipy/stats/_stats_py.py | 6 -- scipy/stats/biasedurn.py | 6 +- scipy/stats/kde.py | 7 +- scipy/stats/morestats.py | 9 +- scipy/stats/mstats_basic.py | 10 +-- scipy/stats/mstats_extras.py | 3 +- scipy/stats/mvn.py | 8 +- scipy/stats/stats.py | 15 +--- 93 files changed, 82 insertions(+), 597 deletions(-) diff --git a/scipy/_lib/tests/test_public_api.py b/scipy/_lib/tests/test_public_api.py index ddcc04c8508c..0e789f9ccb33 100644 --- a/scipy/_lib/tests/test_public_api.py +++ b/scipy/_lib/tests/test_public_api.py @@ -448,14 +448,15 @@ def test_private_but_present_deprecation(module_name, correct_module): correct_import = import_module(import_name) # Attributes that were formerly in `module_name` can still be imported from - # `module_name`, albeit with a deprecation warning. The specific message - # depends on whether the attribute is public in `scipy.xxx` or not. + # `module_name`, albeit with a deprecation warning. for attr_name in module.__all__: - attr = getattr(correct_import, attr_name, None) - if attr is None: - message = f"`{module_name}.{attr_name}` is deprecated..." - else: - message = f"Please import `{attr_name}` from the `{import_name}`..." + if attr_name == "varmats_from_mat": + # defer handling this case, see + # https://github.com/scipy/scipy/issues/19223 + continue + # ensure attribute is present where the warning is pointing + assert getattr(correct_import, attr_name, None) is not None + message = f"Please import `{attr_name}` from the `{import_name}`..." with pytest.deprecated_call(match=message): getattr(module, attr_name) diff --git a/scipy/constants/codata.py b/scipy/constants/codata.py index 72177f20545d..912e0bbf7c4f 100644 --- a/scipy/constants/codata.py +++ b/scipy/constants/codata.py @@ -6,10 +6,7 @@ __all__ = [ # noqa: F822 'physical_constants', 'value', 'unit', 'precision', 'find', - 'ConstantWarning', 'txt2002', 'txt2006', 'txt2010', 'txt2014', - 'txt2018', 'parse_constants_2002to2014', - 'parse_constants_2018toXXXX', 'k', 'c', 'mu0', 'epsilon0', - 'exact_values', 'key', 'val', 'v' + 'ConstantWarning', 'k', 'c', ] diff --git a/scipy/fftpack/pseudo_diffs.py b/scipy/fftpack/pseudo_diffs.py index 07a245cdc41e..ecf71ad3256d 100644 --- a/scipy/fftpack/pseudo_diffs.py +++ b/scipy/fftpack/pseudo_diffs.py @@ -8,7 +8,7 @@ 'diff', 'tilbert', 'itilbert', 'hilbert', 'ihilbert', 'cs_diff', 'cc_diff', 'sc_diff', 'ss_diff', - 'shift', 'iscomplexobj', 'convolve' + 'shift', 'convolve' ] diff --git a/scipy/integrate/_quadpack_py.py b/scipy/integrate/_quadpack_py.py index 599a30dffe5c..af7ed047c0c5 100644 --- a/scipy/integrate/_quadpack_py.py +++ b/scipy/integrate/_quadpack_py.py @@ -10,8 +10,6 @@ __all__ = ["quad", "dblquad", "tplquad", "nquad", "IntegrationWarning"] -error = _quadpack.error - class IntegrationWarning(UserWarning): """ Warning on issues during integration. diff --git a/scipy/integrate/dop.py b/scipy/integrate/dop.py index 5e61e475220e..bf67a9a35b7d 100644 --- a/scipy/integrate/dop.py +++ b/scipy/integrate/dop.py @@ -2,10 +2,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'dopri5', - 'dop853' -] +__all__: list[str] = [] def __dir__(): diff --git a/scipy/integrate/quadpack.py b/scipy/integrate/quadpack.py index 9de6e498e382..144584988095 100644 --- a/scipy/integrate/quadpack.py +++ b/scipy/integrate/quadpack.py @@ -10,7 +10,6 @@ "tplquad", "nquad", "IntegrationWarning", - "error", ] diff --git a/scipy/integrate/vode.py b/scipy/integrate/vode.py index dc58782b99ce..f92927901084 100644 --- a/scipy/integrate/vode.py +++ b/scipy/integrate/vode.py @@ -2,10 +2,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'dvode', - 'zvode' -] +__all__: list[str] = [] def __dir__(): diff --git a/scipy/interpolate/_fitpack2.py b/scipy/interpolate/_fitpack2.py index b6820b4fa4a4..98d49f90c31f 100644 --- a/scipy/interpolate/_fitpack2.py +++ b/scipy/interpolate/_fitpack2.py @@ -21,7 +21,7 @@ import warnings -from numpy import zeros, concatenate, ravel, diff, array, ones # noqa:F401 +from numpy import zeros, concatenate, ravel, diff, array import numpy as np from . import _fitpack_impl diff --git a/scipy/interpolate/fitpack.py b/scipy/interpolate/fitpack.py index 68a6a2409610..6490c93fe02b 100644 --- a/scipy/interpolate/fitpack.py +++ b/scipy/interpolate/fitpack.py @@ -9,7 +9,6 @@ 'BSpline', 'bisplev', 'bisplrep', - 'dblint', 'insert', 'spalde', 'splantider', diff --git a/scipy/interpolate/fitpack2.py b/scipy/interpolate/fitpack2.py index c4848053e4d9..f993961f94d9 100644 --- a/scipy/interpolate/fitpack2.py +++ b/scipy/interpolate/fitpack2.py @@ -15,16 +15,7 @@ 'RectSphereBivariateSpline', 'SmoothBivariateSpline', 'SmoothSphereBivariateSpline', - 'SphereBivariateSpline', 'UnivariateSpline', - 'array', - 'concatenate', - 'dfitpack', - 'dfitpack_int', - 'diff', - 'ones', - 'ravel', - 'zeros', ] diff --git a/scipy/interpolate/interpolate.py b/scipy/interpolate/interpolate.py index 3c6ae3398fa0..341d13954c81 100644 --- a/scipy/interpolate/interpolate.py +++ b/scipy/interpolate/interpolate.py @@ -12,25 +12,11 @@ 'PPoly', 'RectBivariateSpline', 'RegularGridInterpolator', - 'array', - 'asarray', - 'atleast_1d', - 'atleast_2d', - 'comb', - 'dfitpack', 'interp1d', 'interp2d', 'interpn', - 'intp', - 'itertools', 'lagrange', 'make_interp_spline', - 'poly1d', - 'prod', - 'ravel', - 'searchsorted', - 'spec', - 'transpose', ] diff --git a/scipy/interpolate/ndgriddata.py b/scipy/interpolate/ndgriddata.py index bb2b1694d244..20373eaaedaa 100644 --- a/scipy/interpolate/ndgriddata.py +++ b/scipy/interpolate/ndgriddata.py @@ -8,9 +8,7 @@ __all__ = [ # noqa: F822 'CloughTocher2DInterpolator', 'LinearNDInterpolator', - 'NDInterpolatorBase', 'NearestNDInterpolator', - 'cKDTree', 'griddata', ] diff --git a/scipy/interpolate/polyint.py b/scipy/interpolate/polyint.py index dbdae2e90934..e81306304abf 100644 --- a/scipy/interpolate/polyint.py +++ b/scipy/interpolate/polyint.py @@ -10,8 +10,6 @@ 'KroghInterpolator', 'approximate_taylor_polynomial', 'barycentric_interpolate', - 'factorial', - 'float_factorial', 'krogh_interpolate', ] diff --git a/scipy/interpolate/rbf.py b/scipy/interpolate/rbf.py index 7ae1facd6871..772752ef536f 100644 --- a/scipy/interpolate/rbf.py +++ b/scipy/interpolate/rbf.py @@ -5,14 +5,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'Rbf', - 'cdist', - 'linalg', - 'pdist', - 'squareform', - 'xlogy', -] +__all__ = ["Rbf"] # noqa: F822 def __dir__(): diff --git a/scipy/io/_harwell_boeing/__init__.py b/scipy/io/_harwell_boeing/__init__.py index 094479a593ce..e56f374b42ab 100644 --- a/scipy/io/_harwell_boeing/__init__.py +++ b/scipy/io/_harwell_boeing/__init__.py @@ -1,16 +1,6 @@ -from .hb import (MalformedHeader, hb_read, hb_write, HBInfo, - HBFile, HBMatrixType) -from ._fortran_format_parser import (FortranFormatParser, IntFormat, - ExpFormat, BadFortranFormat) +from .hb import hb_read, hb_write -# Deprecated namespaces, to be removed in v2.0.0 -from . import hb - -__all__ = [ - 'MalformedHeader', 'hb_read', 'hb_write', 'HBInfo', - 'HBFile', 'HBMatrixType', 'FortranFormatParser', 'IntFormat', - 'ExpFormat', 'BadFortranFormat', 'hb' -] +__all__ = ["hb_read", "hb_write"] from scipy._lib._testutils import PytestTester test = PytestTester(__name__) diff --git a/scipy/io/_harwell_boeing/hb.py b/scipy/io/_harwell_boeing/hb.py index ff280ea3ab31..9d28e3849ff5 100644 --- a/scipy/io/_harwell_boeing/hb.py +++ b/scipy/io/_harwell_boeing/hb.py @@ -24,8 +24,7 @@ from scipy.sparse import csc_matrix from ._fortran_format_parser import FortranFormatParser, IntFormat, ExpFormat -__all__ = ["MalformedHeader", "hb_read", "hb_write", "HBInfo", "HBFile", - "HBMatrixType"] +__all__ = ["hb_read", "hb_write"] class MalformedHeader(Exception): diff --git a/scipy/io/_harwell_boeing/tests/test_fortran_format.py b/scipy/io/_harwell_boeing/tests/test_fortran_format.py index 53384ca06d16..dae040c523d6 100644 --- a/scipy/io/_harwell_boeing/tests/test_fortran_format.py +++ b/scipy/io/_harwell_boeing/tests/test_fortran_format.py @@ -3,7 +3,7 @@ from numpy.testing import assert_equal from pytest import raises as assert_raises -from scipy.io._harwell_boeing import ( +from scipy.io._harwell_boeing._fortran_format_parser import ( FortranFormatParser, IntFormat, ExpFormat, BadFortranFormat) diff --git a/scipy/io/arff/arffread.py b/scipy/io/arff/arffread.py index 0fff970ce04a..c42ae31db6bd 100644 --- a/scipy/io/arff/arffread.py +++ b/scipy/io/arff/arffread.py @@ -6,15 +6,6 @@ __all__ = [ # noqa: F822 'MetaData', 'loadarff', 'ArffError', 'ParseArffError', - 'r_meta', 'r_comment', 'r_empty', 'r_headerline', - 'r_datameta', 'r_relation', 'r_attribute', 'r_nominal', - 'r_date', 'r_comattrval', 'r_wcomattrval', 'Attribute', - 'NominalAttribute', 'NumericAttribute', 'StringAttribute', - 'DateAttribute', 'RelationalAttribute', 'to_attribute', - 'csv_sniffer_has_bug_last_field', 'workaround_csv_sniffer_bug_last_field', - 'split_data_line', 'tokenize_attribute', 'tokenize_single_comma', - 'tokenize_single_wcomma', 'read_relational_attribute', 'read_header', - 'basic_stats', 'print_attribute', 'test_weka' ] diff --git a/scipy/io/harwell_boeing.py b/scipy/io/harwell_boeing.py index d4d17fe7d522..11254e75bd75 100644 --- a/scipy/io/harwell_boeing.py +++ b/scipy/io/harwell_boeing.py @@ -4,11 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'MalformedHeader', 'hb_read', 'hb_write', 'HBInfo', - 'HBFile', 'HBMatrixType', 'FortranFormatParser', 'IntFormat', - 'ExpFormat', 'BadFortranFormat', 'hb' -] +__all__ = ["hb_read", "hb_write"] # noqa: F822 def __dir__(): diff --git a/scipy/io/idl.py b/scipy/io/idl.py index 1168022eeaa0..01cc8903f95e 100644 --- a/scipy/io/idl.py +++ b/scipy/io/idl.py @@ -4,10 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'readsav', 'DTYPE_DICT', 'RECTYPE_DICT', 'STRUCT_DICT', - 'Pointer', 'ObjectPointer', 'AttrDict' -] +__all__ = ["readsav"] # noqa: F822 def __dir__(): diff --git a/scipy/io/matlab/_mio.py b/scipy/io/matlab/_mio.py index 5be5301da81d..fb52c38fe69f 100644 --- a/scipy/io/matlab/_mio.py +++ b/scipy/io/matlab/_mio.py @@ -9,7 +9,7 @@ from ._mio4 import MatFile4Reader, MatFile4Writer from ._mio5 import MatFile5Reader, MatFile5Writer -__all__ = ['mat_reader_factory', 'loadmat', 'savemat', 'whosmat'] +__all__ = ['loadmat', 'savemat', 'whosmat'] @contextmanager diff --git a/scipy/io/matlab/_mio5.py b/scipy/io/matlab/_mio5.py index 9b4e1b28d6d0..bcd78db198a8 100644 --- a/scipy/io/matlab/_mio5.py +++ b/scipy/io/matlab/_mio5.py @@ -311,11 +311,13 @@ def get_variables(self, variable_names=None): hdr, next_position = self.read_var_header() name = 'None' if hdr.name is None else hdr.name.decode('latin1') if name in mdict: - warnings.warn('Duplicate variable name "%s" in stream' - ' - replacing previous with new\n' - 'Consider mio5.varmats_from_mat to split ' - 'file into single variable files' % name, - MatReadWarning, stacklevel=2) + msg = ( + f'Duplicate variable name "{name}" in stream' + " - replacing previous with new\nConsider" + "scipy.io.matlab._mio5.varmats_from_mat to split " + "file into single variable files" + ) + warnings.warn(msg, MatReadWarning, stacklevel=2) if name == '': # can only be a matlab 7 function workspace name = '__function_workspace__' diff --git a/scipy/io/matlab/_miobase.py b/scipy/io/matlab/_miobase.py index a6a21c33aa3e..05425d235cbd 100644 --- a/scipy/io/matlab/_miobase.py +++ b/scipy/io/matlab/_miobase.py @@ -12,11 +12,7 @@ from . import _byteordercodes as boc __all__ = [ - 'MatFileReader', 'MatReadError', 'MatReadWarning', - 'MatVarReader', 'MatWriteError', 'arr_dtype_number', - 'arr_to_chars', 'convert_dtypes', 'doc_dict', - 'docfiller', 'get_matfile_version', - 'matdims', 'read_dtype' + 'MatReadError', 'MatReadWarning', 'MatWriteError', ] class MatReadError(Exception): diff --git a/scipy/io/matlab/byteordercodes.py b/scipy/io/matlab/byteordercodes.py index 27aaf69dbb78..0a1c5b0f5e77 100644 --- a/scipy/io/matlab/byteordercodes.py +++ b/scipy/io/matlab/byteordercodes.py @@ -4,10 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'aliases', 'native_code', 'swapped_code', - 'sys_is_le', 'to_numpy_code' -] +__all__: list[str] = [] def __dir__(): diff --git a/scipy/io/matlab/mio.py b/scipy/io/matlab/mio.py index 492d62cb7000..65bb31e52dc7 100644 --- a/scipy/io/matlab/mio.py +++ b/scipy/io/matlab/mio.py @@ -4,11 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'mat_reader_factory', 'loadmat', 'savemat', 'whosmat', - 'contextmanager', 'docfiller', - 'MatFile4Reader', 'MatFile4Writer', 'MatFile5Reader', 'MatFile5Writer' -] +__all__ = ["loadmat", "savemat", "whosmat"] # noqa: F822 def __dir__(): return __all__ diff --git a/scipy/io/matlab/mio4.py b/scipy/io/matlab/mio4.py index 6807397b34d8..d13b99a0bced 100644 --- a/scipy/io/matlab/mio4.py +++ b/scipy/io/matlab/mio4.py @@ -4,15 +4,8 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'MatFile4Reader', 'MatFile4Writer', 'SYS_LITTLE_ENDIAN', - 'VarHeader4', 'VarReader4', 'VarWriter4', 'arr_to_2d', 'mclass_info', - 'mdtypes_template', 'miDOUBLE', 'miINT16', 'miINT32', 'miSINGLE', - 'miUINT16', 'miUINT8', 'mxCHAR_CLASS', 'mxFULL_CLASS', 'mxSPARSE_CLASS', - 'np_to_mtypes', 'order_codes', 'MatFileReader', 'docfiller', - 'matdims', 'read_dtype', 'convert_dtypes', 'arr_to_chars', - 'arr_dtype_number', 'squeeze_element', 'chars_to_strings' -] +__all__: list[str] = [] + def __dir__(): return __all__ diff --git a/scipy/io/matlab/mio5.py b/scipy/io/matlab/mio5.py index 65f541d3a2a1..b84ca19799b3 100644 --- a/scipy/io/matlab/mio5.py +++ b/scipy/io/matlab/mio5.py @@ -5,17 +5,8 @@ from scipy._lib.deprecation import _sub_module_deprecation __all__ = [ # noqa: F822 - 'mclass_info', 'mxCHAR_CLASS', 'mxSPARSE_CLASS', - 'BytesIO', 'native_code', - 'swapped_code', 'MatFileReader', 'docfiller', 'matdims', - 'read_dtype', 'arr_to_chars', 'arr_dtype_number', 'MatWriteError', - 'MatReadError', 'MatReadWarning', 'VarReader5', 'MatlabObject', - 'MatlabFunction', 'MDTYPES', 'NP_TO_MTYPES', 'NP_TO_MXTYPES', - 'miCOMPRESSED', 'miMATRIX', 'miINT8', 'miUTF8', 'miUINT32', - 'mxCELL_CLASS', 'mxSTRUCT_CLASS', 'mxOBJECT_CLASS', 'mxDOUBLE_CLASS', - 'mat_struct', 'ZlibInputStream', 'MatFile5Reader', 'varmats_from_mat', - 'EmptyStructMarker', 'to_writeable', 'NDT_FILE_HDR', 'NDT_TAG_FULL', - 'NDT_TAG_SMALL', 'NDT_ARRAY_FLAGS', 'VarWriter5', 'MatFile5Writer' + 'MatWriteError', 'MatReadError', 'MatReadWarning', 'MatlabObject', + 'MatlabFunction', 'mat_struct', 'varmats_from_mat', ] def __dir__(): diff --git a/scipy/io/matlab/mio5_params.py b/scipy/io/matlab/mio5_params.py index 6d771a1993f0..2dcc9a4f3537 100644 --- a/scipy/io/matlab/mio5_params.py +++ b/scipy/io/matlab/mio5_params.py @@ -5,17 +5,7 @@ from scipy._lib.deprecation import _sub_module_deprecation __all__ = [ # noqa: F822 - 'MDTYPES', 'MatlabFunction', 'MatlabObject', 'MatlabOpaque', - 'NP_TO_MTYPES', 'NP_TO_MXTYPES', 'OPAQUE_DTYPE', 'codecs_template', - 'mat_struct', 'mclass_dtypes_template', 'mclass_info', 'mdtypes_template', - 'miCOMPRESSED', 'miDOUBLE', 'miINT16', 'miINT32', 'miINT64', 'miINT8', - 'miMATRIX', 'miSINGLE', 'miUINT16', 'miUINT32', 'miUINT64', 'miUINT8', - 'miUTF16', 'miUTF32', 'miUTF8', 'mxCELL_CLASS', 'mxCHAR_CLASS', - 'mxDOUBLE_CLASS', 'mxFUNCTION_CLASS', 'mxINT16_CLASS', 'mxINT32_CLASS', - 'mxINT64_CLASS', 'mxINT8_CLASS', 'mxOBJECT_CLASS', - 'mxOBJECT_CLASS_FROM_MATRIX_H', 'mxOPAQUE_CLASS', 'mxSINGLE_CLASS', - 'mxSPARSE_CLASS', 'mxSTRUCT_CLASS', 'mxUINT16_CLASS', 'mxUINT32_CLASS', - 'mxUINT64_CLASS', 'mxUINT8_CLASS', 'convert_dtypes' + 'MatlabFunction', 'MatlabObject', 'MatlabOpaque', 'mat_struct', ] def __dir__(): diff --git a/scipy/io/matlab/mio5_utils.py b/scipy/io/matlab/mio5_utils.py index 1f0eeab6e6e6..37ad9e2dc2f5 100644 --- a/scipy/io/matlab/mio5_utils.py +++ b/scipy/io/matlab/mio5_utils.py @@ -4,10 +4,8 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'VarHeader5', 'VarReader5', 'byteswap_u4', 'chars_to_strings', - 'csc_matrix', 'mio5p', 'pycopy', 'swapped_code', 'squeeze_element' -] +__all__: list[str] = [] + def __dir__(): return __all__ diff --git a/scipy/io/matlab/mio_utils.py b/scipy/io/matlab/mio_utils.py index 62d32965e019..6920511d2635 100644 --- a/scipy/io/matlab/mio_utils.py +++ b/scipy/io/matlab/mio_utils.py @@ -4,7 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = ['squeeze_element', 'chars_to_strings'] # noqa: F822 +__all__: list[str] = [] def __dir__(): diff --git a/scipy/io/matlab/miobase.py b/scipy/io/matlab/miobase.py index 034229d14ddd..13e168483944 100644 --- a/scipy/io/matlab/miobase.py +++ b/scipy/io/matlab/miobase.py @@ -4,13 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'MatFileReader', 'MatReadError', 'MatReadWarning', - 'MatVarReader', 'MatWriteError', 'arr_dtype_number', - 'arr_to_chars', 'convert_dtypes', 'doc_dict', - 'docfiller', 'get_matfile_version', - 'matdims', 'read_dtype', 'doccer', 'boc' -] +__all__ = ["MatReadError", "MatReadWarning", "MatWriteError"] # noqa: F822 def __dir__(): return __all__ diff --git a/scipy/io/matlab/streams.py b/scipy/io/matlab/streams.py index 9f0e8c1ecc17..8125271b06cc 100644 --- a/scipy/io/matlab/streams.py +++ b/scipy/io/matlab/streams.py @@ -4,9 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'BLOCK_SIZE', 'GenericStream', 'ZlibInputStream', 'make_stream' -] +__all__: list[str] = [] def __dir__(): return __all__ diff --git a/scipy/io/mmio.py b/scipy/io/mmio.py index e03c62376c52..67cf0684cbf9 100644 --- a/scipy/io/mmio.py +++ b/scipy/io/mmio.py @@ -4,10 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'mminfo', 'mmread', 'mmwrite', 'MMFile', - 'coo_matrix', 'asstr' -] +__all__ = ["mminfo", "mmread", "mmwrite"] # noqa: F822 def __dir__(): diff --git a/scipy/io/netcdf.py b/scipy/io/netcdf.py index f469c5a34724..c1f119dd2bad 100644 --- a/scipy/io/netcdf.py +++ b/scipy/io/netcdf.py @@ -4,15 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'netcdf_file', 'netcdf_variable', - 'array', 'LITTLE_ENDIAN', 'IS_PYPY', 'ABSENT', 'ZERO', - 'NC_BYTE', 'NC_CHAR', 'NC_SHORT', 'NC_INT', 'NC_FLOAT', - 'NC_DOUBLE', 'NC_DIMENSION', 'NC_VARIABLE', 'NC_ATTRIBUTE', - 'FILL_BYTE', 'FILL_CHAR', 'FILL_SHORT', 'FILL_INT', 'FILL_FLOAT', - 'FILL_DOUBLE', 'TYPEMAP', 'FILLMAP', 'REVERSE', 'NetCDFFile', - 'NetCDFVariable' -] +__all__ = ["netcdf_file", "netcdf_variable"] # noqa: F822 def __dir__(): diff --git a/scipy/linalg/_matfuncs.py b/scipy/linalg/_matfuncs.py index aaad628c90ad..122581ec4053 100644 --- a/scipy/linalg/_matfuncs.py +++ b/scipy/linalg/_matfuncs.py @@ -17,9 +17,6 @@ from ._matfuncs_sqrtm import sqrtm from ._matfuncs_expm import pick_pade_structure, pade_UV_calc -# deprecated imports to be removed in SciPy 1.13.0 -from numpy import single # noqa: F401 - __all__ = ['expm', 'cosm', 'sinm', 'tanm', 'coshm', 'sinhm', 'tanhm', 'logm', 'funm', 'signm', 'sqrtm', 'fractional_matrix_power', 'expm_frechet', 'expm_cond', 'khatri_rao'] diff --git a/scipy/linalg/basic.py b/scipy/linalg/basic.py index 643fbc74d02a..04ef3645a2ed 100644 --- a/scipy/linalg/basic.py +++ b/scipy/linalg/basic.py @@ -9,8 +9,7 @@ 'solve', 'solve_triangular', 'solveh_banded', 'solve_banded', 'solve_toeplitz', 'solve_circulant', 'inv', 'det', 'lstsq', 'pinv', 'pinvh', 'matrix_balance', 'matmul_toeplitz', - 'atleast_1d', 'atleast_2d', 'get_lapack_funcs', - 'LinAlgError', 'LinAlgWarning', 'levinson', + 'get_lapack_funcs', 'LinAlgError', 'LinAlgWarning', ] diff --git a/scipy/linalg/decomp.py b/scipy/linalg/decomp.py index b1c61752110c..0d82ab157ce3 100644 --- a/scipy/linalg/decomp.py +++ b/scipy/linalg/decomp.py @@ -9,8 +9,6 @@ 'eig', 'eigvals', 'eigh', 'eigvalsh', 'eig_banded', 'eigvals_banded', 'eigh_tridiagonal', 'eigvalsh_tridiagonal', 'hessenberg', 'cdf2rdf', - 'array', 'isfinite', 'inexact', 'nonzero', 'iscomplexobj', - 'flatnonzero', 'argsort', 'iscomplex', 'einsum', 'eye', 'inf', 'LinAlgError', 'norm', 'get_lapack_funcs' ] diff --git a/scipy/linalg/decomp_cholesky.py b/scipy/linalg/decomp_cholesky.py index beec324d3503..92545a5c6af5 100644 --- a/scipy/linalg/decomp_cholesky.py +++ b/scipy/linalg/decomp_cholesky.py @@ -7,8 +7,7 @@ __all__ = [ # noqa: F822 'cholesky', 'cho_factor', 'cho_solve', 'cholesky_banded', - 'cho_solve_banded', 'asarray_chkfinite', 'atleast_2d', - 'LinAlgError', 'get_lapack_funcs' + 'cho_solve_banded', 'LinAlgError', 'get_lapack_funcs' ] diff --git a/scipy/linalg/decomp_lu.py b/scipy/linalg/decomp_lu.py index ab46ddcbd139..9d5d9a98a04a 100644 --- a/scipy/linalg/decomp_lu.py +++ b/scipy/linalg/decomp_lu.py @@ -7,7 +7,7 @@ __all__ = [ # noqa: F822 'lu', 'lu_solve', 'lu_factor', - 'asarray_chkfinite', 'LinAlgWarning', 'get_lapack_funcs', + 'LinAlgWarning', 'get_lapack_funcs', ] diff --git a/scipy/linalg/decomp_qr.py b/scipy/linalg/decomp_qr.py index 77df617b5edf..4ef58729412c 100644 --- a/scipy/linalg/decomp_qr.py +++ b/scipy/linalg/decomp_qr.py @@ -6,7 +6,7 @@ __all__ = [ # noqa: F822 - 'qr', 'qr_multiply', 'rq', 'get_lapack_funcs', 'safecall' + 'qr', 'qr_multiply', 'rq', 'get_lapack_funcs' ] diff --git a/scipy/linalg/decomp_schur.py b/scipy/linalg/decomp_schur.py index 387fe8ad5c2a..c3c6cc494db9 100644 --- a/scipy/linalg/decomp_schur.py +++ b/scipy/linalg/decomp_schur.py @@ -6,8 +6,7 @@ __all__ = [ # noqa: F822 - 'schur', 'rsf2csf', 'asarray_chkfinite', 'single', 'array', 'norm', - 'LinAlgError', 'get_lapack_funcs', 'eigvals', 'eps', 'feps' + 'schur', 'rsf2csf', 'norm', 'LinAlgError', 'get_lapack_funcs', 'eigvals', ] diff --git a/scipy/linalg/matfuncs.py b/scipy/linalg/matfuncs.py index f845585070b4..9ec8123b3ad8 100644 --- a/scipy/linalg/matfuncs.py +++ b/scipy/linalg/matfuncs.py @@ -9,9 +9,7 @@ 'expm', 'cosm', 'sinm', 'tanm', 'coshm', 'sinhm', 'tanhm', 'logm', 'funm', 'signm', 'sqrtm', 'expm_frechet', 'expm_cond', 'fractional_matrix_power', - 'khatri_rao', 'prod', 'logical_not', 'ravel', 'transpose', - 'conjugate', 'absolute', 'amax', 'sign', 'isfinite', 'single', - 'norm', 'solve', 'inv', 'triu', 'svd', 'schur', 'rsf2csf', 'eps', 'feps' + 'khatri_rao', 'norm', 'solve', 'inv', 'svd', 'schur', 'rsf2csf' ] diff --git a/scipy/linalg/special_matrices.py b/scipy/linalg/special_matrices.py index 03091d12c70c..a881ce765dfa 100644 --- a/scipy/linalg/special_matrices.py +++ b/scipy/linalg/special_matrices.py @@ -8,7 +8,7 @@ 'toeplitz', 'circulant', 'hankel', 'hadamard', 'leslie', 'kron', 'block_diag', 'companion', 'helmert', 'hilbert', 'invhilbert', 'pascal', 'invpascal', 'dft', - 'fiedler', 'fiedler_companion', 'convolution_matrix', 'as_strided' + 'fiedler', 'fiedler_companion', 'convolution_matrix' ] diff --git a/scipy/misc/common.py b/scipy/misc/common.py index e2b5af940b18..f4604ec98e3b 100644 --- a/scipy/misc/common.py +++ b/scipy/misc/common.py @@ -6,7 +6,7 @@ __all__ = [ # noqa: F822 'central_diff_weights', 'derivative', 'ascent', 'face', - 'electrocardiogram', 'array', 'load' + 'electrocardiogram' ] diff --git a/scipy/ndimage/interpolation.py b/scipy/ndimage/interpolation.py index 8a28816cbfad..a2739c60c510 100644 --- a/scipy/ndimage/interpolation.py +++ b/scipy/ndimage/interpolation.py @@ -9,7 +9,6 @@ 'spline_filter1d', 'spline_filter', 'geometric_transform', 'map_coordinates', 'affine_transform', 'shift', 'zoom', 'rotate', - 'docfiller' ] diff --git a/scipy/optimize/_linesearch.py b/scipy/optimize/_linesearch.py index bf6038d2b95b..39e0b3826d5f 100644 --- a/scipy/optimize/_linesearch.py +++ b/scipy/optimize/_linesearch.py @@ -13,7 +13,6 @@ """ from warnings import warn -from scipy.optimize import _minpack2 as minpack2 # noqa: F401 from ._dcsrch import DCSRCH import numpy as np diff --git a/scipy/optimize/_minpack_py.py b/scipy/optimize/_minpack_py.py index 6c9720eb8a36..b67a17ae41b4 100644 --- a/scipy/optimize/_minpack_py.py +++ b/scipy/optimize/_minpack_py.py @@ -15,12 +15,6 @@ from ._lsq.least_squares import prepare_bounds from scipy.optimize._minimize import Bounds -# deprecated imports to be removed in SciPy 1.13.0 -from numpy import dot, eye, take # noqa: F401 -from numpy.linalg import inv # noqa: F401 - -error = _minpack.error - __all__ = ['fsolve', 'leastsq', 'fixed_point', 'curve_fit'] diff --git a/scipy/optimize/_slsqp_py.py b/scipy/optimize/_slsqp_py.py index 8a6543bf1ccb..b6b78aafdd2c 100644 --- a/scipy/optimize/_slsqp_py.py +++ b/scipy/optimize/_slsqp_py.py @@ -26,9 +26,6 @@ from ._constraints import old_bound_to_new, _arr_to_scalar from scipy._lib._array_api import atleast_nd, array_namespace -# deprecated imports to be removed in SciPy 1.13.0 -from numpy import exp, inf # noqa: F401 - __docformat__ = "restructuredtext en" diff --git a/scipy/optimize/cobyla.py b/scipy/optimize/cobyla.py index 10e2b6a101a3..87d111d8fc16 100644 --- a/scipy/optimize/cobyla.py +++ b/scipy/optimize/cobyla.py @@ -7,11 +7,7 @@ __all__ = [ # noqa: F822 'OptimizeResult', - 'RLock', 'fmin_cobyla', - 'functools', - 'izip', - 'synchronized', ] def __dir__(): diff --git a/scipy/optimize/lbfgsb.py b/scipy/optimize/lbfgsb.py index 75b395d27396..866407cabb3d 100644 --- a/scipy/optimize/lbfgsb.py +++ b/scipy/optimize/lbfgsb.py @@ -7,14 +7,8 @@ __all__ = [ # noqa: F822 'LbfgsInvHessProduct', - 'LinearOperator', - 'MemoizeJac', 'OptimizeResult', - 'array', - 'asarray', - 'float64', 'fmin_l_bfgs_b', - 'old_bound_to_new', 'zeros', ] diff --git a/scipy/optimize/linesearch.py b/scipy/optimize/linesearch.py index 0d1a04d83ba6..cb34b25092da 100644 --- a/scipy/optimize/linesearch.py +++ b/scipy/optimize/linesearch.py @@ -5,19 +5,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'LineSearchWarning', - 'line_search', - 'line_search_BFGS', - 'line_search_armijo', - 'line_search_wolfe1', - 'line_search_wolfe2', - 'minpack2', - 'scalar_search_armijo', - 'scalar_search_wolfe1', - 'scalar_search_wolfe2', - 'warn', -] +__all__ = ["line_search"] # noqa: F822 def __dir__(): diff --git a/scipy/optimize/minpack.py b/scipy/optimize/minpack.py index b815dec171af..29fddef53736 100644 --- a/scipy/optimize/minpack.py +++ b/scipy/optimize/minpack.py @@ -6,38 +6,13 @@ __all__ = [ # noqa: F822 - 'LEASTSQ_FAILURE', - 'LEASTSQ_SUCCESS', - 'LinAlgError', 'OptimizeResult', 'OptimizeWarning', - 'asarray', - 'atleast_1d', - 'check_gradient', - 'cholesky', 'curve_fit', - 'dot', - 'dtype', - 'error', - 'eye', - 'finfo', 'fixed_point', 'fsolve', - 'greater', - 'inexact', - 'inf', - 'inv', - 'issubdtype', 'least_squares', 'leastsq', - 'prepare_bounds', - 'prod', - 'shape', - 'solve_triangular', - 'svd', - 'take', - 'transpose', - 'triu', 'zeros', ] diff --git a/scipy/optimize/minpack2.py b/scipy/optimize/minpack2.py index 6e961f42403a..cdb3503e0e1e 100644 --- a/scipy/optimize/minpack2.py +++ b/scipy/optimize/minpack2.py @@ -4,11 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation - -__all__ = [ # noqa: F822 - 'dcsrch', - 'dcstep', -] +__all__: list[str] = [] def __dir__(): diff --git a/scipy/optimize/nonlin.py b/scipy/optimize/nonlin.py index 38c43c3d848e..20b490b40ef7 100644 --- a/scipy/optimize/nonlin.py +++ b/scipy/optimize/nonlin.py @@ -6,44 +6,16 @@ __all__ = [ # noqa: F822 - 'Anderson', 'BroydenFirst', - 'BroydenSecond', - 'DiagBroyden', - 'ExcitingMixing', - 'GenericBroyden', 'InverseJacobian', - 'Jacobian', 'KrylovJacobian', - 'LinAlgError', - 'LinearMixing', - 'LowRankMatrix', - 'NoConvergence', - 'TerminationCondition', 'anderson', - 'asarray', - 'asjacobian', 'broyden1', 'broyden2', 'diagbroyden', - 'dot', 'excitingmixing', - 'get_blas_funcs', - 'inspect', - 'inv', 'linearmixing', - 'maxnorm', 'newton_krylov', - 'nonlin_solve', - 'norm', - 'qr', - 'scalar_search_armijo', - 'scalar_search_wolfe1', - 'scipy', - 'solve', - 'svd', - 'sys', - 'vdot', ] diff --git a/scipy/optimize/optimize.py b/scipy/optimize/optimize.py index 81e78d097a75..4db770e5f6e9 100644 --- a/scipy/optimize/optimize.py +++ b/scipy/optimize/optimize.py @@ -6,26 +6,13 @@ __all__ = [ # noqa: F822 - 'Brent', - 'FD_METHODS', - 'LineSearchWarning', - 'MapWrapper', - 'MemoizeJac', 'OptimizeResult', 'OptimizeWarning', - 'ScalarFunction', - 'approx_derivative', - 'approx_fhess_p', 'approx_fprime', - 'argmin', - 'asarray', - 'atleast_1d', 'bracket', 'brent', 'brute', 'check_grad', - 'check_random_state', - 'eye', 'fmin', 'fmin_bfgs', 'fmin_cg', @@ -34,18 +21,11 @@ 'fminbound', 'golden', 'line_search', - 'line_search_wolfe1', - 'line_search_wolfe2', 'rosen', 'rosen_der', 'rosen_hess', 'rosen_hess_prod', - 'shape', 'show_options', - 'sqrt', - 'squeeze', - 'sys', - 'vecnorm', 'zeros', ] diff --git a/scipy/optimize/slsqp.py b/scipy/optimize/slsqp.py index c225c3cbef7e..c2b77d2eb447 100644 --- a/scipy/optimize/slsqp.py +++ b/scipy/optimize/slsqp.py @@ -7,22 +7,8 @@ __all__ = [ # noqa: F822 'OptimizeResult', - 'append', - 'approx_derivative', - 'approx_jacobian', - 'array', - 'atleast_1d', - 'concatenate', - 'exp', - 'finfo', 'fmin_slsqp', - 'inf', - 'isfinite', - 'linalg', - 'old_bound_to_new', 'slsqp', - 'sqrt', - 'vstack', 'zeros', ] diff --git a/scipy/optimize/tnc.py b/scipy/optimize/tnc.py index 92ff24432c68..e0f66058bbcc 100644 --- a/scipy/optimize/tnc.py +++ b/scipy/optimize/tnc.py @@ -6,30 +6,8 @@ __all__ = [ # noqa: F822 - 'CONSTANT', - 'FCONVERGED', - 'INFEASIBLE', - 'LOCALMINIMUM', - 'LSFAIL', - 'MAXFUN', - 'MSGS', - 'MSG_ALL', - 'MSG_EXIT', - 'MSG_INFO', - 'MSG_ITER', - 'MSG_NONE', - 'MSG_VERS', - 'MemoizeJac', - 'NOPROGRESS', 'OptimizeResult', - 'RCSTRINGS', - 'USERABORT', - 'XCONVERGED', - 'array', 'fmin_tnc', - 'inf', - 'moduleTNC', - 'old_bound_to_new', 'zeros', ] diff --git a/scipy/optimize/zeros.py b/scipy/optimize/zeros.py index 0b8fc89eb5f9..907d49d37fc1 100644 --- a/scipy/optimize/zeros.py +++ b/scipy/optimize/zeros.py @@ -6,21 +6,11 @@ __all__ = [ # noqa: F822 - 'CONVERGED', - 'CONVERR', - 'INPROGRESS', 'RootResults', - 'SIGNERR', - 'TOMS748Solver', - 'VALUEERR', 'bisect', 'brenth', 'brentq', - 'flag_map', - 'namedtuple', 'newton', - 'operator', - 'results_c', 'ridder', 'toms748', ] diff --git a/scipy/signal/bsplines.py b/scipy/signal/bsplines.py index a90408cbc666..206e4a2970c9 100644 --- a/scipy/signal/bsplines.py +++ b/scipy/signal/bsplines.py @@ -7,8 +7,6 @@ __all__ = [ # noqa: F822 'spline_filter', 'gauss_spline', 'cspline1d', 'qspline1d', 'cspline1d_eval', 'qspline1d_eval', - 'zeros_like', 'array', 'arctan2', - 'tan', 'arange', 'floor', 'exp', 'greater', 'add', 'cspline2d', 'sepfir2d' ] diff --git a/scipy/signal/filter_design.py b/scipy/signal/filter_design.py index 24f5116e687e..289dd4bb4971 100644 --- a/scipy/signal/filter_design.py +++ b/scipy/signal/filter_design.py @@ -15,12 +15,6 @@ 'sosfreqz', 'iirnotch', 'iirpeak', 'bilinear_zpk', 'lp2lp_zpk', 'lp2hp_zpk', 'lp2bp_zpk', 'lp2bs_zpk', 'gammatone', 'iircomb', - 'atleast_1d', 'poly', 'polyval', 'roots', 'resize', 'absolute', - 'tan', 'log10', 'arcsinh', 'exp', 'arccosh', - 'ceil', 'conjugate', 'append', 'prod', 'full', 'array', 'mintypecode', - 'npp_polyval', 'polyvalfromroots', 'optimize', 'sp_fft', 'comb', - 'float_factorial', 'abs', 'maxflat', 'yulewalk', - 'EPSILON', 'filter_dict', 'band_dict', 'bessel_norms' ] diff --git a/scipy/signal/fir_filter_design.py b/scipy/signal/fir_filter_design.py index e320fc8b4fe2..2214b82998bd 100644 --- a/scipy/signal/fir_filter_design.py +++ b/scipy/signal/fir_filter_design.py @@ -7,8 +7,6 @@ __all__ = [ # noqa: F822 'kaiser_beta', 'kaiser_atten', 'kaiserord', 'firwin', 'firwin2', 'remez', 'firls', 'minimum_phase', - 'ceil', 'log', 'irfft', 'fft', 'ifft', 'sinc', 'toeplitz', - 'hankel', 'solve', 'LinAlgError', 'LinAlgWarning', 'lstsq' ] diff --git a/scipy/signal/lti_conversion.py b/scipy/signal/lti_conversion.py index ed1b247c47c0..7080990afc9e 100644 --- a/scipy/signal/lti_conversion.py +++ b/scipy/signal/lti_conversion.py @@ -6,8 +6,7 @@ __all__ = [ # noqa: F822 'tf2ss', 'abcd_normalize', 'ss2tf', 'zpk2ss', 'ss2zpk', - 'cont2discrete','eye', 'atleast_2d', - 'poly', 'array', 'outer', 'linalg', 'tf2zpk', 'zpk2tf', 'normalize' + 'cont2discrete', 'tf2zpk', 'zpk2tf', 'normalize' ] diff --git a/scipy/signal/ltisys.py b/scipy/signal/ltisys.py index 4c261d468e68..5123068de559 100644 --- a/scipy/signal/ltisys.py +++ b/scipy/signal/ltisys.py @@ -8,15 +8,10 @@ 'lti', 'dlti', 'TransferFunction', 'ZerosPolesGain', 'StateSpace', 'lsim', 'impulse', 'step', 'bode', 'freqresp', 'place_poles', 'dlsim', 'dstep', 'dimpulse', - 'dfreqresp', 'dbode', 's_qr', 'linalg', + 'dfreqresp', 'dbode', 'tf2zpk', 'zpk2tf', 'normalize', 'freqs', 'freqz', 'freqs_zpk', 'freqz_zpk', 'tf2ss', 'abcd_normalize', - 'ss2tf', 'zpk2ss', 'ss2zpk', 'cont2discrete', 'atleast_1d', - 'squeeze', 'transpose', 'linspace', - 'LinearTimeInvariant', 'TransferFunctionContinuous', - 'TransferFunctionDiscrete', 'ZerosPolesGainContinuous', - 'ZerosPolesGainDiscrete', 'StateSpaceContinuous', - 'StateSpaceDiscrete', 'Bunch' + 'ss2tf', 'zpk2ss', 'ss2zpk', 'cont2discrete', ] diff --git a/scipy/signal/signaltools.py b/scipy/signal/signaltools.py index 88f52a5061c6..310c81c93cd7 100644 --- a/scipy/signal/signaltools.py +++ b/scipy/signal/signaltools.py @@ -13,9 +13,7 @@ 'residuez', 'resample', 'resample_poly', 'detrend', 'lfilter_zi', 'sosfilt_zi', 'sosfiltfilt', 'choose_conv_method', 'filtfilt', 'decimate', 'vectorstrength', - 'timeit', 'cKDTree', 'dlti', 'upfirdn', 'linalg', - 'sp_fft', 'lambertw', 'get_window', 'axis_slice', 'axis_reverse', - 'odd_ext', 'even_ext', 'const_ext', 'cheby1', 'firwin' + 'dlti', 'upfirdn', 'get_window', 'cheby1', 'firwin' ] diff --git a/scipy/signal/spectral.py b/scipy/signal/spectral.py index 2e38b400aa75..299ebed781b0 100644 --- a/scipy/signal/spectral.py +++ b/scipy/signal/spectral.py @@ -7,8 +7,7 @@ __all__ = [ # noqa: F822 'periodogram', 'welch', 'lombscargle', 'csd', 'coherence', 'spectrogram', 'stft', 'istft', 'check_COLA', 'check_NOLA', - 'sp_fft', 'get_window', 'const_ext', 'even_ext', - 'odd_ext', 'zero_ext' + 'get_window', ] diff --git a/scipy/signal/waveforms.py b/scipy/signal/waveforms.py index 2fa191bef7fe..30e71348d042 100644 --- a/scipy/signal/waveforms.py +++ b/scipy/signal/waveforms.py @@ -6,8 +6,7 @@ __all__ = [ # noqa: F822 'sawtooth', 'square', 'gausspulse', 'chirp', 'sweep_poly', - 'unit_impulse', 'place', 'nan', 'mod', 'extract', 'log', 'exp', - 'polyval', 'polyint' + 'unit_impulse', ] diff --git a/scipy/signal/wavelets.py b/scipy/signal/wavelets.py index 0391ec2ab35d..d29deac58eb9 100644 --- a/scipy/signal/wavelets.py +++ b/scipy/signal/wavelets.py @@ -6,7 +6,7 @@ __all__ = [ # noqa: F822 'daub', 'qmf', 'cascade', 'morlet', 'ricker', 'morlet2', 'cwt', - 'eig', 'comb', 'convolve' + 'convolve' ] diff --git a/scipy/signal/windows/windows.py b/scipy/signal/windows/windows.py index a449bc1aaafc..6858f71aceeb 100644 --- a/scipy/signal/windows/windows.py +++ b/scipy/signal/windows/windows.py @@ -10,7 +10,6 @@ 'hamming', 'kaiser', 'gaussian', 'general_cosine', 'general_gaussian', 'general_hamming', 'chebwin', 'cosine', 'hann', 'exponential', 'tukey', 'taylor', 'dpss', 'get_window', - 'linalg', 'sp_fft', 'k', 'v', 'key' ] diff --git a/scipy/sparse/lil.py b/scipy/sparse/lil.py index 31e5f20e4887..5f7bf8eb03bb 100644 --- a/scipy/sparse/lil.py +++ b/scipy/sparse/lil.py @@ -6,18 +6,9 @@ __all__ = [ # noqa: F822 - 'INT_TYPES', - 'IndexMixin', - 'bisect_left', - 'check_reshape_kwargs', - 'check_shape', - 'getdtype', - 'isscalarlike', - 'isshape', 'isspmatrix_lil', + 'lil_array', 'lil_matrix', - 'spmatrix', - 'upcast_scalar', ] diff --git a/scipy/sparse/linalg/dsolve.py b/scipy/sparse/linalg/dsolve.py index e4d0a33200cd..45139f6b280d 100644 --- a/scipy/sparse/linalg/dsolve.py +++ b/scipy/sparse/linalg/dsolve.py @@ -8,11 +8,9 @@ __all__ = [ # noqa: F822 'MatrixRankWarning', 'SuperLU', 'factorized', 'spilu', 'splu', 'spsolve', - 'spsolve_triangular', 'use_solver', 'linsolve', 'test' + 'spsolve_triangular', 'use_solver', 'test' ] -dsolve_modules = ['linsolve'] - def __dir__(): return __all__ diff --git a/scipy/sparse/linalg/eigen.py b/scipy/sparse/linalg/eigen.py index 0022daecea99..588986d6650a 100644 --- a/scipy/sparse/linalg/eigen.py +++ b/scipy/sparse/linalg/eigen.py @@ -7,11 +7,9 @@ __all__ = [ # noqa: F822 'ArpackError', 'ArpackNoConvergence', 'ArpackError', - 'eigs', 'eigsh', 'lobpcg', 'svds', 'arpack', 'test' + 'eigs', 'eigsh', 'lobpcg', 'svds', 'test' ] -eigen_modules = ['arpack'] - def __dir__(): return __all__ diff --git a/scipy/sparse/linalg/interface.py b/scipy/sparse/linalg/interface.py index 9f4c63591106..24f40f185b13 100644 --- a/scipy/sparse/linalg/interface.py +++ b/scipy/sparse/linalg/interface.py @@ -7,8 +7,6 @@ __all__ = [ # noqa: F822 'LinearOperator', 'aslinearoperator', - 'isshape', 'isintlike', 'asmatrix', - 'is_pydata_spmatrix', 'MatrixLinearOperator', 'IdentityOperator' ] diff --git a/scipy/sparse/linalg/isolve.py b/scipy/sparse/linalg/isolve.py index 61e5655caf4b..e032ddd9c673 100644 --- a/scipy/sparse/linalg/isolve.py +++ b/scipy/sparse/linalg/isolve.py @@ -8,7 +8,7 @@ __all__ = [ # noqa: F822 'bicg', 'bicgstab', 'cg', 'cgs', 'gcrotmk', 'gmres', 'lgmres', 'lsmr', 'lsqr', - 'minres', 'qmr', 'tfqmr', 'utils', 'iterative', 'test' + 'minres', 'qmr', 'tfqmr', 'test' ] diff --git a/scipy/sparse/linalg/matfuncs.py b/scipy/sparse/linalg/matfuncs.py index 3535a83aaf98..8ed877ff1aa6 100644 --- a/scipy/sparse/linalg/matfuncs.py +++ b/scipy/sparse/linalg/matfuncs.py @@ -5,11 +5,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'expm', 'inv', 'solve', 'solve_triangular', - 'spsolve', 'is_pydata_spmatrix', 'LinearOperator', - 'UPPER_TRIANGULAR', 'MatrixPowerOperator', 'ProductOperator' -] +__all__ = ["expm", "inv", "spsolve", "LinearOperator"] # noqa: F822 def __dir__(): diff --git a/scipy/sparse/sparsetools.py b/scipy/sparse/sparsetools.py index 47ac80adae71..404e431d89d4 100644 --- a/scipy/sparse/sparsetools.py +++ b/scipy/sparse/sparsetools.py @@ -4,88 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation - -__all__ = [ # noqa: F822 - 'bsr_diagonal', - 'bsr_eldiv_bsr', - 'bsr_elmul_bsr', - 'bsr_ge_bsr', - 'bsr_gt_bsr', - 'bsr_le_bsr', - 'bsr_lt_bsr', - 'bsr_matmat', - 'bsr_matvec', - 'bsr_matvecs', - 'bsr_maximum_bsr', - 'bsr_minimum_bsr', - 'bsr_minus_bsr', - 'bsr_ne_bsr', - 'bsr_plus_bsr', - 'bsr_scale_columns', - 'bsr_scale_rows', - 'bsr_sort_indices', - 'bsr_tocsr', - 'bsr_transpose', - 'coo_matvec', - 'coo_tocsr', - 'coo_todense', - 'cs_graph_components', - 'csc_diagonal', - 'csc_eldiv_csc', - 'csc_elmul_csc', - 'csc_ge_csc', - 'csc_gt_csc', - 'csc_le_csc', - 'csc_lt_csc', - 'csc_matmat', - 'csc_matmat_maxnnz', - 'csc_matvec', - 'csc_matvecs', - 'csc_maximum_csc', - 'csc_minimum_csc', - 'csc_minus_csc', - 'csc_ne_csc', - 'csc_plus_csc', - 'csc_tocsr', - 'csr_column_index1', - 'csr_column_index2', - 'csr_count_blocks', - 'csr_diagonal', - 'csr_eldiv_csr', - 'csr_eliminate_zeros', - 'csr_elmul_csr', - 'csr_ge_csr', - 'csr_gt_csr', - 'csr_has_canonical_format', - 'csr_has_sorted_indices', - 'csr_hstack', - 'csr_le_csr', - 'csr_lt_csr', - 'csr_matmat', - 'csr_matmat_maxnnz', - 'csr_matvec', - 'csr_matvecs', - 'csr_maximum_csr', - 'csr_minimum_csr', - 'csr_minus_csr', - 'csr_ne_csr', - 'csr_plus_csr', - 'csr_row_index', - 'csr_row_slice', - 'csr_sample_offsets', - 'csr_sample_values', - 'csr_scale_columns', - 'csr_scale_rows', - 'csr_sort_indices', - 'csr_sum_duplicates', - 'csr_tobsr', - 'csr_tocsc', - 'csr_todense', - 'dia_matvec', - 'expandptr', - 'get_csr_submatrix', - 'test_throw_error', -] +__all__: list[str] = [] def __dir__(): diff --git a/scipy/sparse/spfuncs.py b/scipy/sparse/spfuncs.py index b005a9b7c56b..911969e414d4 100644 --- a/scipy/sparse/spfuncs.py +++ b/scipy/sparse/spfuncs.py @@ -4,12 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation - -__all__ = [ # noqa: F822 - 'csr_count_blocks', - 'estimate_blocksize', - 'count_blocks' -] +__all__: list[str] = [] def __dir__(): diff --git a/scipy/sparse/sputils.py b/scipy/sparse/sputils.py index bdacb42dd0fb..4ddd27a43889 100644 --- a/scipy/sparse/sputils.py +++ b/scipy/sparse/sputils.py @@ -4,34 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation - -__all__ = [ # noqa: F822 - 'asmatrix', - 'check_reshape_kwargs', - 'check_shape', - 'downcast_intp_index', - 'get_index_dtype', - 'get_sum_dtype', - 'getdata', - 'getdtype', - 'is_pydata_spmatrix', - 'isdense', - 'isintlike', - 'ismatrix', - 'isscalarlike', - 'issequence', - 'isshape', - 'matrix', - 'operator', - 'prod', - 'supported_dtypes', - 'sys', - 'to_native', - 'upcast', - 'upcast_char', - 'upcast_scalar', - 'validateaxis', -] +__all__: list[str] = [] def __dir__(): diff --git a/scipy/spatial/ckdtree.py b/scipy/spatial/ckdtree.py index b838f29cc0d9..40f524c71bf1 100644 --- a/scipy/spatial/ckdtree.py +++ b/scipy/spatial/ckdtree.py @@ -5,16 +5,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'cKDTree', - 'cKDTreeNode', - 'coo_entries', - 'operator', - 'ordered_pairs', - 'os', - 'scipy', - 'threading', -] +__all__ = ["cKDTree"] # noqa: F822 def __dir__(): diff --git a/scipy/spatial/kdtree.py b/scipy/spatial/kdtree.py index c0a6bab41e94..512205ddc15d 100644 --- a/scipy/spatial/kdtree.py +++ b/scipy/spatial/kdtree.py @@ -9,7 +9,6 @@ 'KDTree', 'Rectangle', 'cKDTree', - 'cKDTreeNode', 'distance_matrix', 'minkowski_distance', 'minkowski_distance_p', diff --git a/scipy/spatial/transform/rotation.py b/scipy/spatial/transform/rotation.py index 8aee10d960cf..a71943741512 100644 --- a/scipy/spatial/transform/rotation.py +++ b/scipy/spatial/transform/rotation.py @@ -8,9 +8,6 @@ __all__ = [ # noqa: F822 'Rotation', 'Slerp', - 'check_random_state', - 'create_group', - 're', ] diff --git a/scipy/special/add_newdocs.py b/scipy/special/add_newdocs.py index d1d60bf8e02d..5549717d3571 100644 --- a/scipy/special/add_newdocs.py +++ b/scipy/special/add_newdocs.py @@ -2,7 +2,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = ['get', 'add_newdoc', 'docdict'] # noqa: F822 +__all__: list[str] = [] def __dir__(): diff --git a/scipy/special/orthogonal.py b/scipy/special/orthogonal.py index f9e39d75d4e2..0b13a08a96cb 100644 --- a/scipy/special/orthogonal.py +++ b/scipy/special/orthogonal.py @@ -29,9 +29,7 @@ __all__ = _polyfuns + list(_rootfuns_map.keys()) + [ # noqa: F822 - 'exp', 'inf', 'floor', 'around', 'hstack', 'arange', - 'linalg', 'airy', 'orthopoly1d', 'newfun', - 'oldfun', 'p_roots', 't_roots', 'u_roots', 'c_roots', 's_roots', + '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' ] diff --git a/scipy/special/spfun_stats.py b/scipy/special/spfun_stats.py index f51eacff7abe..a1e58487aaa5 100644 --- a/scipy/special/spfun_stats.py +++ b/scipy/special/spfun_stats.py @@ -4,7 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = ['multigammaln', 'loggam'] # noqa: F822 +__all__ = ['multigammaln'] # noqa: F822 def __dir__(): diff --git a/scipy/stats/_kde.py b/scipy/stats/_kde.py index c1e4ed6acc64..40ff35934e21 100644 --- a/scipy/stats/_kde.py +++ b/scipy/stats/_kde.py @@ -33,9 +33,6 @@ from . import _mvn from ._stats import gaussian_kernel_estimate, gaussian_kernel_estimate_log -# deprecated import to be removed in SciPy 1.13.0 -from scipy.special import logsumexp # noqa: F401 - __all__ = ['gaussian_kde'] diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index c2ddc40e6d40..e8253bddce5f 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -5,9 +5,8 @@ import numpy as np from numpy import (isscalar, r_, log, around, unique, asarray, zeros, - arange, sort, amin, amax, sqrt, array, atleast_1d, # noqa: F401 - compress, pi, exp, ravel, count_nonzero, sin, cos, # noqa: F401 - arctan2, hypot) # noqa: F401 + 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 diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 2c9215c4c689..1aa3644163e2 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -71,12 +71,6 @@ xp_clip, xp_moveaxis_to_end, xp_sign) from scipy._lib.array_api_compat import size as xp_size -# In __all__ but deprecated for removal in SciPy 1.13.0 -from scipy._lib._util import float_factorial # noqa: F401 -from scipy.stats._mstats_basic import ( # noqa: F401 - PointbiserialrResult, Ttest_1sampResult, Ttest_relResult -) - # Functions/classes in other files should be added in `__init__.py`, not here __all__ = ['find_repeats', 'gmean', 'hmean', 'pmean', 'mode', 'tmean', 'tvar', diff --git a/scipy/stats/biasedurn.py b/scipy/stats/biasedurn.py index f5e1cd5c8489..2b1c9f1cf2b3 100644 --- a/scipy/stats/biasedurn.py +++ b/scipy/stats/biasedurn.py @@ -3,11 +3,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - '_PyFishersNCHypergeometric', - '_PyWalleniusNCHypergeometric', - '_PyStochasticLib3' -] +__all__: list[str] = [] def __dir__(): diff --git a/scipy/stats/kde.py b/scipy/stats/kde.py index 08e299b5137c..4401da5a30f4 100644 --- a/scipy/stats/kde.py +++ b/scipy/stats/kde.py @@ -5,12 +5,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'gaussian_kde', 'linalg', 'logsumexp', 'check_random_state', - 'atleast_2d', 'reshape', 'newaxis', 'exp', 'ravel', 'power', - 'atleast_1d', 'squeeze', 'sum', 'transpose', 'cov', - 'gaussian_kernel_estimate' -] +__all__ = ["gaussian_kde"] # noqa: F822 def __dir__(): diff --git a/scipy/stats/morestats.py b/scipy/stats/morestats.py index fce0b989d8b5..ee8e6f43b7aa 100644 --- a/scipy/stats/morestats.py +++ b/scipy/stats/morestats.py @@ -13,14 +13,7 @@ 'fligner', 'mood', 'wilcoxon', 'median_test', 'circmean', 'circvar', 'circstd', 'anderson_ksamp', 'yeojohnson_llf', 'yeojohnson', 'yeojohnson_normmax', - 'yeojohnson_normplot', 'annotations', 'namedtuple', 'isscalar', 'log', - 'around', 'unique', 'arange', 'sort', 'amin', 'amax', 'atleast_1d', - 'array', 'compress', 'exp', 'ravel', 'count_nonzero', 'arctan2', - 'hypot', 'optimize', 'find_repeats', - 'chi2_contingency', 'distributions', 'rv_generic', 'Mean', - 'Variance', 'Std_dev', 'ShapiroResult', 'AndersonResult', - 'Anderson_ksampResult', 'AnsariResult', 'BartlettResult', - 'LeveneResult', 'FlignerResult', 'WilcoxonResult' + 'yeojohnson_normplot', 'find_repeats', 'chi2_contingency', 'distributions', ] diff --git a/scipy/stats/mstats_basic.py b/scipy/stats/mstats_basic.py index f79f07bed7a0..19cc67a6acdf 100644 --- a/scipy/stats/mstats_basic.py +++ b/scipy/stats/mstats_basic.py @@ -28,15 +28,7 @@ 'ttest_ind','ttest_rel','tvar', 'variation', 'winsorize', - 'brunnermunzel', 'ma', 'masked', 'nomask', 'namedtuple', - 'distributions', 'stats_linregress', 'stats_LinregressResult', - 'stats_theilslopes', 'stats_siegelslopes', 'ModeResult', - 'PointbiserialrResult', - 'Ttest_1sampResult', 'Ttest_indResult', 'Ttest_relResult', - 'MannwhitneyuResult', 'KruskalResult', 'trimdoc', 'trim1', - 'DescribeResult', 'stde_median', 'SkewtestResult', 'KurtosistestResult', - 'NormaltestResult', 'F_onewayResult', 'FriedmanchisquareResult', - 'BrunnerMunzelResult' + 'brunnermunzel', ] diff --git a/scipy/stats/mstats_extras.py b/scipy/stats/mstats_extras.py index 01a19f22b257..fec695329cf2 100644 --- a/scipy/stats/mstats_extras.py +++ b/scipy/stats/mstats_extras.py @@ -11,8 +11,7 @@ 'idealfourths', 'median_cihs','mjci','mquantiles_cimj', 'rsh', - 'trimmed_mean_ci', 'ma', 'MaskedArray', 'mstats', - 'norm', 'beta', 't', 'binom' + 'trimmed_mean_ci', ] diff --git a/scipy/stats/mvn.py b/scipy/stats/mvn.py index 832993d47029..65da9e20f6a4 100644 --- a/scipy/stats/mvn.py +++ b/scipy/stats/mvn.py @@ -4,13 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation - -__all__ = [ # noqa: F822 - 'mvnun', - 'mvnun_weighted', - 'mvndst', - 'dkblck' -] +__all__: list[str] = [] def __dir__(): diff --git a/scipy/stats/stats.py b/scipy/stats/stats.py index 142686e08ab5..1c1a5d4c0636 100644 --- a/scipy/stats/stats.py +++ b/scipy/stats/stats.py @@ -26,19 +26,8 @@ 'tiecorrect', 'ranksums', 'kruskal', 'friedmanchisquare', 'rankdata', 'combine_pvalues', 'wasserstein_distance', 'energy_distance', - 'brunnermunzel', 'alexandergovern', 'gcd', 'namedtuple', 'array', - 'ma', 'cdist', 'check_random_state', 'MapWrapper', - 'rng_integers', 'float_factorial', 'linalg', 'distributions', - 'mstats_basic', 'ModeResult', 'DescribeResult', - 'SkewtestResult', 'KurtosistestResult', 'NormaltestResult', - 'HistogramResult', 'CumfreqResult', - 'RelfreqResult', 'SigmaclipResult', 'F_onewayResult', - 'AlexanderGovernResult', - 'PointbiserialrResult', - 'MGCResult', 'Ttest_1sampResult', 'Ttest_indResult', - 'Ttest_relResult', 'Power_divergenceResult', 'KstestResult', - 'Ks_2sampResult', 'RanksumsResult', 'KruskalResult', - 'FriedmanchisquareResult', 'BrunnerMunzelResult', 'RepeatedResults' + 'brunnermunzel', 'alexandergovern', 'distributions', + 'mstats_basic', ] From b1797ed145b10e1925215a8273278fa66a04fc42 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 18 May 2024 10:28:33 -0400 Subject: [PATCH 199/500] MAINT: optimize: another fail_slow exception for COBYQA (#20741) --- scipy/optimize/tests/test_constraint_conversion.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/optimize/tests/test_constraint_conversion.py b/scipy/optimize/tests/test_constraint_conversion.py index d3ae20f23342..df5ab6dee478 100644 --- a/scipy/optimize/tests/test_constraint_conversion.py +++ b/scipy/optimize/tests/test_constraint_conversion.py @@ -90,6 +90,7 @@ def fun(x): assert_allclose(funs['cobyla'], funs['trust-constr'], rtol=1e-4) assert_allclose(funs['cobyqa'], funs['trust-constr'], rtol=1e-4) + @pytest.mark.fail_slow(10) def test_individual_constraint_objects(self): def fun(x): return (x[0] - 1) ** 2 + (x[1] - 2.5) ** 2 + (x[2] - 0.75) ** 2 From 10c6747878f237e2fb2ea28b225f5c841a1df93c Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 18 May 2024 18:52:33 +0300 Subject: [PATCH 200/500] BUG: interpolate: do not segfault on bad boundary conditions (#20732) * BUG: interpolate: do not segfault on bad boundary conditions * Update scipy/interpolate/tests/test_bsplines.py * Update scipy/interpolate/tests/test_bsplines.py * add match statements * Update scipy/interpolate/tests/test_bsplines.py --------- Co-authored-by: Jake Bowhay --- scipy/interpolate/_bsplines.py | 6 ++++++ scipy/interpolate/tests/test_bsplines.py | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/scipy/interpolate/_bsplines.py b/scipy/interpolate/_bsplines.py index 76eb95bf8b61..72c7b8cad80e 100644 --- a/scipy/interpolate/_bsplines.py +++ b/scipy/interpolate/_bsplines.py @@ -1453,6 +1453,12 @@ def make_interp_spline(x, y, k=3, t=None, bc_type=None, axis=0, deriv_r_ords, deriv_r_vals = _process_deriv_spec(deriv_r) nright = deriv_r_ords.shape[0] + if not all(0 <= i <= k for i in deriv_l_ords): + raise ValueError(f"Bad boundary conditions at {x[0]}.") + + if not all(0 <= i <= k for i in deriv_r_ords): + raise ValueError(f"Bad boundary conditions at {x[-1]}.") + # have `n` conditions for `nt` coefficients; need nt-n derivatives n = x.size nt = t.size - k - 1 diff --git a/scipy/interpolate/tests/test_bsplines.py b/scipy/interpolate/tests/test_bsplines.py index 4f16a89b394f..b4008c19915c 100644 --- a/scipy/interpolate/tests/test_bsplines.py +++ b/scipy/interpolate/tests/test_bsplines.py @@ -1346,6 +1346,19 @@ def test_deriv_spec(self): with assert_raises(ValueError): make_interp_spline(x, y, bc_type=(l, r)) + def test_deriv_order_too_large(self): + x = np.arange(7) + y = x**2 + l, r = [(6, 0)], [(1, 0)] # 6th derivative = 0 at x[0] for k=3 + with assert_raises(ValueError, match="Bad boundary conditions at 0."): + # cannot fix 6th derivative at x[0]: does not segfault + make_interp_spline(x, y, bc_type=(l, r)) + + l, r = [(1, 0)], [(-6, 0)] # derivative order < 0 at x[-1] + with assert_raises(ValueError, match="Bad boundary conditions at 6."): + # does not segfault + make_interp_spline(x, y, bc_type=(l, r)) + def test_complex(self): k = 3 xx = self.xx From de48c7418e99287d5d5992e777f7699681ffe7ae Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Sat, 18 May 2024 21:05:54 +0100 Subject: [PATCH 201/500] MAINT/TST: fft: remove xp backend skips, test `fftfreq` `device` (#19900) --- scipy/_lib/_array_api.py | 43 ++++++++++++- scipy/fft/tests/test_basic.py | 37 +---------- scipy/fft/tests/test_helper.py | 110 +++++++++++++++++---------------- 3 files changed, 99 insertions(+), 91 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 0505305be62f..da5d696e0b6a 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -22,9 +22,10 @@ is_array_api_obj, size, numpy as np_compat, + device ) -__all__ = ['array_namespace', '_asarray', 'size'] +__all__ = ['array_namespace', '_asarray', 'size', 'device'] # To enable array API and strict array-like input validation @@ -393,7 +394,44 @@ def xp_unsupported_param_msg(param: Any) -> str: def is_complex(x: Array, xp: ModuleType) -> bool: return xp.isdtype(x.dtype, 'complex floating') -def scipy_namespace_for(xp): + +def get_xp_devices(xp: ModuleType) -> list[str] | list[None]: + """Returns a list of available devices for the given namespace.""" + devices: list[str] = [] + if is_torch(xp): + devices += ['cpu'] + import torch # type: ignore[import] + num_cuda = torch.cuda.device_count() + for i in range(0, num_cuda): + devices += [f'cuda:{i}'] + if torch.backends.mps.is_available(): + devices += ['mps'] + return devices + elif is_cupy(xp): + import cupy # type: ignore[import] + num_cuda = cupy.cuda.runtime.getDeviceCount() + for i in range(0, num_cuda): + devices += [f'cuda:{i}'] + return devices + elif is_jax(xp): + import jax # type: ignore[import] + num_cpu = jax.device_count(backend='cpu') + for i in range(0, num_cpu): + devices += [f'cpu:{i}'] + num_gpu = jax.device_count(backend='gpu') + for i in range(0, num_gpu): + devices += [f'gpu:{i}'] + num_tpu = jax.device_count(backend='tpu') + for i in range(0, num_tpu): + devices += [f'tpu:{i}'] + return devices + + # given namespace is not known to have a list of available devices; + # return `[None]` so that one can use this in tests for `device=None`. + return [None] + + +def scipy_namespace_for(xp: ModuleType) -> ModuleType: """ Return the `scipy` namespace for alternative backends, where it exists, such as `cupyx.scipy` and `jax.scipy`. Useful for ad hoc dispatching. @@ -413,6 +451,7 @@ def scipy_namespace_for(xp): import scipy return scipy + # temporary substitute for xp.minimum, which is not yet in all backends # or covered by array_api_compat. def xp_minimum(x1, x2): diff --git a/scipy/fft/tests/test_basic.py b/scipy/fft/tests/test_basic.py index f4c795c0e3b8..8239f16eefc9 100644 --- a/scipy/fft/tests/test_basic.py +++ b/scipy/fft/tests/test_basic.py @@ -81,8 +81,6 @@ def test_ifft(self, xp): for norm in ["backward", "ortho", "forward"]: xp_assert_close(fft.ifft(fft.fft(x, norm=norm), norm=norm), x) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_fft2(self, xp): x = xp.asarray(random((30, 20)) + 1j*random((30, 20))) expect = fft.fft(fft.fft(x, axis=1), axis=0) @@ -92,8 +90,6 @@ def test_fft2(self, xp): expect / xp.sqrt(xp.asarray(30 * 20, dtype=xp.float64))) xp_assert_close(fft.fft2(x, norm="forward"), expect / (30 * 20)) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_ifft2(self, xp): x = xp.asarray(random((30, 20)) + 1j*random((30, 20))) expect = fft.ifft(fft.ifft(x, axis=1), axis=0) @@ -103,8 +99,6 @@ def test_ifft2(self, xp): expect * xp.sqrt(xp.asarray(30 * 20, dtype=xp.float64))) xp_assert_close(fft.ifft2(x, norm="forward"), expect * (30 * 20)) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_fftn(self, xp): x = xp.asarray(random((30, 20, 10)) + 1j*random((30, 20, 10))) expect = fft.fft(fft.fft(fft.fft(x, axis=2), axis=1), axis=0) @@ -114,8 +108,6 @@ def test_fftn(self, xp): expect / xp.sqrt(xp.asarray(30 * 20 * 10, dtype=xp.float64))) xp_assert_close(fft.fftn(x, norm="forward"), expect / (30 * 20 * 10)) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_ifftn(self, xp): x = xp.asarray(random((30, 20, 10)) + 1j*random((30, 20, 10))) expect = fft.ifft(fft.ifft(fft.ifft(x, axis=2), axis=1), axis=0) @@ -147,8 +139,6 @@ def test_irfft(self, xp): for norm in ["backward", "ortho", "forward"]: xp_assert_close(fft.irfft(fft.rfft(x, norm=norm), norm=norm), x) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_rfft2(self, xp): x = xp.asarray(random((30, 20)), dtype=xp.float64) expect = fft.fft2(xp.asarray(x, dtype=xp.complex128))[:, :11] @@ -158,16 +148,12 @@ def test_rfft2(self, xp): expect / xp.sqrt(xp.asarray(30 * 20, dtype=xp.float64))) xp_assert_close(fft.rfft2(x, norm="forward"), expect / (30 * 20)) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_irfft2(self, xp): x = xp.asarray(random((30, 20))) xp_assert_close(fft.irfft2(fft.rfft2(x)), x) for norm in ["backward", "ortho", "forward"]: xp_assert_close(fft.irfft2(fft.rfft2(x, norm=norm), norm=norm), x) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_rfftn(self, xp): x = xp.asarray(random((30, 20, 10)), dtype=xp.float64) expect = fft.fftn(xp.asarray(x, dtype=xp.complex128))[:, :, :6] @@ -177,8 +163,6 @@ def test_rfftn(self, xp): expect / xp.sqrt(xp.asarray(30 * 20 * 10, dtype=xp.float64))) xp_assert_close(fft.rfftn(x, norm="forward"), expect / (30 * 20 * 10)) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_irfftn(self, xp): x = xp.asarray(random((30, 20, 10))) xp_assert_close(fft.irfftn(fft.rfftn(x)), x) @@ -208,16 +192,12 @@ def test_ihfft(self, xp): for norm in ["backward", "ortho", "forward"]: xp_assert_close(fft.ihfft(fft.hfft(x_herm, norm=norm), norm=norm), x_herm) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_hfft2(self, xp): x = xp.asarray(random((30, 20))) xp_assert_close(fft.hfft2(fft.ihfft2(x)), x) for norm in ["backward", "ortho", "forward"]: xp_assert_close(fft.hfft2(fft.ihfft2(x, norm=norm), norm=norm), x) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_ihfft2(self, xp): x = xp.asarray(random((30, 20)), dtype=xp.float64) expect = fft.ifft2(xp.asarray(x, dtype=xp.complex128))[:, :11] @@ -229,16 +209,12 @@ def test_ihfft2(self, xp): ) xp_assert_close(fft.ihfft2(x, norm="forward"), expect * (30 * 20)) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_hfftn(self, xp): x = xp.asarray(random((30, 20, 10))) xp_assert_close(fft.hfftn(fft.ihfftn(x)), x) for norm in ["backward", "ortho", "forward"]: xp_assert_close(fft.hfftn(fft.ihfftn(x, norm=norm), norm=norm), x) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) def test_ihfftn(self, xp): x = xp.asarray(random((30, 20, 10)), dtype=xp.float64) expect = fft.ifftn(xp.asarray(x, dtype=xp.complex128))[:, :, :6] @@ -260,20 +236,14 @@ def _check_axes(self, op, xp): tr_op = xp_test.permute_dims(op(x, axes=a), axes=a) xp_assert_close(op_tr, tr_op) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) @pytest.mark.parametrize("op", [fft.fftn, fft.ifftn, fft.rfftn, fft.irfftn]) def test_axes_standard(self, op, xp): self._check_axes(op, xp) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) @pytest.mark.parametrize("op", [fft.hfftn, fft.ihfftn]) def test_axes_non_standard(self, op, xp): self._check_axes(op, xp) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) @pytest.mark.parametrize("op", [fft.fftn, fft.ifftn, fft.rfftn, fft.irfftn]) def test_axes_subset_with_shape_standard(self, op, xp): @@ -292,8 +262,6 @@ def test_axes_subset_with_shape_standard(self, op, xp): axes=a) xp_assert_close(op_tr, tr_op) - @skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) @pytest.mark.parametrize("op", [fft.fft2, fft.ifft2, fft.rfft2, fft.irfft2, fft.hfft2, fft.ihfft2, @@ -410,6 +378,7 @@ def test_fft_with_order(dtype, order, fft): raise ValueError +@skip_xp_backends(cpu_only=True) class TestFFTThreadSafe: threads = 16 input_shape = (800, 200) @@ -473,8 +442,6 @@ def test_multiprocess(func): assert_allclose(x, expect) -@skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) class TestIRFFTN: def test_not_last_axis_success(self, xp): @@ -488,8 +455,6 @@ def test_not_last_axis_success(self, xp): fft.irfftn(a, axes=axes) -@skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) @pytest.mark.parametrize("func", [fft.fft, fft.ifft, fft.rfft, fft.irfft, fft.fftn, fft.ifftn, fft.rfftn, fft.irfftn, fft.hfft, fft.ihfft]) diff --git a/scipy/fft/tests/test_helper.py b/scipy/fft/tests/test_helper.py index 3c7326f54e47..88367a06123a 100644 --- a/scipy/fft/tests/test_helper.py +++ b/scipy/fft/tests/test_helper.py @@ -11,7 +11,9 @@ import numpy as np import sys from scipy.conftest import array_api_compatible -from scipy._lib._array_api import xp_assert_close, SCIPY_DEVICE, array_namespace +from scipy._lib._array_api import ( + xp_assert_close, get_xp_devices, device, array_namespace +) from scipy import fft pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends")] @@ -315,8 +317,6 @@ def test_errors(self, xp): _init_nd_shape_and_axes(x, shape=-2, axes=None) -@skip_xp_backends('torch', - reasons=['torch.fft not yet implemented by array-api-compat']) class TestFFTShift: def test_definition(self, xp): @@ -391,61 +391,65 @@ def test_uneven_dims(self, xp): xp_assert_close(fft.ifftshift(shift_dim_both), freqs) -@skip_xp_backends('array_api_strict', 'cupy', - reasons=['fft not yet implemented by array-api-strict', - 'cupy.fft not yet implemented by array-api-compat']) +@skip_xp_backends("cupy", "jax.numpy", + reasons=["CuPy has not implemented the `device` param", + "JAX has not implemented the `device` param"]) class TestFFTFreq: def test_definition(self, xp): - device = SCIPY_DEVICE + x = xp.asarray([0, 1, 2, 3, 4, -4, -3, -2, -1], dtype=xp.float64) + x2 = xp.asarray([0, 1, 2, 3, 4, -5, -4, -3, -2, -1], dtype=xp.float64) + + # default dtype varies across backends + + y = 9 * fft.fftfreq(9, xp=xp) + xp_assert_close(y, x, check_dtype=False, check_namespace=True) + + y = 9 * xp.pi * fft.fftfreq(9, xp.pi, xp=xp) + xp_assert_close(y, x, check_dtype=False) + + y = 10 * fft.fftfreq(10, xp=xp) + xp_assert_close(y, x2, check_dtype=False) + + y = 10 * xp.pi * fft.fftfreq(10, xp.pi, xp=xp) + xp_assert_close(y, x2, check_dtype=False) + + def test_device(self, xp): xp_test = array_namespace(xp.empty(0)) - try: - x = xp.asarray([0, 1, 2, 3, 4, -4, -3, -2, -1], - dtype=xp.float64, device=device) - x2 = xp.asarray([0, 1, 2, 3, 4, -5, -4, -3, -2, -1], - dtype=xp.float64, device=device) - except TypeError: - x = xp.asarray([0, 1, 2, 3, 4, -4, -3, -2, -1], dtype=xp.float64) - x2 = xp.asarray([0, 1, 2, 3, 4, -5, -4, -3, -2, -1], - dtype=xp.float64) - - y = xp.asarray(9 * fft.fftfreq(9, xp=xp_test), dtype=xp.float64) - xp_assert_close(y, x) - y = xp.asarray(9 * xp.pi * fft.fftfreq(9, xp.pi, xp=xp_test), dtype=xp.float64) - xp_assert_close(y, x) - - y = xp.asarray(10 * fft.fftfreq(10, xp=xp_test), - dtype=xp.float64) - xp_assert_close(y, x2) - y = xp.asarray(10 * xp.pi * fft.fftfreq(10, xp.pi, xp=xp_test), - dtype=xp.float64) - xp_assert_close(y, x2) - - -@skip_xp_backends('array_api_strict', 'cupy', - reasons=['fft not yet implemented by array-api-strict', - 'cupy.fft not yet implemented by array-api-compat']) + devices = get_xp_devices(xp) + for d in devices: + y = fft.fftfreq(9, xp=xp, device=d) + x = xp_test.empty(0, device=d) + assert device(y) == device(x) + + +@skip_xp_backends("cupy", "jax.numpy", + reasons=["CuPy has not implemented the `device` param", + "JAX has not implemented the `device` param"]) class TestRFFTFreq: def test_definition(self, xp): - device = SCIPY_DEVICE + x = xp.asarray([0, 1, 2, 3, 4], dtype=xp.float64) + x2 = xp.asarray([0, 1, 2, 3, 4, 5], dtype=xp.float64) + + # default dtype varies across backends + + y = 9 * fft.rfftfreq(9, xp=xp) + xp_assert_close(y, x, check_dtype=False, check_namespace=True) + + y = 9 * xp.pi * fft.rfftfreq(9, xp.pi, xp=xp) + xp_assert_close(y, x, check_dtype=False) + + y = 10 * fft.rfftfreq(10, xp=xp) + xp_assert_close(y, x2, check_dtype=False) + + y = 10 * xp.pi * fft.rfftfreq(10, xp.pi, xp=xp) + xp_assert_close(y, x2, check_dtype=False) + + def test_device(self, xp): xp_test = array_namespace(xp.empty(0)) - try: - x = xp.asarray([0, 1, 2, 3, 4], dtype=xp.float64, device=device) - x2 = xp.asarray([0, 1, 2, 3, 4, 5], dtype=xp.float64, device=device) - except TypeError: - # work around the `device` keyword not being implemented in numpy yet - x = xp.asarray([0, 1, 2, 3, 4], dtype=xp.float64) - x2 = xp.asarray([0, 1, 2, 3, 4, 5], dtype=xp.float64) - - y = xp.asarray(9 * fft.rfftfreq(9, xp=xp_test), dtype=xp.float64) - xp_assert_close(y, x) - y = xp.asarray(9 * xp.pi * fft.rfftfreq(9, xp.pi, xp=xp_test), dtype=xp.float64) - xp_assert_close(y, x) - - y = xp.asarray(10 * fft.rfftfreq(10, xp=xp_test), - dtype=xp.float64) - xp_assert_close(y, x2) - y = xp.asarray(10 * xp.pi * fft.rfftfreq(10, xp.pi, xp=xp_test), - dtype=xp.float64) - xp_assert_close(y, x2) + devices = get_xp_devices(xp) + for d in devices: + y = fft.rfftfreq(9, xp=xp, device=d) + x = xp_test.empty(0, device=d) + assert device(y) == device(x) From 6352640893a4cab6b0597394d276689284929cbf Mon Sep 17 00:00:00 2001 From: Albert Steppi Date: Sun, 19 May 2024 02:20:51 -0400 Subject: [PATCH 202/500] BUG: special: Fix incorrect brackets in cephes hyperg.h (#20745) --- scipy/special/special/cephes/hyperg.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/scipy/special/special/cephes/hyperg.h b/scipy/special/special/cephes/hyperg.h index 1d30ff092641..9a4ff0c760e1 100644 --- a/scipy/special/special/cephes/hyperg.h +++ b/scipy/special/special/cephes/hyperg.h @@ -237,17 +237,15 @@ namespace cephes { /* nan */ acanc = 1.0; - if (std::isinf(asum)) { + if (std::isinf(asum)) /* infinity */ acanc = 0; - acanc *= 30.0; /* fudge factor, since error of asymptotic formula - * often seems this much larger than advertised */ - - adone: - *err = acanc; - return (asum); - } + acanc *= 30.0; /* fudge factor, since error of asymptotic formula + * often seems this much larger than advertised */ + adone: + *err = acanc; + return (asum); } /* Power series summation for confluent hypergeometric function */ From 64aebfd8d71551e7b329e70ba106e123b208c277 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sat, 18 May 2024 22:09:53 +0200 Subject: [PATCH 203/500] CI: upgrade PyTorch version in array API job from 2.0 to 2.3 --- .github/workflows/array_api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 5e3875e6272c..1166702a5662 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -62,7 +62,7 @@ jobs: - name: Install PyTorch CPU run: | - python -m pip install "torch<2.1" --index-url https://download.pytorch.org/whl/cpu + python -m pip install "torch==2.3" --index-url https://download.pytorch.org/whl/cpu - name: Install JAX run: | From 3b36af3b910f52af1df37446ce385a3f6577c448 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sat, 18 May 2024 22:28:21 +0200 Subject: [PATCH 204/500] TYP: extend type annotations in `_lib/_array_api.py` Also resolve a couple of errors visible with MyPy locally. [skip circle] [skip cirrus] --- scipy/_lib/_array_api.py | 24 +++++++++++++++++------- scipy/optimize/_milp.py | 2 +- scipy/stats/_stats_py.py | 6 +++--- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index da5d696e0b6a..58885a759e64 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -441,7 +441,7 @@ def scipy_namespace_for(xp: ModuleType) -> ModuleType: if is_cupy(xp): - import cupyx # type: ignore[import-not-found] + import cupyx # type: ignore[import-not-found,import-untyped] return cupyx.scipy if is_jax(xp): @@ -454,7 +454,7 @@ def scipy_namespace_for(xp: ModuleType) -> ModuleType: # temporary substitute for xp.minimum, which is not yet in all backends # or covered by array_api_compat. -def xp_minimum(x1, x2): +def xp_minimum(x1: Array, x2: Array, /) -> Array: # xp won't be passed in because it doesn't need to be passed in to xp.minimum xp = array_namespace(x1, x2) if hasattr(xp, 'minimum'): @@ -469,9 +469,15 @@ def xp_minimum(x1, x2): # temporary substitute for xp.clip, which is not yet in all backends # or covered by array_api_compat. -def xp_clip(x, a, b, xp=None): +def xp_clip( + x: Array, + /, + min: int | float | Array | None = None, + max: int | float | Array | None = None, + *, + xp: ModuleType | None = None) -> Array: xp = array_namespace(x) if xp is None else xp - a, b = xp.asarray(a, dtype=x.dtype), xp.asarray(b, dtype=x.dtype) + a, b = xp.asarray(min, dtype=x.dtype), xp.asarray(max, dtype=x.dtype) if hasattr(xp, 'clip'): return xp.clip(x, a, b) x, a, b = xp.broadcast_arrays(x, a, b) @@ -485,7 +491,11 @@ def xp_clip(x, a, b, xp=None): # temporary substitute for xp.moveaxis, which is not yet in all backends # or covered by array_api_compat. -def xp_moveaxis_to_end(x, source, xp=None): +def xp_moveaxis_to_end( + x: Array, + source: int, + /, *, + xp: ModuleType | None = None) -> Array: xp = array_namespace(xp) if xp is None else xp axes = list(range(x.ndim)) temp = axes.pop(source) @@ -495,7 +505,7 @@ def xp_moveaxis_to_end(x, source, xp=None): # temporary substitute for xp.copysign, which is not yet in all backends # or covered by array_api_compat. -def xp_copysign(x1, x2, xp=None): +def xp_copysign(x1: Array, x2: Array, /, *, xp: ModuleType | None = None) -> Array: # no attempt to account for special cases xp = array_namespace(x1, x2) if xp is None else xp abs_x1 = xp.abs(x1) @@ -504,7 +514,7 @@ def xp_copysign(x1, x2, xp=None): # partial substitute for xp.sign, which does not cover the NaN special case # that I need. (https://github.com/data-apis/array-api-compat/issues/136) -def xp_sign(x, xp=None): +def xp_sign(x: Array, /, *, xp: ModuleType | None = None) -> Array: xp = array_namespace(x) if xp is None else xp if is_numpy(xp): # only NumPy implements the special cases correctly return xp.sign(x) diff --git a/scipy/optimize/_milp.py b/scipy/optimize/_milp.py index ce50bd237b6d..5ec771eba471 100644 --- a/scipy/optimize/_milp.py +++ b/scipy/optimize/_milp.py @@ -2,7 +2,7 @@ import numpy as np from scipy.sparse import csc_array, vstack, issparse from scipy._lib._util import VisibleDeprecationWarning -from ._highs._highs_wrapper import _highs_wrapper # type: ignore[import-not-found] +from ._highs._highs_wrapper import _highs_wrapper # type: ignore[import-not-found,import-untyped] from ._constraints import LinearConstraint, Bounds from ._optimize import OptimizeResult from ._linprog_highs import _highs_to_scipy_status_message diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 8afbebda4564..0ff6f476aa32 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -4842,8 +4842,8 @@ def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0): # `moveaxis` only recently added to array API, so it's not yey available in # array_api_strict. Replace with e.g. `xp.moveaxis(x, axis, -1)` when available. - x = xp_moveaxis_to_end(x, axis, xp) - y = xp_moveaxis_to_end(y, axis, xp) + x = xp_moveaxis_to_end(x, axis, xp=xp) + y = xp_moveaxis_to_end(y, axis, xp=xp) axis = -1 dtype = xp.result_type(x.dtype, y.dtype) @@ -4942,7 +4942,7 @@ def statistic(x, y, axis): one = xp.asarray(1, dtype=dtype) # `clip` only recently added to array API, so it's not yet available in # array_api_strict. Replace with e.g. `xp.clip(r, -one, one)` when available. - r = xp.asarray(xp_clip(r, -one, one, xp)) + r = xp.asarray(xp_clip(r, -one, one, xp=xp)) r[const_xy] = xp.nan # As explained in the docstring, the distribution of `r` under the null From c97172b499819e43afe8fe1467b29fdf77176b9c Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sat, 18 May 2024 23:08:27 +0200 Subject: [PATCH 205/500] TST: fix test failures on GPU for stats.jarque_bera It converts to/from numpy arrays internally, so is cpu-only. --- scipy/stats/tests/test_stats.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index b7faaabd9ef1..2c57e6a7efd8 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6367,8 +6367,10 @@ def test_input_validation(self): stats.ranksums(self.x, self.y, alternative='foobar') +@pytest.mark.usefixtures("skip_xp_backends") +@skip_xp_backends(cpu_only=True) +@array_api_compatible class TestJarqueBera: - @array_api_compatible def test_jarque_bera_against_R(self, xp): # library(tseries) # options(digits=16) @@ -6382,6 +6384,7 @@ def test_jarque_bera_against_R(self, xp): 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): # array-like only relevant for NumPy np.random.seed(987654321) @@ -6394,14 +6397,12 @@ def test_jarque_bera_array_like(self): 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 - @array_api_compatible def test_jarque_bera_size(self, xp): x = xp.asarray([]) message = "At least one observation is required." with pytest.raises(ValueError, match=message): stats.jarque_bera(x) - @array_api_compatible def test_axis(self, xp): rng = np.random.RandomState(seed=122398129) x = xp.asarray(rng.random(size=(2, 45))) From 217d66e35e8a0dcfedb440330090e3a8bdce5212 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sat, 18 May 2024 23:18:57 +0200 Subject: [PATCH 206/500] TST: optimize: fix test tolerance of `chandrupatla` for CuPy Some `test_vectorization` tolerances were in the 1e-10 range, so similar to those for PyTorch. --- scipy/optimize/tests/test_chandrupatla.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scipy/optimize/tests/test_chandrupatla.py b/scipy/optimize/tests/test_chandrupatla.py index 5fb836435dfd..1300c08784b5 100644 --- a/scipy/optimize/tests/test_chandrupatla.py +++ b/scipy/optimize/tests/test_chandrupatla.py @@ -6,8 +6,7 @@ import scipy._lib._elementwise_iterative_method as eim from scipy.conftest import array_api_compatible from scipy._lib._array_api import (array_namespace, xp_assert_close, xp_assert_equal, - xp_assert_less, xp_minimum, is_numpy, is_cupy, - is_torch) + xp_assert_less, xp_minimum, is_numpy, is_cupy) from scipy.optimize._chandrupatla import (_chandrupatla_minimize, _chandrupatla as _chandrupatla_root) @@ -567,9 +566,8 @@ def f(*args, **kwargs): assert xp.all((res.x[finite] == res.xl[finite]) | (res.x[finite] == res.xr[finite])) - # Torch and NumPy don't solve to precisely the same accuracy - # on all machines. That's OK. - atol = 1e-9 if is_torch(xp) else 1e-15 + # PyTorch and CuPy don't solve to the same accuracy as NumPy - that's OK. + atol = 1e-15 if is_numpy(xp) else 1e-9 ref_fl = [ref.fl for ref in refs] ref_fl = xp.reshape(xp.asarray(ref_fl, dtype=dtype), shape) From f3a0eb7ec21e004538ab2a0e05b3943a94e4faf4 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sun, 19 May 2024 14:15:11 +0200 Subject: [PATCH 207/500] TST: bump tolerance to address `test_axis_nan_policy` failures The `normaltest` tests need 4e-15 to pass in my x86-64 linux conda-forge environment; the `kurtosistest` tests need 6e-15. These tolerances don't need to be this tight and my setup is unlikely to be worst-case, so add a bit of margin on top. Failures: ``` E AssertionError: E Not equal to tolerance rtol=1e-15, atol=0 E E Mismatched elements: 3 / 6 (50%) E Max absolute difference: 7.99360578e-15 E Max relative difference: 3.95721074e-15 E x: array([[5.141364, 0.673337], E [4.039859, 2.172997], E [4.832018, 5.96047 ]]) E y: array([[5.141364, 0.673337], E [4.039859, 2.172997], E [4.832018, 5.96047 ]]) args = (.compare at 0x7ef45c1d6e80>, array([[5.14136359, 0.67333671], [4.03985923, 2...18, 5.96046984]]), array([[5.14136359, 0.67333671], [4.03985923, 2.17299682], [4.83201818, 5.96046984]])) func = kwds = {'equal_nan': True, 'err_msg': '', 'header': 'Not equal to tolerance rtol=1e-15, atol=0', 'verbose': True} self = ========================================================= short test summary info ========================================================== FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--3-propagate-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--3-propagate-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--3-omit-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--3-omit-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--3-raise-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--3-raise-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--2-propagate-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--2-propagate-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--2-omit-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--2-omit-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--2-raise-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--2-raise-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--1-propagate-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--1-propagate-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--1-omit-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--1-omit-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--1-raise-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite--1-raise-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-0-propagate-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-0-propagate-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-0-omit-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-0-omit-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-0-raise-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-0-raise-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-1-propagate-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-1-propagate-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-1-omit-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-1-omit-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-1-raise-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-1-raise-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-2-propagate-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-2-propagate-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-2-omit-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-2-omit-normaltest-args42-kwds42-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-2-raise-kurtosistest-args41-kwds41-1-2-False-None] - AssertionError: FAILED scipy/stats/tests/test_axis_nan_policy.py::test_axis_nan_policy_full[all_finite-2-raise-normaltest-args42-kwds42-1-2-False-None] - AssertionError: ========================================== 36 failed, 3096 passed, 54 skipped in 66.33s (0:01:06) ========================================== ``` [skip ci] --- scipy/stats/tests/test_axis_nan_policy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index ef415af5520b..69d8f01c44fc 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -385,11 +385,11 @@ def unpacker(res): "approximation.") res = unpacker(hypotest(*data, axis=axis, nan_policy=nan_policy, *args, **kwds)) - assert_allclose(res[0], statistics, rtol=1e-15) + assert_allclose(res[0], statistics, rtol=1e-14) assert_equal(res[0].dtype, statistics.dtype) if len(res) == 2: - assert_allclose(res[1], pvalues, rtol=1e-15) + assert_allclose(res[1], pvalues, rtol=1e-14) assert_equal(res[1].dtype, pvalues.dtype) From 76c725a200d7f94e47818c28be9f3cde7f1dfbcd Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sun, 19 May 2024 15:23:02 +0200 Subject: [PATCH 208/500] TST: skip highly parametrized nan-policy tests unless running with xslow >3,000 tests is not only slow to skip (see code comment) but results in 3000 `S` characters of test log output that is much nicer to avoid. [skip cirrus] [skip circle] --- scipy/stats/tests/test_axis_nan_policy.py | 33 ++++++++++++----------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index 69d8f01c44fc..d77e2f8a6815 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -255,22 +255,23 @@ def test_axis_nan_policy_fast(hypotest, args, kwds, n_samples, n_outputs, unpacker, nan_policy, axis, data_generator) -@pytest.mark.slow -@pytest.mark.filterwarnings('ignore::RuntimeWarning') -@pytest.mark.filterwarnings('ignore::UserWarning') -@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"), 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): - if hypotest in {stats.cramervonmises_2samp} 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. + @pytest.mark.filterwarnings('ignore::RuntimeWarning') + @pytest.mark.filterwarnings('ignore::UserWarning') + @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"), 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, From 712670697da54d0682d6bc11003d1f99a4727a97 Mon Sep 17 00:00:00 2001 From: Bharat Raghunathan Date: Sun, 19 May 2024 13:59:47 -0400 Subject: [PATCH 209/500] DOC: sparse.csgraph.dijkstra: add warning for `directed=False` behaviour (#20738) --------- Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/sparse/csgraph/_shortest_path.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scipy/sparse/csgraph/_shortest_path.pyx b/scipy/sparse/csgraph/_shortest_path.pyx index 40ab24e1c6e8..4712f9ff3844 100644 --- a/scipy/sparse/csgraph/_shortest_path.pyx +++ b/scipy/sparse/csgraph/_shortest_path.pyx @@ -437,6 +437,8 @@ def dijkstra(csgraph, directed=True, indices=None, If False, then find the shortest path on an undirected graph: the algorithm can progress from point i to j or j to i along either csgraph[i, j] or csgraph[j, i]. + + .. warning:: Refer the notes below while using with ``directed=False``. indices : array_like or int, optional if specified, only compute the paths from the points at the given indices. From b69113762d51f7b5b167f8e01ce4e8ebe5b00281 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 19 May 2024 20:40:50 -0400 Subject: [PATCH 210/500] ENH: stats.bartlett: add native axis and array-API support --- scipy/stats/_morestats.py | 44 ++++--- scipy/stats/tests/test_axis_nan_policy.py | 140 +++++++++++----------- 2 files changed, 98 insertions(+), 86 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index e8253bddce5f..35b810320db9 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -11,7 +11,8 @@ 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._array_api import array_namespace, xp_minimum, size as xp_size +from scipy._lib._array_api import (array_namespace, xp_minimum, size as xp_size, + xp_moveaxis_to_end) from ._ansari_swilk_statistics import gscale, swilk from . import _stats_py, _wilcoxon @@ -20,7 +21,7 @@ from .contingency import chi2_contingency from . import distributions from ._distn_infrastructure import rv_generic -from ._axis_nan_policy import _axis_nan_policy_factory +from ._axis_nan_policy import _axis_nan_policy_factory, _broadcast_arrays __all__ = ['mvsdist', @@ -2841,7 +2842,7 @@ def ansari(x, y, alternative='two-sided'): @_axis_nan_policy_factory(BartlettResult, n_samples=None) -def bartlett(*samples): +def bartlett(*samples, axis=0): r"""Perform Bartlett's test for equal variances. Bartlett's test tests the null hypothesis that all input samples @@ -3051,30 +3052,41 @@ def bartlett(*samples): [0.007054444444444413, 0.13073888888888888, 0.008890000000000002] """ + xp = array_namespace(*samples) + k = len(samples) if k < 2: raise ValueError("Must enter at least two input sample vectors.") - # Handle empty input and input that is not 1d + # Handle 1d empty input; _axis_nan_policy takes care of N-D for sample in samples: if np.asanyarray(sample).size == 0: NaN = _get_nan(*samples) # get NaN of result_dtype of all samples return BartlettResult(NaN, NaN) - Ni = np.empty(k) - ssq = np.empty(k, 'd') - for j in range(k): - Ni[j] = len(samples[j]) - ssq[j] = np.var(samples[j], ddof=1) - Ntot = np.sum(Ni, axis=0) - spsq = np.sum((Ni - 1)*ssq, axis=0) / (1.0*(Ntot - k)) - numer = (Ntot*1.0 - k) * log(spsq) - np.sum((Ni - 1.0)*log(ssq), axis=0) - denom = 1.0 + 1.0/(3*(k - 1)) * ((np.sum(1.0/(Ni - 1.0), axis=0)) - - 1.0/(Ntot - k)) + samples = _broadcast_arrays(samples, axis=axis, xp=xp) + samples = [xp_moveaxis_to_end(sample, source=axis, xp=xp) for sample in samples] + + Ni = [xp.asarray(sample.shape[-1]) for sample in samples] + Ni = [xp.broadcast_to(N, sample.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) + ssq = xp.concat(ssq, axis=0) + Ntot = xp.sum(Ni, axis=0) + spsq = xp.sum((Ni - 1)*ssq, axis=0) / (Ntot - k) + numer = (Ntot - k) * xp.log(spsq) - xp.sum((Ni - 1)*xp.log(ssq), axis=0) + denom = 1 + 1/(3*(k - 1)) * ((xp.sum(1/(Ni - 1), axis=0)) - 1/(Ntot - k)) T = numer / denom - pval = distributions.chi2.sf(T, k - 1) # 1 - cdf - return BartlettResult(T, pval) + T_np = np.asarray(T) + pvalue = distributions.chi2.sf(T_np, k-1) + pvalue = xp.asarray(pvalue, dtype=T.dtype) + 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')) diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index ef415af5520b..552f2ca8a459 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -40,77 +40,77 @@ def ttest_ci(*args, **kwargs): # 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, 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(), {'mode': 'approx'}, 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.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(), 2, 2, False, None), - (stats.mood, tuple(), {}, 2, 2, False, None), - (stats.shapiro, tuple(), {}, 1, 2, False, None), - (stats.ks_1samp, (norm().cdf,), dict(), 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, (norm().cdf,), 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, ("norm",), dict(), 1, 2, False, - lambda res: (res.statistic, res.pvalue)), - (stats.cramervonmises_2samp, tuple(), dict(), 2, 2, False, - lambda res: (res.statistic, res.pvalue)), - (stats.epps_singleton_2samp, tuple(), dict(), 2, 2, False, None), + # (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, 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(), {'mode': 'approx'}, 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.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(), 2, 2, False, None), + # (stats.mood, tuple(), {}, 2, 2, False, None), + # (stats.shapiro, tuple(), {}, 1, 2, False, None), + # (stats.ks_1samp, (norm().cdf,), dict(), 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, (norm().cdf,), 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, ("norm",), dict(), 1, 2, False, + # lambda res: (res.statistic, res.pvalue)), + # (stats.cramervonmises_2samp, tuple(), dict(), 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.alexandergovern, tuple(), {}, 2, 2, False, - lambda res: (res.statistic, res.pvalue)), - (stats.combine_pvalues, tuple(), {}, 1, 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.alexandergovern, tuple(), {}, 2, 2, False, + # lambda res: (res.statistic, res.pvalue)), + # (stats.combine_pvalues, tuple(), {}, 1, 2, False, None), ] # If the message is one of those expected, put nans in From 8e60e76ed55c29cd7f9019b672bedb8e3f840ba1 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 19 May 2024 20:56:14 -0400 Subject: [PATCH 211/500] TST: stats.bartlett: add array API support --- scipy/stats/_morestats.py | 2 +- scipy/stats/tests/test_axis_nan_policy.py | 140 +++++++++++----------- scipy/stats/tests/test_morestats.py | 32 ++--- 3 files changed, 88 insertions(+), 86 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 35b810320db9..aca77c181b8f 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -3067,7 +3067,7 @@ def bartlett(*samples, axis=0): samples = _broadcast_arrays(samples, axis=axis, xp=xp) samples = [xp_moveaxis_to_end(sample, source=axis, xp=xp) for sample in samples] - Ni = [xp.asarray(sample.shape[-1]) for sample in samples] + Ni = [xp.asarray(sample.shape[-1], dtype=sample.dtype) for sample in samples] Ni = [xp.broadcast_to(N, sample.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] diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index 552f2ca8a459..ef415af5520b 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -40,77 +40,77 @@ def ttest_ci(*args, **kwargs): # 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, 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(), {'mode': 'approx'}, 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.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(), 2, 2, False, None), - # (stats.mood, tuple(), {}, 2, 2, False, None), - # (stats.shapiro, tuple(), {}, 1, 2, False, None), - # (stats.ks_1samp, (norm().cdf,), dict(), 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, (norm().cdf,), 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, ("norm",), dict(), 1, 2, False, - # lambda res: (res.statistic, res.pvalue)), - # (stats.cramervonmises_2samp, tuple(), dict(), 2, 2, False, - # lambda res: (res.statistic, res.pvalue)), - # (stats.epps_singleton_2samp, tuple(), dict(), 2, 2, False, None), + (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, 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(), {'mode': 'approx'}, 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.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(), 2, 2, False, None), + (stats.mood, tuple(), {}, 2, 2, False, None), + (stats.shapiro, tuple(), {}, 1, 2, False, None), + (stats.ks_1samp, (norm().cdf,), dict(), 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, (norm().cdf,), 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, ("norm",), dict(), 1, 2, False, + lambda res: (res.statistic, res.pvalue)), + (stats.cramervonmises_2samp, tuple(), dict(), 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.alexandergovern, tuple(), {}, 2, 2, False, - # lambda res: (res.statistic, res.pvalue)), - # (stats.combine_pvalues, tuple(), {}, 1, 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.alexandergovern, tuple(), {}, 2, 2, False, + lambda res: (res.statistic, res.pvalue)), + (stats.combine_pvalues, tuple(), {}, 1, 2, False, None), ] # If the message is one of those expected, put nans in diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index af33aee9eb49..4ba64039ac05 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -735,33 +735,35 @@ def test_alternative_approx(self): assert_allclose(pval_l, 1-pval/2, atol=1e-12) +@array_api_compatible class TestBartlett: - - def test_data(self): + 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) - assert_almost_equal(T, 20.78587342806484, 7) - assert_almost_equal(pval, 0.0136358632781, 7) + xp_assert_close(T, xp.asarray(20.78587342806484)) + xp_assert_close(pval, xp.asarray(0.0136358632781)) - def test_bad_arg(self): - # Too few args raises ValueError. - assert_raises(ValueError, stats.bartlett, [1]) + 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): + 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) - def test_empty_arg(self): + def test_empty_arg(self, xp): args = (g1, g2, g3, g4, g5, g6, g7, g8, g9, g10, []) - assert_equal((np.nan, np.nan), stats.bartlett(*args)) - - # temporary fix for issue #9252: only accept 1d input - def test_1d_input(self): - x = np.array([[1, 2], [3, 4]]) - assert_raises(ValueError, stats.bartlett, g1, x) + args = [xp.asarray(arg) for arg in args] + res = stats.bartlett(*args) + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) class TestLevene: From beae1610bfd8fff75ea26b3668fcb258aaec07ea Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 19 May 2024 21:59:15 -0400 Subject: [PATCH 212/500] ENH: stats.power_divergence: add array API support --- scipy/special/__init__.py | 4 +- .../special/_support_alternative_backends.py | 14 +++- scipy/stats/_stats_py.py | 68 ++++++++++++------- 3 files changed, 58 insertions(+), 28 deletions(-) diff --git a/scipy/special/__init__.py b/scipy/special/__init__.py index 8f9e7e4c29ac..f6d854c86db6 100644 --- a/scipy/special/__init__.py +++ b/scipy/special/__init__.py @@ -809,8 +809,8 @@ def _load_libsf_error_state(): # Replace some function definitions from _ufuncs to add Array API support from ._support_alternative_backends import ( - log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, - gammaln, gammainc, gammaincc, logit, expit, entr, rel_entr) + log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, + gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy) from . import _basic from ._basic import * diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 1e688a83824d..3feeea13b6dc 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -11,8 +11,8 @@ # These don't really need to be imported, but otherwise IDEs might not realize # that these are defined in this file / report an error in __init__.py from ._ufuncs import ( - log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, # noqa: F401 - gammaln, gammainc, gammaincc, logit, expit, entr, rel_entr) # noqa: F401 + log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, # noqa: F401 + gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy) # noqa: F401 _SCIPY_ARRAY_API = os.environ.get("SCIPY_ARRAY_API", False) array_api_compat_prefix = "scipy._lib.array_api_compat" @@ -70,7 +70,14 @@ def _rel_entr(x, y, *, xp): return res -_generic_implementations = {'rel_entr': _rel_entr} +def _xlogy(x, y, *, xp): + with np.errstate(divide='ignore', invalid='ignore'): + temp = x * xp.log(y) + return xp.where(x == 0., xp.asarray(0., dtype=temp.dtype), temp) + + +_generic_implementations = {'rel_entr': _rel_entr, + 'xlogy': _xlogy} # functools.wraps doesn't work because: @@ -104,6 +111,7 @@ def wrapped(*args, **kwargs): 'expit': 1, 'entr': 1, 'rel_entr': 2, + 'xlogy': 2, } for f_name, n_array_args in array_special_func_map.items(): diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 8afbebda4564..edab6b15221a 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -61,14 +61,16 @@ monte_carlo_test, permutation_test, bootstrap, _batch_generator) from ._axis_nan_policy import (_axis_nan_policy_factory, - _broadcast_concatenate) + _broadcast_concatenate, + _broadcast_shapes) from ._binomtest import _binary_search_for_binom_tst as _binary_search from scipy._lib._bunch import _make_tuple_bunch from scipy import stats from scipy.optimize import root_scalar from scipy._lib._util import normalize_axis_index from scipy._lib._array_api import (array_namespace, is_numpy, atleast_nd, - xp_clip, xp_moveaxis_to_end, xp_sign) + xp_clip, xp_moveaxis_to_end, xp_sign, + xp_minimum) from scipy._lib.array_api_compat import size as xp_size @@ -7899,7 +7901,7 @@ def ttest_rel(a, b, axis=0, nan_policy='propagate', alternative="two-sided"): } -def _count(a, axis=None): +def _m_count(a, *, axis, xp): """Count the number of non-masked elements of an array. This function behaves like `np.ma.count`, but is much faster @@ -7913,17 +7915,29 @@ def _count(a, axis=None): num = int(num) else: if axis is None: - num = a.size + num = xp_size(a) else: num = a.shape[axis] return num -def _m_broadcast_to(a, shape): +def _m_broadcast_to(a, shape, *, xp): if np.ma.isMaskedArray(a): return np.ma.masked_array(np.broadcast_to(a, shape), mask=np.broadcast_to(a.mask, shape)) - return np.broadcast_to(a, shape, subok=True) + return xp.broadcast_to(a, shape) + + +def _m_sum(a, *, axis, xp): + if np.ma.isMaskedArray(a): + return np.asarray(a.sum(axis)) + return xp.sum(a, axis=axis) + + +def _m_mean(a, *, axis, keepdims, xp): + if np.ma.isMaskedArray(a): + return np.asarray(a.mean(axis=axis, keepdims=keepdims)) + return xp.mean(a, axis=axis, keepdims=keepdims) Power_divergenceResult = namedtuple('Power_divergenceResult', @@ -8093,6 +8107,8 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): (array([ 3.5 , 9.25]), array([ 0.62338763, 0.09949846])) """ + xp = array_namespace(f_obs) + # Convert the input argument `lambda_` to a numerical value. if isinstance(lambda_, str): if lambda_ not in _power_div_lambda_names: @@ -8103,21 +8119,22 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): elif lambda_ is None: lambda_ = 1 - f_obs = np.asanyarray(f_obs) - f_obs_float = f_obs.astype(np.float64) + f_obs = f_obs if np.ma.isMaskedArray(f_obs) else xp.asarray(f_obs) + f_obs_float = (f_obs.astype(np.float64) if hasattr(f_obs, 'mask') + else xp.asarray(f_obs, dtype=xp.float64)) if f_exp is not None: - f_exp = np.asanyarray(f_exp) - bshape = np.broadcast_shapes(f_obs_float.shape, f_exp.shape) - f_obs_float = _m_broadcast_to(f_obs_float, bshape) - f_exp = _m_broadcast_to(f_exp, bshape) + f_exp = f_exp if np.ma.isMaskedArray(f_obs) else xp.asarray(f_exp) + bshape = _broadcast_shapes((f_obs_float.shape, f_exp.shape)) + f_obs_float = _m_broadcast_to(f_obs_float, bshape, xp=xp) + f_exp = _m_broadcast_to(f_exp, bshape, xp=xp) rtol = 1e-8 # to pass existing tests with np.errstate(invalid='ignore'): - f_obs_sum = f_obs_float.sum(axis=axis) - f_exp_sum = f_exp.sum(axis=axis) - relative_diff = (np.abs(f_obs_sum - f_exp_sum) / - np.minimum(f_obs_sum, f_exp_sum)) - diff_gt_tol = (relative_diff > rtol).any() + f_obs_sum = _m_sum(f_obs_float, axis=axis, xp=xp) + f_exp_sum = _m_sum(f_exp, axis=axis, xp=xp) + 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=None) if diff_gt_tol: msg = (f"For each axis slice, the sum of the observed " f"frequencies must agree with the sum of the " @@ -8130,7 +8147,7 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): # Ignore 'invalid' errors so the edge case of a data set with length 0 # is handled without spurious warnings. with np.errstate(invalid='ignore'): - f_exp = f_obs.mean(axis=axis, keepdims=True) + f_exp = _m_mean(f_obs, axis=axis, keepdims=True, xp=xp) # `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 @@ -8149,13 +8166,18 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): terms = f_obs * ((f_obs / f_exp)**lambda_ - 1) terms /= 0.5 * lambda_ * (lambda_ + 1) - stat = terms.sum(axis=axis) + stat = _m_sum(terms, axis=axis, xp=xp) - num_obs = _count(terms, axis=axis) - ddof = asarray(ddof) - p = distributions.chi2.sf(stat, num_obs - 1 - ddof) + num_obs = _m_count(terms, axis=axis, xp=xp) + ddof = xp.asarray(ddof) + + stat_np = np.asarray(stat) + pvalue = distributions.chi2.sf(stat_np, num_obs - 1 - ddof) + pvalue = xp.asarray(pvalue, dtype=stat.dtype) + stat = stat[()] if stat.ndim == 0 else stat + pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue - return Power_divergenceResult(stat, p) + return Power_divergenceResult(stat, pvalue) def chisquare(f_obs, f_exp=None, ddof=0, axis=0): From ee43331c7400b49b57c64e4204c842f71bcc7f86 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Mon, 20 May 2024 12:04:03 +1000 Subject: [PATCH 213/500] ENH: some micro-optimisations --- scipy/optimize/_differentialevolution.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index 2510d7190cde..0dd41bf05745 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -963,6 +963,9 @@ def maplike_for_vectorized_func(func, x): self.constraint_violation = np.zeros((self.num_population_members, 1)) self.feasible = np.ones(self.num_population_members, bool) + # an array to shuffle when selecting candidates. Create it here + # rather than repeatedly creating it in _select_samples. + self._random_population_index = np.arange(self.num_population_members) self.disp = disp def init_population_lhs(self): @@ -1658,8 +1661,9 @@ def _unscale_parameters(self, parameters): def _ensure_constraint(self, trial): """Make sure the parameters lie between the limits.""" - mask = np.where((trial > 1) | (trial < 0)) - trial[mask] = self.random_number_generator.uniform(size=mask[0].shape) + mask = np.bitwise_or(trial > 1, trial < 0) + if oob := np.count_nonzero(mask): + trial[mask] = self.random_number_generator.uniform(size=oob) def _mutate(self, candidate): """Create a trial vector based on a mutation strategy.""" @@ -1762,17 +1766,9 @@ def _select_samples(self, candidate, number_samples): obtain random integers from range(self.num_population_members), without replacement. You can't have the original candidate either. """ - pool = np.arange(self.num_population_members) - self.random_number_generator.shuffle(pool) - - idxs = [] - while len(idxs) < number_samples and len(pool) > 0: - idx = pool[0] - pool = pool[1:] - if idx != candidate: - idxs.append(idx) - - return idxs + self.random_number_generator.shuffle(self._random_population_index) + idxs = self._random_population_index[:number_samples + 1] + return idxs[idxs != candidate][:number_samples] class _ConstraintWrapper: From e402ba8e8a41b033e5a3c2f61c690d8ad2349c37 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 19 May 2024 23:23:49 -0400 Subject: [PATCH 214/500] TST: stats.power_divergence: add array API support --- scipy/stats/_stats_py.py | 2 +- scipy/stats/tests/test_stats.py | 176 +++++++++++++++----------------- 2 files changed, 83 insertions(+), 95 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index edab6b15221a..7378876936f4 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -8154,7 +8154,7 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): # cases of lambda_. if lambda_ == 1: # Pearson's chi-squared statistic - terms = (f_obs_float - f_exp)**2 / f_exp + 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) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index b7faaabd9ef1..825c72c7a24e 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -40,7 +40,8 @@ from scipy._lib._util import AxisError from scipy.conftest import array_api_compatible, skip_xp_invalid_arg from scipy._lib._array_api import (xp_assert_close, xp_assert_equal, array_namespace, - copy, is_numpy, is_torch, SCIPY_ARRAY_API) + copy, is_numpy, is_torch, SCIPY_ARRAY_API, + size as xp_size) skip_xp_backends = pytest.mark.skip_xp_backends @@ -4008,82 +4009,52 @@ def test_nd(self, shape): class TestPowerDivergence: def check_power_divergence(self, f_obs, f_exp, ddof, axis, lambda_, - expected_stat): - f_obs = np.asarray(f_obs) + 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 = f_obs.size + num_obs = xp_size(f_obs) else: - b = np.broadcast(f_obs, f_exp) - num_obs = b.shape[axis] + xp_test = array_namespace(f_obs) # torch needs broadcast_arrays + arrays = (xp_test.broadcast_arrays(f_obs, f_exp) if f_exp is not None + else (f_obs,)) + num_obs = arrays[0].shape[axis] with suppress_warnings() as sup: sup.filter(RuntimeWarning, "Mean of empty slice") stat, p = stats.power_divergence( f_obs=f_obs, f_exp=f_exp, ddof=ddof, axis=axis, lambda_=lambda_) - assert_allclose(stat, expected_stat) + 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_obs, f_exp=f_exp, ddof=ddof, axis=axis) - assert_allclose(stat, expected_stat) + 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) - assert_allclose(p, expected_p) - - def test_basic(self): - for case in power_div_1d_cases: - self.check_power_divergence( - case.f_obs, case.f_exp, case.ddof, case.axis, - None, case.chi2) - self.check_power_divergence( - case.f_obs, case.f_exp, case.ddof, case.axis, - "pearson", case.chi2) - self.check_power_divergence( - case.f_obs, case.f_exp, case.ddof, case.axis, - 1, case.chi2) - self.check_power_divergence( - case.f_obs, case.f_exp, case.ddof, case.axis, - "log-likelihood", case.log) - self.check_power_divergence( - case.f_obs, case.f_exp, case.ddof, case.axis, - "mod-log-likelihood", case.mod_log) - self.check_power_divergence( - case.f_obs, case.f_exp, case.ddof, case.axis, - "cressie-read", case.cr) - self.check_power_divergence( - case.f_obs, case.f_exp, case.ddof, case.axis, - 2/3, case.cr) + xp_assert_close(p, xp.asarray(expected_p, dtype=dtype)) - def test_basic_masked(self): - for case in power_div_1d_cases: - mobs = np.ma.array(case.f_obs) - self.check_power_divergence( - mobs, case.f_exp, case.ddof, case.axis, - None, case.chi2) - self.check_power_divergence( - mobs, case.f_exp, case.ddof, case.axis, - "pearson", case.chi2) - self.check_power_divergence( - mobs, case.f_exp, case.ddof, case.axis, - 1, case.chi2) - self.check_power_divergence( - mobs, case.f_exp, case.ddof, case.axis, - "log-likelihood", case.log) - self.check_power_divergence( - mobs, case.f_exp, case.ddof, case.axis, - "mod-log-likelihood", case.mod_log) - self.check_power_divergence( - mobs, case.f_exp, case.ddof, case.axis, - "cressie-read", case.cr) - self.check_power_divergence( - mobs, case.f_exp, case.ddof, case.axis, - 2/3, case.cr) + @array_api_compatible + @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): + @array_api_compatible + 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)) @@ -4091,25 +4062,29 @@ def test_axis(self): 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]) + "pearson", [case0.chi2, case1.chi2], xp=xp) self.check_power_divergence( f_obs, f_exp, 0, 1, - "log-likelihood", [case0.log, case1.log]) + "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]) + "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]) + "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( - np.array(case0.f_obs).reshape(2, 2), None, 0, None, - "pearson", case0.chi2) + f_obs_reshape, None, 0, None, + "pearson", case0.chi2, xp=xp) - def test_ddof_broadcasting(self): + @array_api_compatible + 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 @@ -4124,57 +4099,70 @@ def test_ddof_broadcasting(self): 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 = np.array([[0], [1]]) + ddof = xp.asarray([[0], [1]]) stat, p = stats.power_divergence(f_obs, f_exp, ddof=ddof) - assert_allclose(stat, expected_chi2) + xp_assert_close(stat, expected_chi2) # Compute the p values separately, passing in scalars for ddof. - stat0, p0 = stats.power_divergence(f_obs, f_exp, ddof=ddof[0,0]) - stat1, p1 = stats.power_divergence(f_obs, f_exp, ddof=ddof[1,0]) + stat0, p0 = stats.power_divergence(f_obs, f_exp, ddof=ddof[0, 0]) + stat1, p1 = stats.power_divergence(f_obs, f_exp, ddof=ddof[1, 0]) - assert_array_equal(p, np.vstack((p0, p1))) + xp_test = array_namespace(f_obs) # needs `concat`, `newaxis` + expected_p = xp_test.concat((p0[xp_test.newaxis, :], + p1[xp_test.newaxis, :]), + axis=0) + xp_assert_close(p, expected_p) - def test_empty_cases(self): + @array_api_compatible + @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(): - for case in power_div_empty_cases: - self.check_power_divergence( - case.f_obs, case.f_exp, case.ddof, case.axis, - "pearson", case.chi2) - self.check_power_divergence( - case.f_obs, case.f_exp, case.ddof, case.axis, - "log-likelihood", case.log) - self.check_power_divergence( - case.f_obs, case.f_exp, case.ddof, case.axis, - "mod-log-likelihood", case.mod_log) - self.check_power_divergence( - case.f_obs, case.f_exp, case.ddof, case.axis, - "cressie-read", case.cr) - - def test_power_divergence_result_attributes(self): + self.check_power_divergence( + case.f_obs, case.f_exp, case.ddof, case.axis, + lambda_, expected_stat, xp) + + @array_api_compatible + 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_obs, f_exp=f_exp, ddof=ddof, axis=axis, lambda_="pearson") attributes = ('statistic', 'pvalue') check_named_results(res, attributes) - def test_power_divergence_gh_12282(self): + @array_api_compatible + def test_power_divergence_gh_12282(self, xp): # The sums of observed and expected frequencies must match - f_obs = np.array([[10, 20], [30, 20]]) - f_exp = np.array([[5, 15], [35, 25]]) - with assert_raises(ValueError, match='For each axis slice...'): - stats.power_divergence(f_obs=[10, 20], f_exp=[30, 60]) - with assert_raises(ValueError, match='For each axis slice...'): + f_obs = xp.asarray([[10., 20.], [30., 20.]]) + f_exp = xp.asarray([[5., 15.], [35., 25.]]) + message = 'For each axis slice...' + with pytest.raises(ValueError, match=message): + stats.power_divergence(f_obs=f_obs, f_exp=xp.asarray([30., 60.])) + with pytest.raises(ValueError, match=message): stats.power_divergence(f_obs=f_obs, f_exp=f_exp, axis=1) stat, pval = stats.power_divergence(f_obs=f_obs, f_exp=f_exp) - assert_allclose(stat, [5.71428571, 2.66666667]) - assert_allclose(pval, [0.01682741, 0.10247043]) + xp_assert_close(stat, xp.asarray([5.71428571, 2.66666667])) + xp_assert_close(pval, xp.asarray([0.01682741, 0.10247043])) def test_gh_chisquare_12282(): From cde4bf9a44ead43be318528450862ba0d43566d0 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Mon, 20 May 2024 14:13:54 +1000 Subject: [PATCH 215/500] TST: fix tests caused by internal changes in differential_evolution --- .../tests/test__differential_evolution.py | 32 +++++-------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/scipy/optimize/tests/test__differential_evolution.py b/scipy/optimize/tests/test__differential_evolution.py index 02b08f197e46..9b51c80ed810 100644 --- a/scipy/optimize/tests/test__differential_evolution.py +++ b/scipy/optimize/tests/test__differential_evolution.py @@ -1611,11 +1611,11 @@ def func(x): # changed. The essential part of the test is that there is a number # after the '=', so if necessary, the text could be reduced to, say, # "MAXCV = 0.". - assert "MAXCV = 0.414" in result.message + assert "MAXCV = 0.4" in result.message def test_strategy_fn(self): # examines ability to customize strategy by mimicking one of the - # in-built strategies and comparing to the actual in-built strategy. + # in-built strategies parameter_count = 4 popsize = 10 bounds = [(0, 10.)] * parameter_count @@ -1623,19 +1623,16 @@ def test_strategy_fn(self): mutation = 0.8 recombination = 0.7 + calls = [0] def custom_strategy_fn(candidate, population, rng=None): + calls[0] += 1 trial = np.copy(population[candidate]) fill_point = rng.choice(parameter_count) pool = np.arange(total_popsize) rng.shuffle(pool) - - idxs = [] - while len(idxs) < 2 and len(pool) > 0: - idx = pool[0] - pool = pool[1:] - if idx != candidate: - idxs.append(idx) + idxs = pool[:2 + 1] + idxs = idxs[idxs != candidate][:2] r0, r1 = idxs[:2] @@ -1660,21 +1657,8 @@ def custom_strategy_fn(candidate, population, rng=None): polish=False ) assert solver.strategy is custom_strategy_fn - res = solver.solve() - - res2 = differential_evolution( - rosen, - bounds, - mutation=mutation, - popsize=popsize, - recombination=recombination, - maxiter=2, - strategy='best1bin', - polish=False, - seed=10 - ) - assert_allclose(res.population, res2.population) - assert_allclose(res.x, res2.x) + solver.solve() + assert calls[0] > 0 def custom_strategy_fn(candidate, population, rng=None): return np.array([1.0, 2.0]) From 86e2fbe7a6f6d7232dad40931d5d3ab6b374d1ec Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 19 May 2024 21:42:29 -0700 Subject: [PATCH 216/500] MAINT: stats.bartlett: fixup --- scipy/stats/_morestats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index aca77c181b8f..be797e3a6c0a 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -3060,8 +3060,8 @@ def bartlett(*samples, axis=0): # Handle 1d empty input; _axis_nan_policy takes care of N-D for sample in samples: - if np.asanyarray(sample).size == 0: - NaN = _get_nan(*samples) # get NaN of result_dtype of all samples + if xp.asarray(sample).size == 0: + NaN = _get_nan(*samples, xp=xp) # get NaN of result_dtype of all samples return BartlettResult(NaN, NaN) samples = _broadcast_arrays(samples, axis=axis, xp=xp) From f8f555693fb36affa357df31ad7abd0cc0a98564 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 19 May 2024 22:45:57 -0700 Subject: [PATCH 217/500] Update scipy/stats/_morestats.py --- scipy/stats/_morestats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index be797e3a6c0a..b1abae754614 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -3065,7 +3065,7 @@ def bartlett(*samples, axis=0): return BartlettResult(NaN, NaN) samples = _broadcast_arrays(samples, axis=axis, xp=xp) - samples = [xp_moveaxis_to_end(sample, source=axis, xp=xp) for sample in samples] + samples = [xp_moveaxis_to_end(sample, axis, xp=xp) for sample in samples] Ni = [xp.asarray(sample.shape[-1], dtype=sample.dtype) for sample in samples] Ni = [xp.broadcast_to(N, sample.shape[:-1]) for N in Ni] From 1549364612a8ce28b5634da98559eb3af0b7716f Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Mon, 20 May 2024 16:24:56 +1000 Subject: [PATCH 218/500] ENH: differential_evolution small micro optimisation --- scipy/optimize/_differentialevolution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index 0dd41bf05745..4d81df53971d 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -1650,7 +1650,7 @@ def _scale_parameters(self, trial): # trial either has shape (N, ) or (L, N), where L is the number of # solutions being scaled scaled = self.__scale_arg1 + (trial - 0.5) * self.__scale_arg2 - if np.any(self.integrality): + if np.count_nonzero(self.integrality): i = np.broadcast_to(self.integrality, scaled.shape) scaled[i] = np.round(scaled[i]) return scaled From 51c0e3fff102855e45649b74a40019039ce7e807 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 20 May 2024 00:00:49 -0700 Subject: [PATCH 219/500] Update scipy/stats/_morestats.py --- scipy/stats/_morestats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index b1abae754614..71bb78f0f11e 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -3060,7 +3060,7 @@ def bartlett(*samples, axis=0): # Handle 1d empty input; _axis_nan_policy takes care of N-D for sample in samples: - if xp.asarray(sample).size == 0: + if xp_size(xp.asarray(sample)) == 0: NaN = _get_nan(*samples, xp=xp) # get NaN of result_dtype of all samples return BartlettResult(NaN, NaN) From d248b50fb66faf466255f12cd42237ee5f1a7c20 Mon Sep 17 00:00:00 2001 From: Daniel Schmitz <40656107+dschmitz89@users.noreply.github.com> Date: Mon, 20 May 2024 12:09:30 +0200 Subject: [PATCH 220/500] BUG: stats: Fix `zipf.pmf` and `zipfian.pmf` for int32 `k` (#20702) * BUG: fix zipf.pmf for integer k * ENH: increase range for zipfian.pmf for integer k --------- Co-authored-by: Tyler Reddy --- scipy/stats/_discrete_distns.py | 6 ++++-- scipy/stats/tests/test_discrete_distns.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/scipy/stats/_discrete_distns.py b/scipy/stats/_discrete_distns.py index cda7c4e11a5e..b0216eaeeeaf 100644 --- a/scipy/stats/_discrete_distns.py +++ b/scipy/stats/_discrete_distns.py @@ -1313,8 +1313,9 @@ 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 + Pk = 1.0 / special.zeta(a, 1) * k**-a return Pk def _munp(self, n, a): @@ -1412,7 +1413,8 @@ def _get_support(self, a, n): return 1, n def _pmf(self, k, a, n): - return 1.0 / _gen_harmonic(n, a) / k**a + k = k.astype(np.float64) + return 1.0 / _gen_harmonic(n, a) * k**-a def _cdf(self, k, a, n): return _gen_harmonic(k, a) / _gen_harmonic(n, a) diff --git a/scipy/stats/tests/test_discrete_distns.py b/scipy/stats/tests/test_discrete_distns.py index c5993ebde375..3741cf79c96d 100644 --- a/scipy/stats/tests/test_discrete_distns.py +++ b/scipy/stats/tests/test_discrete_distns.py @@ -351,6 +351,14 @@ def pzip(k, a, n): 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) + class TestNCH: np.random.seed(2) # seeds 0 and 1 had some xl = xu; randint failed @@ -627,3 +635,14 @@ def test_betanbinom_kurtosis(self, n, a, b, ref): # 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) \ No newline at end of file From fa424f590156d50589b34e6015013c488ae5ba35 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 20 May 2024 10:40:10 -0700 Subject: [PATCH 221/500] TST: stats.chisquare: add array API support --- scipy/stats/_stats_py.py | 8 ++ scipy/stats/tests/test_stats.py | 142 ++++++++++++++++---------------- 2 files changed, 80 insertions(+), 70 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 7378876936f4..b1aac8a7727c 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -8108,6 +8108,7 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): """ xp = array_namespace(f_obs) + default_float = xp.asarray(1.).dtype # Convert the input argument `lambda_` to a numerical value. if isinstance(lambda_, str): @@ -8120,11 +8121,18 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): lambda_ = 1 f_obs = f_obs if np.ma.isMaskedArray(f_obs) else xp.asarray(f_obs) + dtype = default_float if xp.isdtype(f_obs.dtype, 'integral') else f_obs.dtype + f_obs = (f_obs.astype(dtype) if np.ma.isMaskedArray(f_obs) + else xp.asarray(f_obs, dtype=dtype)) f_obs_float = (f_obs.astype(np.float64) if hasattr(f_obs, 'mask') else xp.asarray(f_obs, dtype=xp.float64)) if f_exp is not None: f_exp = f_exp if np.ma.isMaskedArray(f_obs) else xp.asarray(f_exp) + dtype = default_float if xp.isdtype(f_exp.dtype, 'integral') else f_exp.dtype + f_exp = (f_exp.astype(dtype) if np.ma.isMaskedArray(f_exp) + else xp.asarray(f_exp, dtype=dtype)) + bshape = _broadcast_shapes((f_obs_float.shape, f_exp.shape)) f_obs_float = _m_broadcast_to(f_obs_float, bshape, xp=xp) f_exp = _m_broadcast_to(f_exp, bshape, xp=xp) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 825c72c7a24e..862c79621cd0 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -4164,26 +4164,73 @@ def test_power_divergence_gh_12282(self, xp): xp_assert_close(stat, xp.asarray([5.71428571, 2.66666667])) xp_assert_close(pval, xp.asarray([0.01682741, 0.10247043])) - -def test_gh_chisquare_12282(): - # Currently `chisquare` is implemented via power_divergence - # in case that ever changes, perform a basic test like - # test_power_divergence_gh_12282 - with assert_raises(ValueError, match='For each axis slice...'): - stats.chisquare(f_obs=[10, 20], f_exp=[30, 60]) - - -@pytest.mark.parametrize("n, dtype", [(200, np.uint8), (1000000, np.int32)]) -def test_chiquare_data_types_attributes(n, dtype): - # Regression test for gh-10159 and gh-18368 - obs = np.array([n, 0], dtype=dtype) - exp = np.array([n // 2, n // 2], dtype=dtype) - res = stats.chisquare(obs, exp) - stat, p = res - assert_allclose(stat, n, rtol=1e-13) - # check that attributes are identical to unpacked outputs - see gh-18368 - assert_equal(res.statistic, stat) - assert_equal(res.pvalue, p) +@array_api_compatible +class TestChisquare: + def test_gh_chisquare_12282(self, xp): + # Currently `chisquare` is implemented via power_divergence + # in case that ever changes, perform a basic test like + # test_power_divergence_gh_12282 + with assert_raises(ValueError, match='For each axis slice...'): + f_obs = xp.asarray([10., 20.]) + f_exp = xp.asarray([30., 60.]) + stats.chisquare(f_obs=f_obs, f_exp=f_exp) + + @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) + + 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 = np.array([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 = np.arange(1, len(obs) + 1) + alpha = np.log(obs.sum() / np.exp(beta*i).sum()) + expected_counts = np.exp(alpha + beta*i) + + # `table4` holds just the second and third columns from Table 4. + table4 = np.vstack((obs, expected_counts)).T + + table5 = np.array([ + # 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, + ]).reshape(-1, 2) + + for lambda_, expected_stat in table5: + stat, p = stats.power_divergence(table4[:,0], table4[:,1], + lambda_=lambda_) + assert_allclose(stat, expected_stat, rtol=5e-3) def test_chisquare_masked_arrays(): @@ -4237,16 +4284,16 @@ def test_chisquare_masked_arrays(): with suppress_warnings() as sup: sup.filter(RuntimeWarning, "Mean of empty slice") chisq, p = stats.chisquare(np.ma.array([])) - assert_(isinstance(chisq, np.ma.MaskedArray)) + # assert_(isinstance(chisq, np.ma.MaskedArray)) assert_equal(chisq.shape, ()) - assert_(chisq.mask) + # assert_(chisq.mask) empty3 = np.ma.array([[],[],[]]) # empty3 is a collection of 0 data sets (whose lengths would be 3, if # there were any), so the return value is an array with length 0. chisq, p = stats.chisquare(empty3) - assert_(isinstance(chisq, np.ma.MaskedArray)) + # assert_(isinstance(chisq, np.ma.MaskedArray)) mat.assert_array_equal(chisq, []) # empty3.T is an array containing 3 data sets, each with length 0, @@ -4256,54 +4303,9 @@ def test_chisquare_masked_arrays(): sup.filter(RuntimeWarning, "Mean of empty slice") chisq, p = stats.chisquare(empty3.T) - assert_(isinstance(chisq, np.ma.MaskedArray)) + # assert_(isinstance(chisq, np.ma.MaskedArray)) assert_equal(chisq.shape, (3,)) - assert_(np.all(chisq.mask)) - - -def test_power_divergence_against_cressie_read_data(): - # 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 = np.array([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 = np.arange(1, len(obs) + 1) - alpha = np.log(obs.sum() / np.exp(beta*i).sum()) - expected_counts = np.exp(alpha + beta*i) - - # `table4` holds just the second and third columns from Table 4. - table4 = np.vstack((obs, expected_counts)).T - - table5 = np.array([ - # 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, - ]).reshape(-1, 2) - - for lambda_, expected_stat in table5: - stat, p = stats.power_divergence(table4[:,0], table4[:,1], - lambda_=lambda_) - assert_allclose(stat, expected_stat, rtol=5e-3) + # assert_(np.all(chisq.mask)) def test_friedmanchisquare(): From d688b38fafcb568523df3c8167cf4022d2e3d5e2 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 20 May 2024 11:15:53 -0700 Subject: [PATCH 222/500] MAINT: stats.power_divergence: deprecate masked array support --- scipy/stats/_stats_py.py | 42 ++++-- scipy/stats/tests/test_stats.py | 242 +++++++++++++++++--------------- 2 files changed, 158 insertions(+), 126 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index b1aac8a7727c..5ae915552778 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -7928,9 +7928,10 @@ def _m_broadcast_to(a, shape, *, xp): return xp.broadcast_to(a, shape) -def _m_sum(a, *, axis, xp): +def _m_sum(a, *, axis, preserve_mask, xp): if np.ma.isMaskedArray(a): - return np.asarray(a.sum(axis)) + sum = a.sum(axis) + return sum if preserve_mask else np.asarray(sum) return xp.sum(a, axis=axis) @@ -7955,9 +7956,19 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): ---------- f_obs : array_like Observed frequencies in each category. + + .. deprecated:: 1.14.0 + Support for masked array input was deprecated in + SciPy 1.14.0 and will be removed in version 1.16.0. + f_exp : array_like, optional Expected frequencies in each category. By default the categories are assumed to be equally likely. + + .. deprecated:: 1.14.0 + Support for masked array input was deprecated in + SciPy 1.14.0 and will be removed in version 1.16.0. + 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 @@ -8011,7 +8022,8 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): 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 ``1e-8``. + 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 @@ -8028,12 +8040,6 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): the asymptotic distribution is not a chisquare, in which case this test is not appropriate. - This function handles masked arrays. If an element of `f_obs` or `f_exp` - is masked, then data at that position is ignored, and does not count - towards the size of the data set. - - .. versionadded:: 0.13.0 - References ---------- .. [1] Lowry, Richard. "Concepts and Applications of Inferential @@ -8120,6 +8126,14 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): elif lambda_ is None: lambda_ = 1 + def warn_masked(arg): + if isinstance(arg, ma.MaskedArray): + message = ( + "`power_divergence` and `chisquare` support for masked array input was " + "deprecated in SciPy 1.14.0 and will be removed in version 1.16.0.") + warnings.warn(message, DeprecationWarning, stacklevel=2) + + warn_masked(f_obs) f_obs = f_obs if np.ma.isMaskedArray(f_obs) else xp.asarray(f_obs) dtype = default_float if xp.isdtype(f_obs.dtype, 'integral') else f_obs.dtype f_obs = (f_obs.astype(dtype) if np.ma.isMaskedArray(f_obs) @@ -8128,6 +8142,7 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): else xp.asarray(f_obs, dtype=xp.float64)) if f_exp is not None: + warn_masked(f_exp) f_exp = f_exp if np.ma.isMaskedArray(f_obs) else xp.asarray(f_exp) dtype = default_float if xp.isdtype(f_exp.dtype, 'integral') else f_exp.dtype f_exp = (f_exp.astype(dtype) if np.ma.isMaskedArray(f_exp) @@ -8136,10 +8151,11 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): bshape = _broadcast_shapes((f_obs_float.shape, f_exp.shape)) f_obs_float = _m_broadcast_to(f_obs_float, bshape, xp=xp) f_exp = _m_broadcast_to(f_exp, bshape, xp=xp) - rtol = 1e-8 # to pass existing tests + 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 = _m_sum(f_obs_float, axis=axis, xp=xp) - f_exp_sum = _m_sum(f_exp, axis=axis, xp=xp) + f_obs_sum = _m_sum(f_obs_float, axis=axis, preserve_mask=False, xp=xp) + f_exp_sum = _m_sum(f_exp, axis=axis, preserve_mask=False, xp=xp) 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=None) @@ -8174,7 +8190,7 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): terms = f_obs * ((f_obs / f_exp)**lambda_ - 1) terms /= 0.5 * lambda_ * (lambda_ + 1) - stat = _m_sum(terms, axis=axis, xp=xp) + stat = _m_sum(terms, axis=axis, preserve_mask=True, xp=xp) num_obs = _m_count(terms, axis=axis, xp=xp) ddof = xp.asarray(ddof) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 862c79621cd0..c7a861a15376 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -3382,7 +3382,7 @@ def test_constant_moments(self, dtype, expect, order, xp): @skip_xp_backends('jax.numpy', reasons=["JAX arrays do not support item assignment"]) - @pytest.mark.usefixtures("skip_xp_backends") + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_moment_propagate_nan(self, xp): # Check that the shape of the result is the same for inputs @@ -3445,7 +3445,7 @@ def test_empty_1d(self): @skip_xp_backends('jax.numpy', reasons=["JAX arrays do not support item assignment"]) - @pytest.mark.usefixtures("skip_xp_backends") + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_skewness(self, xp): # Scalar test case @@ -3506,7 +3506,7 @@ def test_skew_constant_value(self, xp): @skip_xp_backends('jax.numpy', reasons=["JAX arrays do not support item assignment"]) - @pytest.mark.usefixtures("skip_xp_backends") + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_precision_loss_gh15554(self, xp): # gh-15554 was one of several issues that have reported problems with @@ -3520,7 +3520,7 @@ def test_precision_loss_gh15554(self, xp): @skip_xp_backends('jax.numpy', reasons=["JAX arrays do not support item assignment"]) - @pytest.mark.usefixtures("skip_xp_backends") + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible @pytest.mark.parametrize('axis', [-1, 0, 2, None]) @pytest.mark.parametrize('bias', [False, True]) @@ -4006,6 +4006,9 @@ def test_nd(self, shape): ] +@array_api_compatible +@pytest.mark.usefixtures("skip_xp_backends") +@pytest.mark.skip_xp_backends(cpu_only=True, reasons=['Uses NumPy for pvalue']) class TestPowerDivergence: def check_power_divergence(self, f_obs, f_exp, ddof, axis, lambda_, @@ -4041,7 +4044,6 @@ def check_power_divergence(self, f_obs, f_exp, ddof, axis, lambda_, num_obs - 1 - ddof) xp_assert_close(p, xp.asarray(expected_p, dtype=dtype)) - @array_api_compatible @pytest.mark.parametrize('case', power_div_1d_cases) @pytest.mark.parametrize('lambda_stat', [(None, 'chi2'), ('pearson', 'chi2'), (1, 'chi2'), @@ -4053,7 +4055,6 @@ def test_basic(self, case, lambda_stat, xp): self.check_power_divergence(case.f_obs, case.f_exp, case.ddof, case.axis, lambda_, expected_stat, xp) - @array_api_compatible def test_axis(self, xp): case0 = power_div_1d_cases[0] case1 = power_div_1d_cases[1] @@ -4083,7 +4084,6 @@ def test_axis(self, xp): f_obs_reshape, None, 0, None, "pearson", case0.chi2, xp=xp) - @array_api_compatible def test_ddof_broadcasting(self, xp): # Test that ddof broadcasts correctly. # ddof does not affect the test statistic. It is broadcast @@ -4121,7 +4121,6 @@ def test_ddof_broadcasting(self, xp): axis=0) xp_assert_close(p, expected_p) - @array_api_compatible @pytest.mark.parametrize('case', power_div_empty_cases) @pytest.mark.parametrize('lambda_stat', [('pearson', 'chi2'), ('log-likelihood', 'log'), @@ -4135,7 +4134,6 @@ def test_empty_cases(self, case, lambda_stat, xp): case.f_obs, case.f_exp, case.ddof, case.axis, lambda_, expected_stat, xp) - @array_api_compatible 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 @@ -4150,7 +4148,6 @@ def test_power_divergence_result_attributes(self, xp): attributes = ('statistic', 'pvalue') check_named_results(res, attributes) - @array_api_compatible 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.]]) @@ -4164,30 +4161,6 @@ def test_power_divergence_gh_12282(self, xp): xp_assert_close(stat, xp.asarray([5.71428571, 2.66666667])) xp_assert_close(pval, xp.asarray([0.01682741, 0.10247043])) -@array_api_compatible -class TestChisquare: - def test_gh_chisquare_12282(self, xp): - # Currently `chisquare` is implemented via power_divergence - # in case that ever changes, perform a basic test like - # test_power_divergence_gh_12282 - with assert_raises(ValueError, match='For each axis slice...'): - f_obs = xp.asarray([10., 20.]) - f_exp = xp.asarray([30., 60.]) - stats.chisquare(f_obs=f_obs, f_exp=f_exp) - - @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) - 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", @@ -4197,17 +4170,19 @@ def test_power_divergence_against_cressie_read_data(self, xp): # 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 = np.array([15, 11, 14, 17, 5, 11, 10, 4, 8, - 10, 7, 9, 11, 3, 6, 1, 1, 4]) + 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 = np.arange(1, len(obs) + 1) - alpha = np.log(obs.sum() / np.exp(beta*i).sum()) - expected_counts = np.exp(alpha + beta*i) + 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 = np.vstack((obs, expected_counts)).T + xp_test = array_namespace(obs) # NumPy needs concat, torch needs newaxis + table4 = xp_test.concat((obs[xp_test.newaxis, :], + expected_counts[xp_test.newaxis, :])).T - table5 = np.array([ + table5 = xp.asarray([ # lambda, statistic -10.0, 72.2e3, -5.0, 28.9e1, @@ -4225,87 +4200,128 @@ def test_power_divergence_against_cressie_read_data(self, xp): 3.0, 24.8, 5.0, 35.5, 10.0, 21.4e1, - ]).reshape(-1, 2) + ]) + table5 = xp.reshape(table5, (-1, 2)) - for lambda_, expected_stat in table5: + for i in range(table5.shape[0]): + lambda_, expected_stat = table5[i, 0], table5[i, 1] stat, p = stats.power_divergence(table4[:,0], table4[:,1], lambda_=lambda_) assert_allclose(stat, expected_stat, rtol=5e-3) -def test_chisquare_masked_arrays(): - # Test masked arrays. - obs = np.array([[8, 8, 16, 32, -1], [-1, -1, 3, 4, 5]]).T - mask = np.array([[0, 0, 0, 0, 1], [1, 1, 0, 0, 0]]).T - mobs = np.ma.masked_array(obs, mask) - expected_chisq = np.array([24.0, 0.5]) - expected_g = np.array([2*(2*8*np.log(0.5) + 32*np.log(2.0)), - 2*(3*np.log(0.75) + 5*np.log(1.25))]) - - chi2 = stats.distributions.chi2 - - chisq, p = stats.chisquare(mobs) - mat.assert_array_equal(chisq, expected_chisq) - mat.assert_array_almost_equal(p, chi2.sf(expected_chisq, - mobs.count(axis=0) - 1)) - - g, p = stats.power_divergence(mobs, lambda_='log-likelihood') - mat.assert_array_almost_equal(g, expected_g, decimal=15) - mat.assert_array_almost_equal(p, chi2.sf(expected_g, - mobs.count(axis=0) - 1)) - - chisq, p = stats.chisquare(mobs.T, axis=1) - mat.assert_array_equal(chisq, expected_chisq) - mat.assert_array_almost_equal(p, chi2.sf(expected_chisq, - mobs.T.count(axis=1) - 1)) - g, p = stats.power_divergence(mobs.T, axis=1, lambda_="log-likelihood") - mat.assert_array_almost_equal(g, expected_g, decimal=15) - mat.assert_array_almost_equal(p, chi2.sf(expected_g, - mobs.count(axis=0) - 1)) - - obs1 = np.ma.array([3, 5, 6, 99, 10], mask=[0, 0, 0, 1, 0]) - exp1 = np.ma.array([2, 4, 8, 10, 99], mask=[0, 0, 0, 0, 1]) - chi2, p = stats.chisquare(obs1, f_exp=exp1) - # Because of the mask at index 3 of obs1 and at index 4 of exp1, - # only the first three elements are included in the calculation - # of the statistic. - mat.assert_array_equal(chi2, 1/2 + 1/4 + 4/8) - - # When axis=None, the two values should have type np.float64. - chisq, p = stats.chisquare(np.ma.array([1,2,3]), axis=None) - assert_(isinstance(chisq, np.float64)) - assert_(isinstance(p, np.float64)) - assert_equal(chisq, 1.0) - assert_almost_equal(p, stats.distributions.chi2.sf(1.0, 2)) - - # Empty arrays: - # A data set with length 0 returns a masked scalar. - with np.errstate(invalid='ignore'): - with suppress_warnings() as sup: - sup.filter(RuntimeWarning, "Mean of empty slice") - chisq, p = stats.chisquare(np.ma.array([])) - # assert_(isinstance(chisq, np.ma.MaskedArray)) - assert_equal(chisq.shape, ()) - # assert_(chisq.mask) - - empty3 = np.ma.array([[],[],[]]) +@array_api_compatible +@pytest.mark.usefixtures("skip_xp_backends") +@pytest.mark.skip_xp_backends(cpu_only=True, reasons=['Uses NumPy for pvalue']) +class TestChisquare: + def test_gh_chisquare_12282(self, xp): + # Currently `chisquare` is implemented via power_divergence + # in case that ever changes, perform a basic test like + # test_power_divergence_gh_12282 + with assert_raises(ValueError, match='For each axis slice...'): + f_obs = xp.asarray([10., 20.]) + f_exp = xp.asarray([30., 60.]) + stats.chisquare(f_obs=f_obs, f_exp=f_exp) - # empty3 is a collection of 0 data sets (whose lengths would be 3, if - # there were any), so the return value is an array with length 0. - chisq, p = stats.chisquare(empty3) - # assert_(isinstance(chisq, np.ma.MaskedArray)) - mat.assert_array_equal(chisq, []) + @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) - # empty3.T is an array containing 3 data sets, each with length 0, - # so an array of size (3,) is returned, with all values masked. - with np.errstate(invalid='ignore'): - with suppress_warnings() as sup: - sup.filter(RuntimeWarning, "Mean of empty slice") - chisq, p = stats.chisquare(empty3.T) - # assert_(isinstance(chisq, np.ma.MaskedArray)) - assert_equal(chisq.shape, (3,)) - # assert_(np.all(chisq.mask)) +@skip_xp_invalid_arg +class TestChisquareMA: + @pytest.mark.filterwarnings('ignore::DeprecationWarning') + def test_chisquare_masked_arrays(self): + # Test masked arrays. + obs = np.array([[8, 8, 16, 32, -1], [-1, -1, 3, 4, 5]]).T + mask = np.array([[0, 0, 0, 0, 1], [1, 1, 0, 0, 0]]).T + mobs = np.ma.masked_array(obs, mask) + expected_chisq = np.array([24.0, 0.5]) + expected_g = np.array([2*(2*8*np.log(0.5) + 32*np.log(2.0)), + 2*(3*np.log(0.75) + 5*np.log(1.25))]) + + chi2 = stats.distributions.chi2 + + chisq, p = stats.chisquare(mobs) + mat.assert_array_equal(chisq, expected_chisq) + mat.assert_array_almost_equal(p, chi2.sf(expected_chisq, + mobs.count(axis=0) - 1)) + + g, p = stats.power_divergence(mobs, lambda_='log-likelihood') + mat.assert_array_almost_equal(g, expected_g, decimal=15) + mat.assert_array_almost_equal(p, chi2.sf(expected_g, + mobs.count(axis=0) - 1)) + + chisq, p = stats.chisquare(mobs.T, axis=1) + mat.assert_array_equal(chisq, expected_chisq) + mat.assert_array_almost_equal(p, chi2.sf(expected_chisq, + mobs.T.count(axis=1) - 1)) + g, p = stats.power_divergence(mobs.T, axis=1, lambda_="log-likelihood") + mat.assert_array_almost_equal(g, expected_g, decimal=15) + mat.assert_array_almost_equal(p, chi2.sf(expected_g, + mobs.count(axis=0) - 1)) + + obs1 = np.ma.array([3, 5, 6, 99, 10], mask=[0, 0, 0, 1, 0]) + exp1 = np.ma.array([2, 4, 8, 10, 99], mask=[0, 0, 0, 0, 1]) + chi2, p = stats.chisquare(obs1, f_exp=exp1) + # Because of the mask at index 3 of obs1 and at index 4 of exp1, + # only the first three elements are included in the calculation + # of the statistic. + mat.assert_array_equal(chi2, 1/2 + 1/4 + 4/8) + + # When axis=None, the two values should have type np.float64. + chisq, p = stats.chisquare(np.ma.array([1,2,3]), axis=None) + assert_(isinstance(chisq, np.float64)) + assert_(isinstance(p, np.float64)) + assert_equal(chisq, 1.0) + assert_almost_equal(p, stats.distributions.chi2.sf(1.0, 2)) + + # Empty arrays: + # A data set with length 0 returns a masked scalar. + with np.errstate(invalid='ignore'): + with suppress_warnings() as sup: + sup.filter(RuntimeWarning, "Mean of empty slice") + chisq, p = stats.chisquare(np.ma.array([])) + assert_(isinstance(chisq, np.ma.MaskedArray)) + assert_equal(chisq.shape, ()) + assert_(chisq.mask) + + empty3 = np.ma.array([[],[],[]]) + + # empty3 is a collection of 0 data sets (whose lengths would be 3, if + # there were any), so the return value is an array with length 0. + chisq, p = stats.chisquare(empty3) + assert_(isinstance(chisq, np.ma.MaskedArray)) + mat.assert_array_equal(chisq, []) + + # empty3.T is an array containing 3 data sets, each with length 0, + # so an array of size (3,) is returned, with all values masked. + with np.errstate(invalid='ignore'): + with suppress_warnings() as sup: + sup.filter(RuntimeWarning, "Mean of empty slice") + chisq, p = stats.chisquare(empty3.T) + + assert_(isinstance(chisq, np.ma.MaskedArray)) + assert_equal(chisq.shape, (3,)) + assert_(np.all(chisq.mask)) + + def test_deprecation_warning(self): + a = np.asarray([1., 2., 3.]) + ma = np.ma.masked_array(a) + message = "`power_divergence` and `chisquare` support for masked..." + with pytest.warns(DeprecationWarning, match=message): + stats.chisquare(ma) + with pytest.warns(DeprecationWarning, match=message): + stats.chisquare(a, ma) def test_friedmanchisquare(): From e2e1940d311230f31f3ff72e28bea9665177d429 Mon Sep 17 00:00:00 2001 From: Roman Nigmatullin Date: Mon, 20 May 2024 21:29:08 +0300 Subject: [PATCH 223/500] ENH: add dtype validation on sparse array/matrix creation. --- scipy/sparse/_base.py | 4 ++-- scipy/sparse/_compressed.py | 3 ++- scipy/sparse/_coo.py | 3 ++- scipy/sparse/_dia.py | 3 ++- scipy/sparse/_dok.py | 6 +++--- scipy/sparse/_lil.py | 5 +++-- scipy/sparse/_sputils.py | 5 ++++- 7 files changed, 18 insertions(+), 11 deletions(-) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 510f7565065d..d79e34d19c50 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -4,7 +4,7 @@ from ._sputils import (asmatrix, check_reshape_kwargs, check_shape, get_sum_dtype, isdense, isscalarlike, - matrix, validateaxis,) + matrix, validateaxis, getdtype) from ._matrix import spmatrix @@ -217,7 +217,7 @@ def astype(self, dtype, casting='unsafe', copy=True): this array/matrix do not share any memory. """ - dtype = np.dtype(dtype) + dtype = getdtype(dtype) if self.dtype != dtype: return self.tocsr().astype( dtype, casting=casting, copy=copy).asformat(self.format) diff --git a/scipy/sparse/_compressed.py b/scipy/sparse/_compressed.py index a86c09fc5edf..593113fb69c1 100644 --- a/scipy/sparse/_compressed.py +++ b/scipy/sparse/_compressed.py @@ -107,7 +107,8 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): self._shape = check_shape(self._swap((major_d, minor_d)), allow_1d=is_array) if dtype is not None: - self.data = self.data.astype(dtype, copy=False) + newdtype = getdtype(dtype) + self.data = self.data.astype(newdtype, copy=False) self.check_format(full_check=False) diff --git a/scipy/sparse/_coo.py b/scipy/sparse/_coo.py index 2f4580b3f004..abe245b0e803 100644 --- a/scipy/sparse/_coo.py +++ b/scipy/sparse/_coo.py @@ -94,7 +94,8 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): self.has_canonical_format = True if dtype is not None: - self.data = self.data.astype(dtype, copy=False) + newdtype = getdtype(dtype) + self.data = self.data.astype(newdtype, copy=False) self._check() diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index 73b55908466d..f50da45fb9a8 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -78,7 +78,8 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): self._shape = check_shape(A.shape) if dtype is not None: - self.data = self.data.astype(dtype) + newdtype = getdtype(dtype) + self.data = self.data.astype(newdtype) #check format if self.offsets.ndim != 1: diff --git a/scipy/sparse/_dok.py b/scipy/sparse/_dok.py index 08a039136ff3..fa7b89111b0a 100644 --- a/scipy/sparse/_dok.py +++ b/scipy/sparse/_dok.py @@ -37,7 +37,7 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): self._dict = arg1._dict self._shape = check_shape(arg1.shape, allow_1d=is_array) - self.dtype = arg1.dtype + self.dtype = getdtype(arg1.dtype) else: # Dense ctor try: arg1 = np.asarray(arg1) @@ -51,11 +51,11 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): if dtype is not None: arg1 = arg1.astype(dtype) self._dict = {i: v for i, v in enumerate(arg1) if v != 0} - self.dtype = arg1.dtype + self.dtype = getdtype(arg1.dtype) else: d = self._coo_container(arg1, dtype=dtype).todok() self._dict = d._dict - self.dtype = d.dtype + self.dtype = getdtype(d.dtype) self._shape = check_shape(arg1.shape, allow_1d=is_array) def update(self, val): diff --git a/scipy/sparse/_lil.py b/scipy/sparse/_lil.py index 2503aa628b58..1ec2a0fb5f2c 100644 --- a/scipy/sparse/_lil.py +++ b/scipy/sparse/_lil.py @@ -32,7 +32,8 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): A = arg1.tolil() if dtype is not None: - A = A.astype(dtype, copy=False) + newdtype = getdtype(dtype) + A = A.astype(newdtype, copy=False) self._shape = check_shape(A.shape) self.dtype = A.dtype @@ -62,7 +63,7 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): A = self._csr_container(A, dtype=dtype).tolil() self._shape = check_shape(A.shape) - self.dtype = A.dtype + self.dtype = getdtype(A.dtype) self.rows = A.rows self.data = A.data diff --git a/scipy/sparse/_sputils.py b/scipy/sparse/_sputils.py index fa515606006d..3536f8d6e9b4 100644 --- a/scipy/sparse/_sputils.py +++ b/scipy/sparse/_sputils.py @@ -4,6 +4,7 @@ import sys from typing import Any, Literal, Optional, Union import operator +from warnings import warn import numpy as np from math import prod import scipy.sparse as sp @@ -130,7 +131,9 @@ def getdtype(dtype, a=None, default=None): raise ValueError( "object dtype is not supported by sparse matrices" ) - + if newdtype not in supported_dtypes: + warn(f"dtype {newdtype} is not supported", stacklevel=3) + return newdtype From 808c82b1cb5e4fdb3cedc8e744565889078e22be Mon Sep 17 00:00:00 2001 From: Roman Nigmatullin Date: Mon, 20 May 2024 22:00:08 +0300 Subject: [PATCH 224/500] ENH: change warning message on validation --- scipy/sparse/_sputils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/sparse/_sputils.py b/scipy/sparse/_sputils.py index 3536f8d6e9b4..b926d5dee449 100644 --- a/scipy/sparse/_sputils.py +++ b/scipy/sparse/_sputils.py @@ -132,7 +132,7 @@ def getdtype(dtype, a=None, default=None): "object dtype is not supported by sparse matrices" ) if newdtype not in supported_dtypes: - warn(f"dtype {newdtype} is not supported", stacklevel=3) + warn(f"scipy.sparse does not support dtype {newdtype}", stacklevel=3) return newdtype From 19968abe9fbfb76640a338456de8b505f5fd2223 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 20 May 2024 13:27:10 -0700 Subject: [PATCH 225/500] Update scipy/stats/tests/test_morestats.py [skip ci] --- scipy/stats/tests/test_morestats.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 4ba64039ac05..3f7d2bff6816 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -736,6 +736,8 @@ def test_alternative_approx(self): @array_api_compatible +@pytest.mark.usefixtures("skip_xp_backends") +@pytest.mark.skip_xp_backends(cpu_only=True, reasons=['Uses NumPy for pvalue']) class TestBartlett: def test_data(self, xp): # https://www.itl.nist.gov/div898/handbook/eda/section3/eda357.htm From cd05b0e5579b7649e4041ea0f817d45d4d7dff47 Mon Sep 17 00:00:00 2001 From: Nimish Telang Date: Mon, 20 May 2024 17:13:46 -0400 Subject: [PATCH 226/500] ENH: stats: Add the Irwin-Hall distribution (#20481) - ENH: stats: Add the Irwin-Hall distribution --- benchmarks/benchmarks/stats.py | 2 + scipy/stats/__init__.py | 1 + scipy/stats/_continuous_distns.py | 132 ++++++++++++++++++++- scipy/stats/_discrete_distns.py | 44 +------ scipy/stats/_distn_infrastructure.py | 41 ++++++- scipy/stats/_distr_params.py | 4 + scipy/stats/tests/test_continuous_basic.py | 14 +-- scipy/stats/tests/test_distributions.py | 104 +++++++++++++++- scipy/stats/tests/test_fit.py | 10 +- 9 files changed, 293 insertions(+), 59 deletions(-) diff --git a/benchmarks/benchmarks/stats.py b/benchmarks/benchmarks/stats.py index fcdcc8ad5596..3a5692c405a0 100644 --- a/benchmarks/benchmarks/stats.py +++ b/benchmarks/benchmarks/stats.py @@ -245,6 +245,8 @@ def setup(self, dist_name, method): if isinstance(self.dist, stats.rv_discrete): raise NotImplementedError("This attribute is not a member " "of the distribution") + if self.dist.name in {'irwinhall'}: + raise NotImplementedError("Fit is unreliable.") # the only positional argument is the data to be fitted self.args = [self.dist.rvs(*dist_shapes, size=100, random_state=0, **kwds)] elif method == 'rvs': diff --git a/scipy/stats/__init__.py b/scipy/stats/__init__.py index ba5a4886ce94..3866a23354a6 100644 --- a/scipy/stats/__init__.py +++ b/scipy/stats/__init__.py @@ -97,6 +97,7 @@ 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 diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py index 5e6832de507d..b06abfa7f926 100644 --- a/scipy/stats/_continuous_distns.py +++ b/scipy/stats/_continuous_distns.py @@ -9,6 +9,7 @@ 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) @@ -23,8 +24,8 @@ from . import _stats from ._tukeylambda_stats import (tukeylambda_variance as _tlvar, tukeylambda_kurtosis as _tlkurt) -from ._distn_infrastructure import ( - get_distribution_names, _kurtosis, +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, @@ -9036,6 +9037,133 @@ def _munp(self, n, b): 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): + 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") class recipinvgauss_gen(rv_continuous): r"""A reciprocal inverse Gaussian continuous random variable. diff --git a/scipy/stats/_discrete_distns.py b/scipy/stats/_discrete_distns.py index b0216eaeeeaf..f7721f5c3a03 100644 --- a/scipy/stats/_discrete_distns.py +++ b/scipy/stats/_discrete_distns.py @@ -14,16 +14,14 @@ import numpy as np from ._distn_infrastructure import (rv_discrete, get_distribution_names, - _check_shape, _ShapeInfo) + _vectorize_rvs_over_shapes, + _ShapeInfo, _isintegral) from ._biasedurn import (_PyFishersNCHypergeometric, _PyWalleniusNCHypergeometric, _PyStochasticLib3) import scipy.special._ufuncs as scu -def _isintegral(x): - return x == np.round(x) - class binom_gen(rv_discrete): r"""A binomial discrete random variable. @@ -1678,44 +1676,6 @@ def _stats(self, alpha): yulesimon = yulesimon_gen(name='yulesimon', a=1) -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 - - class _nchypergeom_gen(rv_discrete): r"""A noncentral hypergeometric discrete random variable. diff --git a/scipy/stats/_distn_infrastructure.py b/scipy/stats/_distn_infrastructure.py index c8dbfcb6ec21..50611b9b9f3d 100644 --- a/scipy/stats/_distn_infrastructure.py +++ b/scipy/stats/_distn_infrastructure.py @@ -410,13 +410,50 @@ def _skew(data): def _kurtosis(data): - """kurtosis is fourth central moment / variance**2 - 3.""" + """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): @@ -430,6 +467,8 @@ def _fit_determine_optimizer(optimizer): raise ValueError("%s is not a valid optimizer" % optimizer) from e return optimizer +def _isintegral(x): + return x == np.round(x) def _sum_finite(x): """ diff --git a/scipy/stats/_distr_params.py b/scipy/stats/_distr_params.py index 2b86298d592f..65b82929fe62 100644 --- a/scipy/stats/_distr_params.py +++ b/scipy/stats/_distr_params.py @@ -55,6 +55,7 @@ ['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)], @@ -226,6 +227,9 @@ ['invgamma', (-1, )], ['invgauss', (-1, )], ['invweibull', (-1, )], + ['irwinhall', (-1,)], + ['irwinhall', (0,)], + ['irwinhall', (2.5,)], ['jf_skew_t', (-1, 0)], ['johnsonsb', (1, -2)], ['johnsonsu', (1, -2)], diff --git a/scipy/stats/tests/test_continuous_basic.py b/scipy/stats/tests/test_continuous_basic.py index b2be9d590d3e..9f93fb662350 100644 --- a/scipy/stats/tests/test_continuous_basic.py +++ b/scipy/stats/tests/test_continuous_basic.py @@ -52,7 +52,7 @@ 'recipinvgauss', 'kstwo', 'kappa4'} xslow_fit_mle = {'gausshyper', 'ncf', 'ncx2', 'recipinvgauss', 'vonmises_line'} -xfail_fit_mle = {'ksone', 'kstwo', 'trapezoid', 'truncpareto'} +xfail_fit_mle = {'ksone', 'kstwo', 'trapezoid', 'truncpareto', 'irwinhall'} skip_fit_mle = {'levy_stable', 'studentized_range'} # far too slow (>10min) xslow_fit_mm = {'argus', 'beta', 'exponpow', 'gausshyper', 'gengamma', 'genhalflogistic', 'geninvgauss', 'gompertz', 'halfgennorm', @@ -60,11 +60,11 @@ 'truncweibull_min', 'wrapcauchy'} xfail_fit_mm = {'alpha', 'betaprime', 'bradford', 'burr', 'burr12', 'cauchy', 'crystalball', 'exponweib', 'f', 'fisk', 'foldcauchy', 'genextreme', - 'genpareto', 'halfcauchy', 'invgamma', 'jf_skew_t', 'johnsonsu', - 'kappa3', 'kappa4', 'levy', 'levy_l', 'loglaplace', 'lomax', 'mielke', - 'ncf', 'nct', 'pareto', 'powerlognorm', 'powernorm', 'rel_breitwigner', - 'skewcauchy', 't', 'trapezoid', 'truncexpon', 'truncpareto', - 'tukeylambda', 'vonmises', 'vonmises_line'} + 'genpareto', 'halfcauchy', 'invgamma', 'irwinhall', 'jf_skew_t', + 'johnsonsu', 'kappa3', 'kappa4', 'levy', 'levy_l', 'loglaplace', + 'lomax', 'mielke', 'ncf', 'nct', 'pareto', 'powerlognorm', 'powernorm', + 'rel_breitwigner', 'skewcauchy', 't', 'trapezoid', 'truncexpon', + 'truncpareto', 'tukeylambda', 'vonmises', 'vonmises_line'} skip_fit_mm = {'genexpon', 'genhyperbolic', 'ksone', 'kstwo', 'levy_stable', 'recipinvgauss', 'studentized_range'} # far too slow (>10min) @@ -76,7 +76,7 @@ 'dgamma', 'dweibull', 'erlang', 'f', 'foldcauchy', 'gamma', 'gausshyper', 'gengamma', 'genhyperbolic', 'geninvgauss', 'gennorm', 'genpareto', - 'halfcauchy', 'halfgennorm', 'invgamma', 'jf_skew_t', + 'halfcauchy', 'halfgennorm', 'invgamma', 'irwinhall', 'jf_skew_t', 'ksone', 'kstwo', 'kstwobign', 'levy_l', 'loggamma', 'logistic', 'loguniform', 'maxwell', 'nakagami', 'ncf', 'nct', 'ncx2', 'norminvgauss', 'pearson3', diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py index a90af1301691..4e8dfc5257df 100644 --- a/scipy/stats/tests/test_distributions.py +++ b/scipy/stats/tests/test_distributions.py @@ -13,7 +13,8 @@ from numpy.testing import (assert_equal, assert_array_equal, assert_almost_equal, assert_array_almost_equal, assert_allclose, assert_, assert_warns, - assert_array_less, suppress_warnings, IS_PYPY) + assert_array_less, suppress_warnings, + assert_array_max_ulp, IS_PYPY) import pytest from pytest import raises as assert_raises @@ -5850,13 +5851,14 @@ def test_args_reduce(): class TestFitMethod: - skip = ['ncf', 'ksone', 'kstwo'] + # fitting assumes continuous parameters + skip = ['ncf', 'ksone', 'kstwo', 'irwinhall'] def setup_method(self): np.random.seed(1234) # skip these b/c deprecated, or only loc and scale arguments - fitSkipNonFinite = ['expon', 'norm', 'uniform'] + fitSkipNonFinite = ['expon', 'norm', 'uniform', 'irwinhall'] @pytest.mark.parametrize('dist,args', distcont) def test_fit_w_non_finite_data_values(self, dist, args): @@ -9669,6 +9671,102 @@ def test_sf(self): 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) + + # 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. diff --git a/scipy/stats/tests/test_fit.py b/scipy/stats/tests/test_fit.py index b9cf08a61308..726657239c77 100644 --- a/scipy/stats/tests/test_fit.py +++ b/scipy/stats/tests/test_fit.py @@ -24,6 +24,7 @@ 'gausshyper', 'genexpon', 'gengamma', + 'irwinhall', 'kappa4', 'ksone', 'kstwo', @@ -63,7 +64,7 @@ 'chi2', 'crystalball', 'dgamma', 'dweibull', 'f', 'fatiguelife', 'fisk', 'foldcauchy', 'genextreme', 'gengamma', 'genhyperbolic', 'gennorm', 'genpareto', - 'halfcauchy', 'invgamma', 'invweibull', 'jf_skew_t', + 'halfcauchy', 'invgamma', 'invweibull', 'irwinhall', 'jf_skew_t', 'johnsonsu', 'kappa3', 'ksone', 'kstwo', 'levy', 'levy_l', 'levy_stable', 'loglaplace', 'lomax', 'mielke', 'nakagami', 'ncf', 'nct', 'ncx2', 'pareto', 'powerlognorm', 'powernorm', @@ -228,9 +229,9 @@ def test_nnlf_and_related_methods(dist, params): def cases_test_fit_mle(): # These fail default test or hang - skip_basic_fit = {'argus', 'foldnorm', 'truncpareto', 'truncweibull_min', - 'ksone', 'levy_stable', 'studentized_range', 'kstwo', - 'arcsine'} + skip_basic_fit = {'argus', 'irwinhall', 'foldnorm', 'truncpareto', + 'truncweibull_min', 'ksone', 'levy_stable', + 'studentized_range', 'kstwo', 'arcsine'} # Please keep this list in alphabetical order... slow_basic_fit = {'alpha', 'betaprime', 'binom', 'bradford', 'burr12', @@ -273,6 +274,7 @@ def cases_test_fit_mle(): 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 From c3d0eac478ec93b4daab2a5ba274f80cdb2d84f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Tue, 21 May 2024 13:24:24 +0200 Subject: [PATCH 227/500] Support pydata/sparse for norm and lsqr --- scipy/sparse/linalg/_eigen/arpack/arpack.py | 6 +++++- scipy/sparse/linalg/_isolve/lsqr.py | 2 ++ scipy/sparse/linalg/_norm.py | 2 ++ scipy/sparse/linalg/tests/test_pydata_sparse.py | 7 +++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/scipy/sparse/linalg/_eigen/arpack/arpack.py b/scipy/sparse/linalg/_eigen/arpack/arpack.py index f7a6fa218ca4..4fb6f3e4eb09 100644 --- a/scipy/sparse/linalg/_eigen/arpack/arpack.py +++ b/scipy/sparse/linalg/_eigen/arpack/arpack.py @@ -40,7 +40,9 @@ from scipy.sparse.linalg._interface import aslinearoperator, LinearOperator from scipy.sparse import eye, issparse from scipy.linalg import eig, eigh, lu_factor, lu_solve -from scipy.sparse._sputils import isdense, is_pydata_spmatrix +from scipy.sparse._sputils import ( + convert_pydata_sparse_to_scipy, isdense, is_pydata_spmatrix, +) from scipy.sparse.linalg import gmres, splu from scipy._lib._util import _aligned_zeros from scipy._lib._threadsafety import ReentrancyLock @@ -1256,6 +1258,8 @@ def eigs(A, k=6, M=None, sigma=None, which='LM', v0=None, (13, 6) """ + A = convert_pydata_sparse_to_scipy(A) + 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: diff --git a/scipy/sparse/linalg/_isolve/lsqr.py b/scipy/sparse/linalg/_isolve/lsqr.py index 010f61bc5412..ba684d147e42 100644 --- a/scipy/sparse/linalg/_isolve/lsqr.py +++ b/scipy/sparse/linalg/_isolve/lsqr.py @@ -54,6 +54,7 @@ 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 @@ -319,6 +320,7 @@ def lsqr(A, b, damp=0.0, atol=1e-6, btol=1e-6, conlim=1e8, 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) b = np.atleast_1d(b) if b.ndim > 1: diff --git a/scipy/sparse/linalg/_norm.py b/scipy/sparse/linalg/_norm.py index 38f3a6d7a6f8..4cd7fd6f50dc 100644 --- a/scipy/sparse/linalg/_norm.py +++ b/scipy/sparse/linalg/_norm.py @@ -4,6 +4,7 @@ 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 @@ -110,6 +111,7 @@ def norm(x, ord=None, axis=None): >>> 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") diff --git a/scipy/sparse/linalg/tests/test_pydata_sparse.py b/scipy/sparse/linalg/tests/test_pydata_sparse.py index b42448d0ef1a..62a7adc49e98 100644 --- a/scipy/sparse/linalg/tests/test_pydata_sparse.py +++ b/scipy/sparse/linalg/tests/test_pydata_sparse.py @@ -212,6 +212,13 @@ def test_onenormest(matrices): assert_allclose(est, est0) +def test_norm(matrices): + A_dense, A_sparse, b = matrices + norm0 = splin.norm(sp.csr_matrix(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_matrix(A_dense)) From c3ce012452b558682f6d690c990b6afe71d903e0 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 21 May 2024 06:28:26 -0700 Subject: [PATCH 228/500] TST: stats: refactor tests of normality tests (#20756) --- doc/source/conf.py | 2 +- scipy/stats/_stats_py.py | 18 +- scipy/stats/tests/test_axis_nan_policy.py | 4 +- scipy/stats/tests/test_mstats_basic.py | 1 + scipy/stats/tests/test_stats.py | 279 ++++++++-------------- 5 files changed, 120 insertions(+), 184 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 863010427f1f..ce309446eec2 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -350,7 +350,7 @@ for key in ( 'interp2d` is deprecated', # Deprecation of scipy.interpolate.interp2d 'scipy.misc', # scipy.misc deprecated in v1.10.0; use scipy.datasets - 'kurtosistest only valid', # intentionally "bad" excample in docstring + '`kurtosistest` p-value may be', # intentionally "bad" excample in docstring 'scipy.signal.daub is deprecated', 'scipy.signal.qmf is deprecated', 'scipy.signal.cascade is deprecated', diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 0ff6f476aa32..39a6783aec4f 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -1612,8 +1612,9 @@ def skewtest(a, axis=0, nan_policy='propagate', alternative='two-sided'): b2 = skew(a, axis) n = a.shape[axis] if n < 8: - raise ValueError( - f"skewtest is not valid with less than 8 samples; {n} samples were given.") + message = ("`skewtest` requires at least 8 observations; " + f"{n} observations were given.") + raise ValueError(message) y = b2 * math.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))) @@ -1803,14 +1804,15 @@ def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): a, axis = _chk_asarray(a, axis, xp=xp) n = a.shape[axis] + if n < 5: - raise ValueError( - "kurtosistest requires at least 5 observations; %i observations" - " were given." % int(n)) + message = ("`kurtosistest` requires at least 5 observations; " + f"only {n=} observations were given.") + raise ValueError(message) if n < 20: - warnings.warn("kurtosistest only valid for n>=20 ... continuing " - "anyway, n=%i" % int(n), - stacklevel=2) + message = ("`kurtosistest` p-value may be inaccurate with fewer than 20 " + f"observations; only {n=} observations were given.") + warnings.warn(message, stacklevel=2) b2 = kurtosis(a, axis, fisher=False) E = 3.0*(n-1) / (n+1) diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index d77e2f8a6815..b9c73666186e 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -135,8 +135,8 @@ def ttest_ci(*args, **kwargs): "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 is not valid with less than", - "kurtosistest requires at least 5", + "`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.",} diff --git a/scipy/stats/tests/test_mstats_basic.py b/scipy/stats/tests/test_mstats_basic.py index e327e7ca89c5..729a30fef1aa 100644 --- a/scipy/stats/tests/test_mstats_basic.py +++ b/scipy/stats/tests/test_mstats_basic.py @@ -1840,6 +1840,7 @@ def test_skewtest_2D_WithMask(self): def test_normaltest(self): with np.errstate(over='raise'), suppress_warnings() as sup: + sup.filter(UserWarning, "`kurtosistest` p-value may be inaccurate") sup.filter(UserWarning, "kurtosistest only valid for n>=20") for n in self.get_n(): if n > 8: diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 2c57e6a7efd8..f8addd23475d 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -3381,7 +3381,7 @@ def test_constant_moments(self, dtype, expect, order, xp): @skip_xp_backends('jax.numpy', reasons=["JAX arrays do not support item assignment"]) - @pytest.mark.usefixtures("skip_xp_backends") + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_moment_propagate_nan(self, xp): # Check that the shape of the result is the same for inputs @@ -3444,7 +3444,7 @@ def test_empty_1d(self): @skip_xp_backends('jax.numpy', reasons=["JAX arrays do not support item assignment"]) - @pytest.mark.usefixtures("skip_xp_backends") + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_skewness(self, xp): # Scalar test case @@ -3505,7 +3505,7 @@ def test_skew_constant_value(self, xp): @skip_xp_backends('jax.numpy', reasons=["JAX arrays do not support item assignment"]) - @pytest.mark.usefixtures("skip_xp_backends") + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_precision_loss_gh15554(self, xp): # gh-15554 was one of several issues that have reported problems with @@ -3519,7 +3519,7 @@ def test_precision_loss_gh15554(self, xp): @skip_xp_backends('jax.numpy', reasons=["JAX arrays do not support item assignment"]) - @pytest.mark.usefixtures("skip_xp_backends") + @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible @pytest.mark.parametrize('axis', [-1, 0, 2, None]) @pytest.mark.parametrize('bias', [False, True]) @@ -6188,160 +6188,113 @@ def test_describe_empty(self, xp): reasons=['Uses NumPy for pvalue']) @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible -def test_normalitytests(xp): - assert_raises(ValueError, stats.skewtest, 4.) - assert_raises(ValueError, stats.kurtosistest, 4.) - assert_raises(ValueError, stats.normaltest, 4.) - - # numbers verified with R: dagoTest in 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 - st_normal, st_skew, st_kurt = (xp.asarray(3.92371918158185551), - xp.asarray(1.98078826090875881), - xp.asarray(-0.01403734404759738)) - pv_normal, pv_skew, pv_kurt = (xp.asarray(0.14059672529747502), - xp.asarray(0.04761502382843208), - xp.asarray(0.98880018772590561)) - pv_skew_less, pv_kurt_less = 1 - pv_skew / 2, pv_kurt / 2 - pv_skew_greater, pv_kurt_greater = pv_skew / 2, 1 - pv_kurt / 2 - x = np.array((-2, -1, 0, 1, 2, 3.)*4)**2 - x_xp = xp.asarray((-2, -1, 0, 1, 2, 3.)*4)**2 - attributes = ('statistic', 'pvalue') +class NormalityTests: + def test_too_small(self, xp): + # 1D sample has too few observations -> error + test_fun = getattr(stats, self.test_name) + message = "...requires at least..." + with pytest.raises(ValueError, match=message): + test_fun(xp.asarray(4.)) - res = stats.normaltest(x_xp) - xp_assert_close(res.statistic, st_normal) - xp_assert_close(res.pvalue, pv_normal) - check_named_results(stats.normaltest(x), attributes) - - res = stats.skewtest(x_xp) - xp_assert_close(res.statistic, st_skew) - xp_assert_close(res.pvalue, pv_skew) - res = stats.skewtest(x_xp, alternative='less') - xp_assert_close(res.statistic, st_skew) - xp_assert_close(res.pvalue, pv_skew_less) - res = stats.skewtest(x_xp, alternative='greater') - xp_assert_close(res.statistic, st_skew) - xp_assert_close(res.pvalue, pv_skew_greater) - check_named_results(stats.skewtest(x), attributes) - - res = stats.kurtosistest(x_xp) - xp_assert_close(res.statistic, st_kurt) - xp_assert_close(res.pvalue, pv_kurt) - res = stats.kurtosistest(x_xp, alternative='less') - xp_assert_close(res.statistic, st_kurt) - xp_assert_close(res.pvalue, pv_kurt_less) - res = stats.kurtosistest(x_xp, alternative='greater') - xp_assert_close(res.statistic, st_kurt) - xp_assert_close(res.pvalue, pv_kurt_greater) - check_named_results(stats.kurtosistest(x), attributes) - - # some more intuitive tests for kurtosistest and skewtest. - # see gh-13549. - # skew parameter is 1 > 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) - - # 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) - - # Test axis=None (equal to axis=0 for 1-D input) - res = stats.normaltest(x_xp, axis=None) - xp_assert_close(res.statistic, st_normal) - xp_assert_close(res.pvalue, pv_normal) - - res = stats.skewtest(x_xp, axis=None) - xp_assert_close(res.statistic, st_skew) - xp_assert_close(res.pvalue, pv_skew) - - res = stats.kurtosistest(x_xp, axis=None) - xp_assert_close(res.statistic, st_kurt) - xp_assert_close(res.pvalue, pv_kurt) - - 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 = stats.skewtest(x) - xp_assert_equal(res.statistic, NaN) - xp_assert_equal(res.pvalue, NaN) + @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, ref_pvalue = xp.asarray(self.case_ref) + + 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`') - res = stats.kurtosistest(x) - xp_assert_equal(res.statistic, NaN) - xp_assert_equal(res.pvalue, NaN) + 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')) - res = stats.normaltest(x) - xp_assert_equal(res.statistic, NaN) - xp_assert_equal(res.pvalue, NaN) + 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) + +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 raise a ValueError. + stats.skewtest(xp.arange(8.0)) + message = '`skewtest` requires at least 8 observations...' + with pytest.raises(ValueError, match=message): + stats.skewtest(xp.arange(7.0)) + +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 raise a ValueError. + # At least 20 are needed to avoid warning + # Regression test for ticket #1425. + stats.kurtosistest(xp.arange(20.0)) + + message = "`kurtosistest` p-value may be inaccurate..." + with pytest.warns(UserWarning, match=message): + stats.kurtosistest(xp.arange(5.0)) + with pytest.warns(UserWarning, match=message): + stats.kurtosistest(xp.arange(19.0)) + + message = '`kurtosistest` requires at least 5 observations...' + with pytest.raises(ValueError, match=message): + stats.kurtosistest(xp.arange(4.0)) - # nan_policy only compatible with NumPy arrays - x = np.arange(10.) - x[9] = np.nan - expected = (1.0184643553962129, 0.30845733195153502) - assert_array_almost_equal(stats.skewtest(x, nan_policy='omit'), expected) - - # test alternative with nan_policy='omit' - a1[10:100] = np.nan - z, p = stats.skewtest(a1, nan_policy='omit') - zl, pl = stats.skewtest(a1, nan_policy='omit', alternative='less') - zg, pg = stats.skewtest(a1, nan_policy='omit', alternative='greater') - assert_allclose(zl, z, atol=1e-15) - assert_allclose(zg, z, atol=1e-15) - assert_allclose(pl, 1 - p/2, atol=1e-15) - assert_allclose(pg, p/2, atol=1e-15) - with np.errstate(all='ignore'): - assert_raises(ValueError, stats.skewtest, x, nan_policy='raise') - assert_raises(ValueError, stats.skewtest, x, nan_policy='foobar') - assert_raises(ValueError, stats.skewtest, list(range(8)), - alternative='foobar') - - x = np.arange(30.) - x[29] = np.nan - - # nan_policy only compatible with NumPy arrays - expected = (-2.2683547379505273, 0.023307594135872967) - assert_array_almost_equal(stats.kurtosistest(x, nan_policy='omit'), - expected) - - # test alternative with nan_policy='omit' - a2[10:20] = np.nan - z, p = stats.kurtosistest(a2[:100], nan_policy='omit') - zl, pl = stats.kurtosistest(a2[:100], nan_policy='omit', - alternative='less') - zg, pg = stats.kurtosistest(a2[:100], nan_policy='omit', - alternative='greater') - assert_allclose(zl, z, atol=1e-15) - assert_allclose(zg, z, atol=1e-15) - assert_allclose(pl, 1 - p/2, atol=1e-15) - assert_allclose(pg, p/2, atol=1e-15) - - assert_raises(ValueError, stats.kurtosistest, x, nan_policy='raise') - assert_raises(ValueError, stats.kurtosistest, x, nan_policy='foobar') - assert_raises(ValueError, stats.kurtosistest, list(range(20)), - alternative='foobar') - - expected = (6.2260409514287449, 0.04446644248650191) - assert_array_almost_equal(stats.normaltest(x, nan_policy='omit'), expected) - - assert_raises(ValueError, stats.normaltest, x, nan_policy='raise') - assert_raises(ValueError, stats.normaltest, x, nan_policy='foobar') - - # 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 +class TestNormalTest(NormalityTests): + test_name = 'normaltest' + case_ref = (3.92371918158185551, 0.14059672529747502) # statistic, pvalue class TestRankSums: @@ -6423,26 +6376,6 @@ def test_axis(self, xp): xp_assert_close(res.pvalue, resT.pvalue) -@array_api_compatible -def test_skewtest_too_few_samples(xp): - # Regression test for ticket #1492. - # skewtest requires at least 8 samples; 7 should raise a ValueError. - x = xp.arange(7.0) - message = 'skewtest is not valid with less than 8 samples...' - with pytest.raises(ValueError, match=message): - stats.skewtest(x) - - -@array_api_compatible -def test_kurtosistest_too_few_samples(xp): - # Regression test for ticket #1425. - # kurtosistest requires at least 5 samples; 4 should raise a ValueError. - x = xp.arange(4.0) - message = 'kurtosistest requires at least 5 observations...' - with pytest.raises(ValueError, match=message): - stats.kurtosistest(x) - - class TestMannWhitneyU: X = [19.8958398126694, 19.5452691647182, 19.0577309166425, 21.716543054589, 20.3269502208702, 20.0009273294025, 19.3440043632957, 20.4216806548105, From e4b1a551b18c01b74343d661e0c08fa4b5a804b8 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Tue, 21 May 2024 15:04:58 +0100 Subject: [PATCH 229/500] DOC: stats.kstat/kstatvar: documentation improvements (#20746) * DOC: stats: update formulas given for kstat/kstatvar to reflect implementations * DOC: formatting and style improvements * DOC: use f-strings instead of modulo operator Co-authored-by: Matt Haberland --- scipy/stats/_morestats.py | 76 ++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 71bb78f0f11e..f777d6be0ffc 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -220,10 +220,10 @@ def mvsdist(data): ) def kstat(data, n=2, *, axis=None): r""" - Return the nth k-statistic (1<=n<=4 so far). + Return the `n` th k-statistic ( ``1<=n<=4`` so far). - The nth k-statistic k_n is the unique symmetric unbiased estimator of the - nth cumulant kappa_n. + The `n` th k-statistic ``k_n`` is the unique symmetric unbiased estimator of the + `n` th cumulant :math:`\kappa_n` [1]_ [2]_. Parameters ---------- @@ -240,7 +240,7 @@ def kstat(data, n=2, *, axis=None): Returns ------- kstat : float - The nth k-statistic. + The `n` th k-statistic. See Also -------- @@ -249,23 +249,29 @@ def kstat(data, n=2, *, axis=None): Notes ----- - For a sample size n, the first few k-statistics are given by: + For a sample size :math:`n`, the first few k-statistics are given by .. math:: - k_{1} = \mu - k_{2} = \frac{n}{n-1} m_{2} - k_{3} = \frac{ n^{2} } {(n-1) (n-2)} m_{3} - k_{4} = \frac{ n^{2} [(n + 1)m_{4} - 3(n - 1) m^2_{2}]} {(n-1) (n-2) (n-3)} + 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:`\mu` is the sample mean, :math:`m_2` is the sample - variance, and :math:`m_i` is the i-th sample central moment. + where + + .. math:: + + S_r \equiv \sum_{i=1}^n X_i^r, + + and :math:`X_i` is the :math:`i` th data point. References ---------- - http://mathworld.wolfram.com/k-Statistic.html + .. [1] http://mathworld.wolfram.com/k-Statistic.html - http://mathworld.wolfram.com/Cumulant.html + .. [2] http://mathworld.wolfram.com/Cumulant.html Examples -------- @@ -273,20 +279,20 @@ def kstat(data, n=2, *, axis=None): >>> from numpy.random import default_rng >>> rng = default_rng() - As sample size increases, n-th moment and n-th k-statistic converge to the + 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 n in [2, 3, 4, 5, 6, 7]: - ... x = rng.normal(size=10**n) + >>> for i in range(2,8): + ... x = rng.normal(size=10**i) ... m, k = stats.moment(x, 3), stats.kstat(x, 3) - ... print("%.3g %.3g %.3g" % (m, k, m-k)) - -0.631 -0.651 0.0194 # random - 0.0282 0.0283 -8.49e-05 - -0.0454 -0.0454 1.36e-05 - 7.53e-05 7.53e-05 -2.26e-09 - 0.00166 0.00166 -4.99e-09 - -2.88e-06 -2.88e-06 8.63e-13 + ... 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) @@ -324,7 +330,7 @@ def kstat(data, n=2, *, axis=None): def kstatvar(data, n=2, *, axis=None): r"""Return an unbiased estimator of the variance of the k-statistic. - See `kstat` for more details of the k-statistic. + See `kstat` and [1]_ for more details about the k-statistic. Parameters ---------- @@ -341,7 +347,7 @@ def kstatvar(data, n=2, *, axis=None): Returns ------- kstatvar : float - The nth k-statistic variance. + The `n` th k-statistic variance. See Also -------- @@ -350,21 +356,17 @@ def kstatvar(data, n=2, *, axis=None): Notes ----- - The variances of the first few k-statistics are given by: + Unbiased estimators of the variances of the first two k-statistics are given by .. math:: - var(k_{1}) = \frac{\kappa^2}{n} - var(k_{2}) = \frac{\kappa^4}{n} + \frac{2\kappa^2_{2}}{n - 1} - var(k_{3}) = \frac{\kappa^6}{n} + \frac{9 \kappa_2 \kappa_4}{n - 1} + - \frac{9 \kappa^2_{3}}{n - 1} + - \frac{6 n \kappa^3_{2}}{(n-1) (n-2)} - var(k_{4}) = \frac{\kappa^8}{n} + \frac{16 \kappa_2 \kappa_6}{n - 1} + - \frac{48 \kappa_{3} \kappa_5}{n - 1} + - \frac{34 \kappa^2_{4}}{n-1} + - \frac{72 n \kappa^2_{2} \kappa_4}{(n - 1) (n - 2)} + - \frac{144 n \kappa_{2} \kappa^2_{3}}{(n - 1) (n - 2)} + - \frac{24 (n + 1) n \kappa^4_{2}}{(n - 1) (n - 2) (n - 3)} + \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 + """ # noqa: E501 xp = array_namespace(data) data = xp.asarray(data) From de8d573ad46749ed377cf83062312521c98d7ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A8=97=E0=A8=97=E0=A8=A8=E0=A8=A6=E0=A9=80=E0=A8=AA=20?= =?UTF-8?q?=E0=A8=B8=E0=A8=BF=E0=A9=B0=E0=A8=98=20=28Gagandeep=20Singh=29?= Date: Tue, 21 May 2024 22:58:57 +0530 Subject: [PATCH 230/500] ENH/BLD: Add install tags for `tests` (#20712) --- .github/workflows/linux.yml | 11 ++++- dev.py | 7 +++- scipy/_lib/meson.build | 9 ++-- scipy/_lib/tests/meson.build | 3 +- scipy/cluster/tests/meson.build | 3 +- scipy/constants/tests/meson.build | 3 +- scipy/datasets/tests/meson.build | 3 +- scipy/fft/_pocketfft/tests/meson.build | 3 +- scipy/fft/tests/meson.build | 3 +- scipy/fftpack/tests/meson.build | 3 +- scipy/integrate/_ivp/tests/meson.build | 3 +- scipy/integrate/meson.build | 6 ++- scipy/integrate/tests/meson.build | 3 +- scipy/interpolate/tests/meson.build | 8 ++-- scipy/io/_harwell_boeing/tests/meson.build | 3 +- scipy/io/arff/tests/meson.build | 6 ++- scipy/io/matlab/tests/meson.build | 6 ++- scipy/io/meson.build | 3 +- scipy/io/tests/meson.build | 6 ++- scipy/linalg/tests/data/meson.build | 3 +- scipy/linalg/tests/meson.build | 6 ++- scipy/meson.build | 1 + scipy/misc/tests/meson.build | 3 +- scipy/ndimage/meson.build | 6 ++- scipy/ndimage/tests/meson.build | 6 ++- scipy/odr/tests/meson.build | 3 +- .../_trustregion_constr/tests/meson.build | 3 +- scipy/optimize/tests/meson.build | 6 ++- scipy/signal/tests/meson.build | 3 +- scipy/sparse/csgraph/tests/meson.build | 3 +- scipy/sparse/linalg/_dsolve/tests/meson.build | 3 +- .../linalg/_eigen/arpack/tests/meson.build | 3 +- .../linalg/_eigen/lobpcg/tests/meson.build | 3 +- scipy/sparse/linalg/_eigen/tests/meson.build | 3 +- scipy/sparse/linalg/_isolve/tests/meson.build | 3 +- scipy/sparse/linalg/tests/meson.build | 3 +- scipy/sparse/tests/meson.build | 6 ++- scipy/spatial/tests/meson.build | 6 ++- scipy/spatial/transform/tests/meson.build | 3 +- scipy/special/meson.build | 12 ++++-- scipy/special/tests/meson.build | 9 ++-- .../stats/tests/data/levy_stable/meson.build | 3 +- scipy/stats/tests/data/meson.build | 3 +- scipy/stats/tests/data/nist_anova/meson.build | 3 +- .../tests/data/nist_linregress/meson.build | 3 +- scipy/stats/tests/meson.build | 3 +- scipy/stats/tests/test_generation/meson.build | 3 +- tools/check_installation.py | 41 +++++++++++++++---- 48 files changed, 177 insertions(+), 71 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 3cbe99e3a8e8..2c9789aba166 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -114,6 +114,15 @@ jobs: ./check_pyext_symbol_hiding.sh ../build popd + - name: Check usage of install tags + run: | + rm -r ${{ env.INSTALLDIR }} + python dev.py build --tags=runtime,python-runtime,devel + python tools/check_installation.py ${{ env.INSTALLDIR }} --no-tests + rm -r ${{ env.INSTALLDIR }} + python dev.py build --tags=runtime,python-runtime,devel,tests + python tools/check_installation.py ${{ env.INSTALLDIR }} + - name: Mypy if: matrix.python-version == '3.10' run: | @@ -179,7 +188,7 @@ jobs: # Non-isolated build, so we use dependencies installed inside the source tree python -m pip install -U pip # need pip >=23 for `--config-settings` python -m pip install . --no-build-isolation --config-settings=compile-args=-j2 - + # Basic tests cd .. python -c "import scipy" diff --git a/dev.py b/dev.py index 950e2147fd9f..c4d957e96cf4 100644 --- a/dev.py +++ b/dev.py @@ -436,6 +436,10 @@ class Build(Task): help=("If set, use `Accelerate` as the BLAS/LAPACK to build against." " Takes precedence over -with-scipy-openblas (macOS only)") ) + tags = Option( + ['--tags'], default="runtime,python-runtime,tests,devel", + show_default=True, help="Install tags to be used by meson." + ) @classmethod def setup_build(cls, dirs, args): @@ -544,7 +548,8 @@ def install_project(cls, dirs, args): if non_empty and not dirs.site.exists(): raise RuntimeError("Can't install in non-empty directory: " f"'{dirs.installed}'") - cmd = ["meson", "install", "-C", args.build_dir, "--only-changed"] + cmd = ["meson", "install", "-C", args.build_dir, + "--only-changed", "--tags", args.tags] log_filename = dirs.root / 'meson-install.log' start_time = datetime.datetime.now() cmd_str = ' '.join([str(p) for p in cmd]) diff --git a/scipy/_lib/meson.build b/scipy/_lib/meson.build index e8bea4cdcc47..fe1252bd3f94 100644 --- a/scipy/_lib/meson.build +++ b/scipy/_lib/meson.build @@ -45,7 +45,8 @@ py3.extension_module('_test_ccallback', include_directories: 'src', link_args: version_link_args, install: true, - subdir: 'scipy/_lib' + subdir: 'scipy/_lib', + install_tag: 'tests' ) py3.extension_module('_fpumode', @@ -62,7 +63,8 @@ py3.extension_module('_test_deprecation_call', include_directories: 'src', link_args: version_link_args, install: true, - subdir: 'scipy/_lib' + subdir: 'scipy/_lib', + install_tag: 'tests' ) py3.extension_module('_test_deprecation_def', @@ -71,7 +73,8 @@ py3.extension_module('_test_deprecation_def', include_directories: 'src', link_args: version_link_args, install: true, - subdir: 'scipy/_lib' + subdir: 'scipy/_lib', + install_tag: 'tests' ) # May be easier as a compile flag, but use a config header file to stay diff --git a/scipy/_lib/tests/meson.build b/scipy/_lib/tests/meson.build index 64c05741288c..410249032667 100644 --- a/scipy/_lib/tests/meson.build +++ b/scipy/_lib/tests/meson.build @@ -18,5 +18,6 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/_lib/tests' + subdir: 'scipy/_lib/tests', + install_tag: 'tests' ) diff --git a/scipy/cluster/tests/meson.build b/scipy/cluster/tests/meson.build index 25b887549af0..a6efdb1136f9 100644 --- a/scipy/cluster/tests/meson.build +++ b/scipy/cluster/tests/meson.build @@ -9,5 +9,6 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/cluster/tests' + subdir: 'scipy/cluster/tests', + install_tag: 'tests' ) diff --git a/scipy/constants/tests/meson.build b/scipy/constants/tests/meson.build index ecc59aa632b1..9abea4141d7c 100644 --- a/scipy/constants/tests/meson.build +++ b/scipy/constants/tests/meson.build @@ -6,5 +6,6 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/constants/tests' + subdir: 'scipy/constants/tests', + install_tag: 'tests' ) diff --git a/scipy/datasets/tests/meson.build b/scipy/datasets/tests/meson.build index a1e4281bdfaa..b50392a661f8 100644 --- a/scipy/datasets/tests/meson.build +++ b/scipy/datasets/tests/meson.build @@ -5,5 +5,6 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/datasets/tests' + subdir: 'scipy/datasets/tests', + install_tag: 'tests' ) diff --git a/scipy/fft/_pocketfft/tests/meson.build b/scipy/fft/_pocketfft/tests/meson.build index ae173f6a0904..4e0578538e22 100644 --- a/scipy/fft/_pocketfft/tests/meson.build +++ b/scipy/fft/_pocketfft/tests/meson.build @@ -6,5 +6,6 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/fft/_pocketfft/tests' + subdir: 'scipy/fft/_pocketfft/tests', + install_tag: 'tests' ) diff --git a/scipy/fft/tests/meson.build b/scipy/fft/tests/meson.build index c3b771cf0b2e..4d04310cd5d0 100644 --- a/scipy/fft/tests/meson.build +++ b/scipy/fft/tests/meson.build @@ -11,5 +11,6 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/fft/tests' + subdir: 'scipy/fft/tests', + install_tag: 'tests' ) diff --git a/scipy/fftpack/tests/meson.build b/scipy/fftpack/tests/meson.build index ecb808dbddf0..a93aead1bba1 100644 --- a/scipy/fftpack/tests/meson.build +++ b/scipy/fftpack/tests/meson.build @@ -16,5 +16,6 @@ test_sources = [ py3.install_sources( [python_sources, test_sources], - subdir: 'scipy/fftpack/tests' + subdir: 'scipy/fftpack/tests', + install_tag: 'tests' ) diff --git a/scipy/integrate/_ivp/tests/meson.build b/scipy/integrate/_ivp/tests/meson.build index 0897d0501d5c..9d0239ea4aa4 100644 --- a/scipy/integrate/_ivp/tests/meson.build +++ b/scipy/integrate/_ivp/tests/meson.build @@ -3,5 +3,6 @@ py3.install_sources([ 'test_ivp.py', 'test_rk.py' ], - subdir: 'scipy/integrate/_ivp/tests' + subdir: 'scipy/integrate/_ivp/tests', + install_tag: 'tests' ) diff --git a/scipy/integrate/meson.build b/scipy/integrate/meson.build index 1a0edfc724d3..23a715dd5862 100644 --- a/scipy/integrate/meson.build +++ b/scipy/integrate/meson.build @@ -165,7 +165,8 @@ py3.extension_module('_test_multivariate', [quadpack_test_src], link_args: version_link_args, install: true, - subdir: 'scipy/integrate' + subdir: 'scipy/integrate', + install_tag: 'tests' ) py3.extension_module('_test_odeint_banded', @@ -176,7 +177,8 @@ py3.extension_module('_test_odeint_banded', dependencies: [lapack_dep, fortranobject_dep], install: true, link_language: 'fortran', - subdir: 'scipy/integrate' + subdir: 'scipy/integrate', + install_tag: 'tests' ) subdir('_ivp') diff --git a/scipy/integrate/tests/meson.build b/scipy/integrate/tests/meson.build index a0829eb07878..dfec4bc2c0c6 100644 --- a/scipy/integrate/tests/meson.build +++ b/scipy/integrate/tests/meson.build @@ -9,5 +9,6 @@ py3.install_sources([ 'test_quadrature.py', 'test_tanhsinh.py' ], - subdir: 'scipy/integrate/tests' + subdir: 'scipy/integrate/tests', + install_tag: 'tests' ) diff --git a/scipy/interpolate/tests/meson.build b/scipy/interpolate/tests/meson.build index 25fe2cbe13ec..98d9104ae192 100644 --- a/scipy/interpolate/tests/meson.build +++ b/scipy/interpolate/tests/meson.build @@ -10,10 +10,11 @@ py3.install_sources([ 'test_pade.py', 'test_polyint.py', 'test_rbf.py', - 'test_rbfinterp.py', + 'test_rbfinterp.py', 'test_rgi.py' ], - subdir: 'scipy/interpolate/tests' + subdir: 'scipy/interpolate/tests', + install_tag: 'tests' ) py3.install_sources([ @@ -21,5 +22,6 @@ py3.install_sources([ 'data/estimate_gradients_hang.npy', 'data/gcvspl.npz' ], - subdir: 'scipy/interpolate/tests/data' + subdir: 'scipy/interpolate/tests/data', + install_tag: 'tests' ) diff --git a/scipy/io/_harwell_boeing/tests/meson.build b/scipy/io/_harwell_boeing/tests/meson.build index fc7662749ffe..a92460d008f6 100644 --- a/scipy/io/_harwell_boeing/tests/meson.build +++ b/scipy/io/_harwell_boeing/tests/meson.build @@ -3,5 +3,6 @@ py3.install_sources([ 'test_fortran_format.py', 'test_hb.py' ], - subdir: 'scipy/io/_harwell_boeing/tests' + subdir: 'scipy/io/_harwell_boeing/tests', + install_tag: 'tests' ) diff --git a/scipy/io/arff/tests/meson.build b/scipy/io/arff/tests/meson.build index b904b802bdf8..f69e37cfdf6e 100644 --- a/scipy/io/arff/tests/meson.build +++ b/scipy/io/arff/tests/meson.build @@ -2,7 +2,8 @@ py3.install_sources([ '__init__.py', 'test_arffread.py' ], - subdir: 'scipy/io/arff/tests' + subdir: 'scipy/io/arff/tests', + install_tag: 'tests' ) py3.install_sources([ @@ -23,5 +24,6 @@ py3.install_sources([ 'data/test8.arff', 'data/test9.arff' ], - subdir: 'scipy/io/arff/tests/data' + subdir: 'scipy/io/arff/tests/data', + install_tag: 'tests' ) diff --git a/scipy/io/matlab/tests/meson.build b/scipy/io/matlab/tests/meson.build index a100259efbc7..5c3bd708289a 100644 --- a/scipy/io/matlab/tests/meson.build +++ b/scipy/io/matlab/tests/meson.build @@ -9,7 +9,8 @@ py3.install_sources([ 'test_pathological.py', 'test_streams.py' ], - subdir: 'scipy/io/matlab/tests' + subdir: 'scipy/io/matlab/tests', + install_tag: 'tests' ) py3.install_sources([ @@ -124,5 +125,6 @@ py3.install_sources([ 'data/testunicode_7.4_GLNX86.mat', 'data/testvec_4_GLNX86.mat' ], - subdir: 'scipy/io/matlab/tests/data' + subdir: 'scipy/io/matlab/tests/data', + install_tag: 'tests' ) diff --git a/scipy/io/meson.build b/scipy/io/meson.build index 94ad2d289222..60f71c69684a 100644 --- a/scipy/io/meson.build +++ b/scipy/io/meson.build @@ -9,7 +9,8 @@ py3.extension_module('_test_fortran', dependencies: [lapack_dep, fortranobject_dep], install: true, link_language: 'fortran', - subdir: 'scipy/io' + subdir: 'scipy/io', + install_tag: 'tests' ) py3.install_sources([ diff --git a/scipy/io/tests/meson.build b/scipy/io/tests/meson.build index 5e191f2bf8b7..5caa5a64f72a 100644 --- a/scipy/io/tests/meson.build +++ b/scipy/io/tests/meson.build @@ -7,7 +7,8 @@ py3.install_sources([ 'test_paths.py', 'test_wavfile.py' ], - subdir: 'scipy/io/tests' + subdir: 'scipy/io/tests', + install_tag: 'tests' ) py3.install_sources([ @@ -97,5 +98,6 @@ py3.install_sources([ 'data/test-8000Hz-le-5ch-9S-5bit.wav', 'data/various_compressed.sav' ], - subdir: 'scipy/io/tests/data' + subdir: 'scipy/io/tests/data', + install_tag: 'tests' ) diff --git a/scipy/linalg/tests/data/meson.build b/scipy/linalg/tests/data/meson.build index ff22c5a2321e..47ef5e2fee82 100644 --- a/scipy/linalg/tests/data/meson.build +++ b/scipy/linalg/tests/data/meson.build @@ -10,5 +10,6 @@ npz_files = [ py3.install_sources( npz_files, - subdir: 'scipy/linalg/tests/data' + subdir: 'scipy/linalg/tests/data', + install_tag: 'tests' ) diff --git a/scipy/linalg/tests/meson.build b/scipy/linalg/tests/meson.build index d94d82c11a68..16763a7c0853 100644 --- a/scipy/linalg/tests/meson.build +++ b/scipy/linalg/tests/meson.build @@ -28,12 +28,14 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/linalg/tests' + subdir: 'scipy/linalg/tests', + install_tag: 'tests' ) py3.install_sources( ['_cython_examples/extending.pyx', '_cython_examples/meson.build'], - subdir: 'scipy/linalg/tests/_cython_examples' + subdir: 'scipy/linalg/tests/_cython_examples', + install_tag: 'tests' ) subdir('data') diff --git a/scipy/meson.build b/scipy/meson.build index 0c4c95f220e9..a3c331ef0943 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -563,6 +563,7 @@ lapack_dep = declare_dependency( dependencies: lapack_dep, link_with: [g77_abi_wrappers, blas_lapack_wrapper_lib] ) + subdir('_lib') subdir('special') subdir('linalg') diff --git a/scipy/misc/tests/meson.build b/scipy/misc/tests/meson.build index e6281c7348f6..8b799c58a321 100644 --- a/scipy/misc/tests/meson.build +++ b/scipy/misc/tests/meson.build @@ -7,5 +7,6 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/misc/tests' + subdir: 'scipy/misc/tests', + install_tag: 'tests' ) diff --git a/scipy/ndimage/meson.build b/scipy/ndimage/meson.build index ac8a1d9ba0e7..6a631f554559 100644 --- a/scipy/ndimage/meson.build +++ b/scipy/ndimage/meson.build @@ -30,7 +30,8 @@ py3.extension_module('_ctest', dependencies: np_dep, link_args: version_link_args, install: true, - subdir: 'scipy/ndimage' + subdir: 'scipy/ndimage', + install_tag: 'tests' ) py3.extension_module('_cytest', @@ -39,7 +40,8 @@ py3.extension_module('_cytest', dependencies: np_dep, link_args: version_link_args, install: true, - subdir: 'scipy/ndimage' + subdir: 'scipy/ndimage', + install_tag: 'tests' ) diff --git a/scipy/ndimage/tests/meson.build b/scipy/ndimage/tests/meson.build index 6b9939677709..2b989fe9e2d4 100644 --- a/scipy/ndimage/tests/meson.build +++ b/scipy/ndimage/tests/meson.build @@ -14,7 +14,8 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/ndimage/tests' + subdir: 'scipy/ndimage/tests', + install_tag: 'tests' ) py3.install_sources([ @@ -22,5 +23,6 @@ py3.install_sources([ 'data/label_results.txt', 'data/label_strels.txt', ], - subdir: 'scipy/ndimage/tests/data' + subdir: 'scipy/ndimage/tests/data', + install_tag: 'tests' ) diff --git a/scipy/odr/tests/meson.build b/scipy/odr/tests/meson.build index 4fe0809d0258..5ba9df7bd4f6 100644 --- a/scipy/odr/tests/meson.build +++ b/scipy/odr/tests/meson.build @@ -5,5 +5,6 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/odr/tests' + subdir: 'scipy/odr/tests', + install_tag: 'tests' ) diff --git a/scipy/optimize/_trustregion_constr/tests/meson.build b/scipy/optimize/_trustregion_constr/tests/meson.build index 7c3aa108cc7d..629dc332d096 100644 --- a/scipy/optimize/_trustregion_constr/tests/meson.build +++ b/scipy/optimize/_trustregion_constr/tests/meson.build @@ -5,5 +5,6 @@ py3.install_sources([ 'test_qp_subproblem.py', 'test_report.py' ], - subdir: 'scipy/optimize/_trustregion_constr/tests' + subdir: 'scipy/optimize/_trustregion_constr/tests', + install_tag: 'tests' ) diff --git a/scipy/optimize/tests/meson.build b/scipy/optimize/tests/meson.build index 9574f577a645..0e920366a6d1 100644 --- a/scipy/optimize/tests/meson.build +++ b/scipy/optimize/tests/meson.build @@ -45,10 +45,12 @@ py3.install_sources([ 'test_trustregion_krylov.py', 'test_zeros.py' ], - subdir: 'scipy/optimize/tests' + subdir: 'scipy/optimize/tests', + install_tag: 'tests' ) py3.install_sources( ['_cython_examples/extending.pyx', '_cython_examples/meson.build'], - subdir: 'scipy/optimize/tests/_cython_examples' + subdir: 'scipy/optimize/tests/_cython_examples', + install_tag: 'tests' ) diff --git a/scipy/signal/tests/meson.build b/scipy/signal/tests/meson.build index bff448fafab8..28f45e142482 100644 --- a/scipy/signal/tests/meson.build +++ b/scipy/signal/tests/meson.build @@ -23,5 +23,6 @@ py3.install_sources([ 'test_wavelets.py', 'test_windows.py' ], - subdir: 'scipy/signal/tests' + subdir: 'scipy/signal/tests', + install_tag: 'tests' ) diff --git a/scipy/sparse/csgraph/tests/meson.build b/scipy/sparse/csgraph/tests/meson.build index 3b8ea7ad32e9..7ad02ba3b26a 100644 --- a/scipy/sparse/csgraph/tests/meson.build +++ b/scipy/sparse/csgraph/tests/meson.build @@ -15,5 +15,6 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/sparse/csgraph/tests' + subdir: 'scipy/sparse/csgraph/tests', + install_tag: 'tests' ) diff --git a/scipy/sparse/linalg/_dsolve/tests/meson.build b/scipy/sparse/linalg/_dsolve/tests/meson.build index 2e8cb0fe857e..f7d812a3629a 100644 --- a/scipy/sparse/linalg/_dsolve/tests/meson.build +++ b/scipy/sparse/linalg/_dsolve/tests/meson.build @@ -2,5 +2,6 @@ py3.install_sources([ '__init__.py', 'test_linsolve.py' ], - subdir: 'scipy/sparse/linalg/_dsolve/tests' + subdir: 'scipy/sparse/linalg/_dsolve/tests', + install_tag: 'tests' ) diff --git a/scipy/sparse/linalg/_eigen/arpack/tests/meson.build b/scipy/sparse/linalg/_eigen/arpack/tests/meson.build index 3c5fb0ca64ce..e9ede03578ce 100644 --- a/scipy/sparse/linalg/_eigen/arpack/tests/meson.build +++ b/scipy/sparse/linalg/_eigen/arpack/tests/meson.build @@ -2,5 +2,6 @@ py3.install_sources([ '__init__.py', 'test_arpack.py' ], - subdir: 'scipy/sparse/linalg/_eigen/arpack/tests' + subdir: 'scipy/sparse/linalg/_eigen/arpack/tests', + install_tag: 'tests' ) diff --git a/scipy/sparse/linalg/_eigen/lobpcg/tests/meson.build b/scipy/sparse/linalg/_eigen/lobpcg/tests/meson.build index b04c572a4fcc..2e9ad65c0bdb 100644 --- a/scipy/sparse/linalg/_eigen/lobpcg/tests/meson.build +++ b/scipy/sparse/linalg/_eigen/lobpcg/tests/meson.build @@ -2,5 +2,6 @@ py3.install_sources([ '__init__.py', 'test_lobpcg.py' ], - subdir: 'scipy/sparse/linalg/_eigen/lobpcg/tests' + subdir: 'scipy/sparse/linalg/_eigen/lobpcg/tests', + install_tag: 'tests' ) diff --git a/scipy/sparse/linalg/_eigen/tests/meson.build b/scipy/sparse/linalg/_eigen/tests/meson.build index 9f54032514ae..3b4a07b7be26 100644 --- a/scipy/sparse/linalg/_eigen/tests/meson.build +++ b/scipy/sparse/linalg/_eigen/tests/meson.build @@ -2,5 +2,6 @@ py3.install_sources([ '__init__.py', 'test_svds.py' ], - subdir: 'scipy/sparse/linalg/_eigen/tests' + subdir: 'scipy/sparse/linalg/_eigen/tests', + install_tag: 'tests' ) diff --git a/scipy/sparse/linalg/_isolve/tests/meson.build b/scipy/sparse/linalg/_isolve/tests/meson.build index 94357b10904a..00ec906675c5 100644 --- a/scipy/sparse/linalg/_isolve/tests/meson.build +++ b/scipy/sparse/linalg/_isolve/tests/meson.build @@ -8,5 +8,6 @@ py3.install_sources([ 'test_minres.py', 'test_utils.py' ], - subdir: 'scipy/sparse/linalg/_isolve/tests' + subdir: 'scipy/sparse/linalg/_isolve/tests', + install_tag: 'tests' ) diff --git a/scipy/sparse/linalg/tests/meson.build b/scipy/sparse/linalg/tests/meson.build index 3a52ee836df4..ca7458878fb0 100644 --- a/scipy/sparse/linalg/tests/meson.build +++ b/scipy/sparse/linalg/tests/meson.build @@ -10,5 +10,6 @@ py3.install_sources([ 'test_pydata_sparse.py', 'test_special_sparse_arrays.py' ], - subdir: 'scipy/sparse/linalg/tests' + subdir: 'scipy/sparse/linalg/tests', + install_tag: 'tests' ) diff --git a/scipy/sparse/tests/meson.build b/scipy/sparse/tests/meson.build index 17e86ff53345..06d9353cdc43 100644 --- a/scipy/sparse/tests/meson.build +++ b/scipy/sparse/tests/meson.build @@ -20,7 +20,8 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/sparse/tests' + subdir: 'scipy/sparse/tests', + install_tag: 'tests' ) data_sources = [ @@ -30,5 +31,6 @@ data_sources = [ py3.install_sources( data_sources, - subdir: 'scipy/sparse/tests/data' + subdir: 'scipy/sparse/tests/data', + install_tag: 'tests' ) diff --git a/scipy/spatial/tests/meson.build b/scipy/spatial/tests/meson.build index 3b51f58eb738..46b83a62888a 100644 --- a/scipy/spatial/tests/meson.build +++ b/scipy/spatial/tests/meson.build @@ -9,7 +9,8 @@ py3.install_sources([ 'test_slerp.py', 'test_spherical_voronoi.py' ], - subdir: 'scipy/spatial/tests' + subdir: 'scipy/spatial/tests', + install_tag: 'tests' ) py3.install_sources([ @@ -45,5 +46,6 @@ py3.install_sources([ 'data/random-uint-data.txt', 'data/selfdual-4d-polytope.txt' ], - subdir: 'scipy/spatial/tests/data' + subdir: 'scipy/spatial/tests/data', + install_tag: 'tests' ) diff --git a/scipy/spatial/transform/tests/meson.build b/scipy/spatial/transform/tests/meson.build index d8d212665f40..da3c100ffa87 100644 --- a/scipy/spatial/transform/tests/meson.build +++ b/scipy/spatial/transform/tests/meson.build @@ -4,5 +4,6 @@ py3.install_sources([ 'test_rotation_groups.py', 'test_rotation_spline.py' ], - subdir: 'scipy/spatial/transform/tests' + subdir: 'scipy/spatial/transform/tests', + install_tag: 'tests' ) diff --git a/scipy/special/meson.build b/scipy/special/meson.build index 9456b72846fe..a52ec928f616 100644 --- a/scipy/special/meson.build +++ b/scipy/special/meson.build @@ -231,7 +231,14 @@ py3.extension_module('_test_internal', dependencies: [np_dep], link_args: version_link_args, install: true, - subdir: 'scipy/special' + subdir: 'scipy/special', + install_tag: 'tests' +) + +py3.install_sources( + '_test_internal.pyi', + subdir: 'scipy/special', + install_tag: 'tests' ) # Must use `custom_target`, because `py3.install_sources` does not work with @@ -267,7 +274,7 @@ foreach npz_file: npz_files ], install: true, install_dir: py3.get_install_dir() / 'scipy/special/tests/data', - install_tag: 'python-runtime', + install_tag: 'tests', ) endforeach @@ -340,7 +347,6 @@ python_sources = [ '_spfun_stats.py', '_spherical_bessel.py', '_support_alternative_backends.py', - '_test_internal.pyi', '_testutils.py', '_ufuncs.pyi', 'add_newdocs.py', diff --git a/scipy/special/tests/meson.build b/scipy/special/tests/meson.build index 7a803fc8663c..4a163b043fd6 100644 --- a/scipy/special/tests/meson.build +++ b/scipy/special/tests/meson.build @@ -59,15 +59,18 @@ python_sources = [ py3.install_sources( python_sources, - subdir: 'scipy/special/tests' + subdir: 'scipy/special/tests', + install_tag: 'tests' ) py3.install_sources( 'data/__init__.py', - subdir: 'scipy/special/tests/data' + subdir: 'scipy/special/tests/data', + install_tag: 'tests' ) py3.install_sources( ['_cython_examples/extending.pyx', '_cython_examples/meson.build'], - subdir: 'scipy/special/tests/_cython_examples' + subdir: 'scipy/special/tests/_cython_examples', + install_tag: 'tests' ) diff --git a/scipy/stats/tests/data/levy_stable/meson.build b/scipy/stats/tests/data/levy_stable/meson.build index 10a4266f5de4..820c860b6697 100644 --- a/scipy/stats/tests/data/levy_stable/meson.build +++ b/scipy/stats/tests/data/levy_stable/meson.build @@ -3,5 +3,6 @@ py3.install_sources([ 'stable-Z1-cdf-sample-data.npy', 'stable-Z1-pdf-sample-data.npy' ], - subdir: 'scipy/stats/tests/data/levy_stable' + subdir: 'scipy/stats/tests/data/levy_stable', + install_tag: 'tests' ) diff --git a/scipy/stats/tests/data/meson.build b/scipy/stats/tests/data/meson.build index 92bce95483da..5dec43e61ad5 100644 --- a/scipy/stats/tests/data/meson.build +++ b/scipy/stats/tests/data/meson.build @@ -5,7 +5,8 @@ py3.install_sources([ 'rel_breitwigner_pdf_sample_data_ROOT.npy', 'studentized_range_mpmath_ref.json' ], - subdir: 'scipy/stats/tests/data' + subdir: 'scipy/stats/tests/data', + install_tag: 'tests' ) subdir('levy_stable') diff --git a/scipy/stats/tests/data/nist_anova/meson.build b/scipy/stats/tests/data/nist_anova/meson.build index a8b532c9cc32..f9927725b3d7 100644 --- a/scipy/stats/tests/data/nist_anova/meson.build +++ b/scipy/stats/tests/data/nist_anova/meson.build @@ -11,5 +11,6 @@ py3.install_sources([ 'SmLs08.dat', 'SmLs09.dat' ], - subdir: 'scipy/stats/tests/data/nist_anova' + subdir: 'scipy/stats/tests/data/nist_anova', + install_tag: 'tests' ) diff --git a/scipy/stats/tests/data/nist_linregress/meson.build b/scipy/stats/tests/data/nist_linregress/meson.build index 0ca88ae33ee9..3d7828c4bdeb 100644 --- a/scipy/stats/tests/data/nist_linregress/meson.build +++ b/scipy/stats/tests/data/nist_linregress/meson.build @@ -1,5 +1,6 @@ py3.install_sources([ 'Norris.dat' ], - subdir: 'scipy/stats/tests/data/nist_linregress' + subdir: 'scipy/stats/tests/data/nist_linregress', + install_tag: 'tests' ) diff --git a/scipy/stats/tests/meson.build b/scipy/stats/tests/meson.build index e37173820483..39564cf74c0d 100644 --- a/scipy/stats/tests/meson.build +++ b/scipy/stats/tests/meson.build @@ -33,7 +33,8 @@ py3.install_sources([ 'test_tukeylambda_stats.py', 'test_variation.py' ], - subdir: 'scipy/stats/tests' + subdir: 'scipy/stats/tests', + install_tag: 'tests' ) subdir('data') diff --git a/scipy/stats/tests/test_generation/meson.build b/scipy/stats/tests/test_generation/meson.build index 001e5faa4541..f538776cbaf3 100644 --- a/scipy/stats/tests/test_generation/meson.build +++ b/scipy/stats/tests/test_generation/meson.build @@ -2,5 +2,6 @@ py3.install_sources([ 'generate_fisher_exact_results_from_r.R', 'studentized_range_mpmath_ref.py' ], - subdir: 'scipy/stats/tests/test_generation' + subdir: 'scipy/stats/tests/test_generation', + install_tag: 'tests' ) diff --git a/tools/check_installation.py b/tools/check_installation.py index eb589e1e7d7b..7afd435caf8f 100644 --- a/tools/check_installation.py +++ b/tools/check_installation.py @@ -54,25 +54,46 @@ ] -def main(install_dir): +def main(install_dir, no_tests): INSTALLED_DIR = os.path.join(ROOT_DIR, install_dir) if not os.path.exists(INSTALLED_DIR): raise ValueError(f"Provided install dir {INSTALLED_DIR} does not exist") scipy_test_files = get_test_files(SCIPY_DIR) + scipy_test_extension_modules = get_test_files(INSTALLED_DIR, "so") installed_test_files = get_test_files(INSTALLED_DIR) + if no_tests: + if len(scipy_test_extension_modules) > 0: + raise Exception(f"{scipy_test_extension_modules.values()} " + "should not be installed but " + "are found in the installation directory.") + else: + if len(scipy_test_extension_modules) == 0: + raise Exception("Test for extension modules should be " + "installed but are not found in the " + "installation directory.") + # Check test files detected in repo are installed for test_file in scipy_test_files.keys(): if test_file in exception_list_test_files: continue + if no_tests: + if test_file in installed_test_files: + raise Exception(f"{test_file} should not be installed but " + "is found in the installation directory.") + continue + if test_file not in installed_test_files.keys(): raise Exception(f"{scipy_test_files[test_file]} is not installed; " f"either install it or add `{test_file}` to the " "exception list in `tools/check_installation.py`") - print("----------- All the test files were installed --------------") + if no_tests: + print("----------- No test files were installed --------------") + else: + print("----------- All the test files were installed --------------") scipy_pyi_files = get_pyi_files(SCIPY_DIR) installed_pyi_files = get_pyi_files(INSTALLED_DIR) @@ -80,9 +101,11 @@ def main(install_dir): # Check *.pyi files detected in repo are installed for pyi_file in scipy_pyi_files.keys(): if pyi_file not in installed_pyi_files.keys(): + if no_tests and "test" in scipy_pyi_files[pyi_file]: + continue raise Exception("%s is not installed" % scipy_pyi_files[pyi_file]) - print("----------- All the .pyi files were installed --------------") + print("----------- All the necessary .pyi files were installed --------------") def get_suffix_path(current_path, levels=1): @@ -93,9 +116,10 @@ def get_suffix_path(current_path, levels=1): return os.path.relpath(current_path, current_new) -def get_test_files(dir): +def get_test_files(dir, ext="py"): test_files = dict() - for path in glob.glob(f'{dir}/**/test_*.py', recursive=True): + underscore = "_" if ext == "so" else "" + for path in glob.glob(f'{dir}/**/{underscore}test_*.{ext}', recursive=True): suffix_path = get_suffix_path(path, 3) suffix_path = changed_installed_path.get(suffix_path, suffix_path) if "highspy" not in suffix_path: @@ -114,9 +138,12 @@ def get_pyi_files(dir): if __name__ == '__main__': - if not len(sys.argv) == 2: + if len(sys.argv) < 2: raise ValueError("Incorrect number of input arguments, need " "check_installation.py relpath/to/installed/scipy") install_dir = sys.argv[1] - main(install_dir) + no_tests = False + if len(sys.argv) == 3: + no_tests = sys.argv[2] == "--no-tests" + main(install_dir, no_tests) From c2fccd3e09d60bc1b35d2bdec979bcc330eaaff1 Mon Sep 17 00:00:00 2001 From: fancidev Date: Wed, 22 May 2024 04:24:01 +0800 Subject: [PATCH 231/500] DOC: stats.{circmean, circvar, circstd}: improve accuracy/clarity (#20726) --- scipy/stats/_morestats.py | 135 +++++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 47 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index f777d6be0ffc..9dc263099fa3 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -4382,30 +4382,52 @@ def _circfuncs_common(samples, high, low, xp=None): result_to_tuple=lambda x: (x,) ) def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): - """Compute the circular mean for samples in a range. + 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. - high : float or int, optional - High boundary for the sample range. Default is ``2*pi``. - low : float or int, optional - Low boundary for the sample range. Default is 0. + 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. + 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 simplicity, all angles are printed out in degrees. + For readability, all angles are printed out in degrees. >>> import numpy as np >>> from scipy.stats import circmean @@ -4448,21 +4470,36 @@ def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): result_to_tuple=lambda x: (x,) ) def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): - """Compute the circular variance for samples assumed to be in a range. + 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. - high : float or int, optional - High boundary for the sample range. Default is ``2*pi``. - low : float or int, optional - Low boundary for the sample range. Default is 0. + 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. + 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 -------- @@ -4471,16 +4508,15 @@ def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): Notes ----- - This uses the following definition of circular variance: ``1-R``, where - ``R`` is the mean resultant vector. The - returned value is in the range [0, 1], 0 standing for no variance, and 1 - for a large variance. In the limit of small angles, this value is similar - to half the 'linear' variance. + 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. + University Press, 1993. + .. [2] Mardia, K. V. and Jupp, P. E. *Directional Statistics*. + John Wiley & Sons, 1999. Examples -------- @@ -4529,27 +4565,42 @@ def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): ) def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, normalize=False): - """ - Compute the circular standard deviation for samples assumed to be in the - range [low to high]. + 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. - high : float or int, optional - High boundary for the sample range. Default is ``2*pi``. - low : float or int, optional - Low boundary for the sample range. Default is 0. + 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``. normalize : boolean, optional - If True, the returned value is equal to ``sqrt(-2*log(R))`` and does - not depend on the variable units. If False (default), the returned - value is scaled by ``((high-low)/(2*pi))``. + 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. + Circular standard deviation, optionally normalized. + + If the input array is empty, ``np.nan`` is returned. See Also -------- @@ -4558,25 +4609,15 @@ def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, Notes ----- - This uses a definition of circular standard deviation from [1]_. - Essentially, the calculation is as follows. - - .. code-block:: python - - import numpy as np - C = np.cos(samples).mean() - S = np.sin(samples).mean() - R = np.sqrt(C**2 + S**2) - l = 2*np.pi / (high-low) - circstd = np.sqrt(-2*np.log(R)) / l - - In the limit of small angles, it returns a number close to the 'linear' - standard deviation. + 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 -------- From c106aa5b7e283d58dd63ecf77bb5ccbb8d7cfc3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mendes?= Date: Wed, 22 May 2024 13:56:42 +0100 Subject: [PATCH 232/500] BUG: interpolate: fix high memory usage for 2 classes (#20404) BUG: fix high memory usage in LinearNDInterpolator and `CloughTocher2DInterpolator` (#20357) Reviewed at https://github.com/scipy/scipy/pull/20404, see also discussion in https://github.com/scipy/scipy/issues/20357 --- benchmarks/benchmarks/interpolate.py | 35 ++++++++++++--- scipy/interpolate/interpnd.pyx | 66 +++++++++++----------------- 2 files changed, 55 insertions(+), 46 deletions(-) diff --git a/benchmarks/benchmarks/interpolate.py b/benchmarks/benchmarks/interpolate.py index 4c531361c9f1..a2c85d282d60 100644 --- a/benchmarks/benchmarks/interpolate.py +++ b/benchmarks/benchmarks/interpolate.py @@ -8,6 +8,8 @@ with safe_import(): import scipy.interpolate as interpolate +with safe_import(): + from scipy.sparse import csr_matrix class Leaks(Benchmark): unit = "relative increase with repeats" @@ -83,7 +85,34 @@ def setup(self, n_grids, method): def time_evaluation(self, n_grids, method): interpolate.griddata(self.points, self.values, (self.grid_x, self.grid_y), method=method) + +class GridDataPeakMem(Benchmark): + """ + Benchmark based on https://github.com/scipy/scipy/issues/20357 + """ + def setup(self): + shape = (7395, 6408) + num_nonzero = 488686 + + rng = np.random.default_rng(1234) + random_rows = rng.integers(0, shape[0], num_nonzero) + random_cols = rng.integers(0, shape[1], num_nonzero) + + random_values = rng.random(num_nonzero, dtype=np.float32) + + sparse_matrix = csr_matrix((random_values, (random_rows, random_cols)), + shape=shape, dtype=np.float32) + sparse_matrix = sparse_matrix.toarray() + + self.coords = np.column_stack(np.nonzero(sparse_matrix)) + self.values = sparse_matrix[self.coords[:, 0], self.coords[:, 1]] + self.grid_x, self.grid_y = np.mgrid[0:sparse_matrix.shape[0], + 0:sparse_matrix.shape[1]] + + def peakmem_griddata(self): + interpolate.griddata(self.coords, self.values, (self.grid_x, self.grid_y), + method='cubic') class Interpolate1d(Benchmark): param_names = ['n_samples', 'method'] @@ -424,9 +453,6 @@ def __init__(self, points, xi, tol=1e-6, maxiter=400, **kwargs): tol=tol, maxiter=maxiter) self.xi = None self._preprocess_xi(*xi) - self.simplices, self.c = ( - interpolate.CloughTocher2DInterpolator._find_simplicies(self, self.xi) - ) def _preprocess_xi(self, *args): if self.xi is None: @@ -435,9 +461,6 @@ def _preprocess_xi(self, *args): ) return self.xi, self.interpolation_points_shape - def _find_simplicies(self, xi): - return self.simplices, self.c - def __call__(self, values): self._set_values(values) return super().__call__(self.xi) diff --git a/scipy/interpolate/interpnd.pyx b/scipy/interpolate/interpnd.pyx index 0be480522b3f..61e0e716e071 100644 --- a/scipy/interpolate/interpnd.pyx +++ b/scipy/interpolate/interpnd.pyx @@ -146,31 +146,6 @@ class NDInterpolatorBase: xi = np.ascontiguousarray(xi, dtype=np.float64) return self._scale_x(xi), interpolation_points_shape - @cython.boundscheck(False) - @cython.wraparound(False) - def _find_simplicies(self, const double[:,::1] xi): - cdef int[:] isimplices - cdef double[:,:] c - cdef qhull.DelaunayInfo_t info - cdef double eps, eps_broad - cdef int start - - qhull._get_delaunay_info(&info, self.tri, 1, 1, 0) - - eps = 100 * DBL_EPSILON - eps_broad = sqrt(eps) - - c = np.zeros((xi.shape[0], NPY_MAXDIMS)) - isimplices = np.zeros(xi.shape[0], np.int32) - with nogil: - for i in range(xi.shape[0]): - # 1) Find the simplex - - isimplices[i] = qhull._find_simplex(&info, &c[i, 0], - &xi[i,0], - &start, eps, eps_broad) - return np.copy(isimplices), np.copy(c) - def __call__(self, *args): """ interpolator(xi) @@ -342,25 +317,32 @@ class LinearNDInterpolator(NDInterpolatorBase): cdef double_or_complex[:,::1] out cdef const int[:,::1] simplices = self.tri.simplices cdef double_or_complex fill_value - cdef int i, j, k, m, ndim, isimplex, nvalues - cdef int[:] isimplices - cdef double[:,:] c - - isimplices,c = self._find_simplicies(xi) - + cdef double c[NPY_MAXDIMS] + cdef int i, j, k, m, ndim, isimplex, start, nvalues + cdef qhull.DelaunayInfo_t info + cdef double eps, eps_broad + ndim = xi.shape[1] fill_value = self.fill_value + qhull._get_delaunay_info(&info, self.tri, 1, 0, 0) + out = np.empty((xi.shape[0], self.values.shape[1]), dtype=self.values.dtype) nvalues = out.shape[1] + start = 0 + eps = 100 * DBL_EPSILON + eps_broad = sqrt(DBL_EPSILON) + with nogil: for i in range(xi.shape[0]): # 1) Find the simplex - isimplex = isimplices[i] + isimplex = qhull._find_simplex(&info, c, + &xi[0,0] + i*ndim, + &start, eps, eps_broad) # 2) Linear barycentric interpolation @@ -376,7 +358,7 @@ class LinearNDInterpolator(NDInterpolatorBase): for j in range(ndim+1): for k in range(nvalues): m = simplices[isimplex,j] - out[i,k] = out[i,k] + c[i, j] * values[m,k] + out[i,k] = out[i,k] + c[j] * values[m,k] return out @@ -980,16 +962,14 @@ class CloughTocher2DInterpolator(NDInterpolatorBase): cdef const double_or_complex[:,:,:] grad = self.grad cdef double_or_complex[:,::1] out cdef const int[:,::1] simplices = self.tri.simplices - cdef double[:,:] c + cdef double c[NPY_MAXDIMS] cdef double_or_complex f[NPY_MAXDIMS+1] cdef double_or_complex df[2*NPY_MAXDIMS+2] cdef double_or_complex w cdef double_or_complex fill_value - cdef int i, j, k, ndim, isimplex, nvalues + cdef int i, j, k, ndim, isimplex, start, nvalues cdef qhull.DelaunayInfo_t info - cdef int[:] isimplices - - isimplices,c = self._find_simplicies(xi) + cdef double eps, eps_broad ndim = xi.shape[1] fill_value = self.fill_value @@ -1000,11 +980,17 @@ class CloughTocher2DInterpolator(NDInterpolatorBase): dtype=self.values.dtype) nvalues = out.shape[1] + start = 0 + eps = 100 * DBL_EPSILON + eps_broad = sqrt(eps) + with nogil: for i in range(xi.shape[0]): # 1) Find the simplex - isimplex = isimplices[i] + isimplex = qhull._find_simplex(&info, c, + &xi[i,0], + &start, eps, eps_broad) # 2) Clough-Tocher interpolation @@ -1020,7 +1006,7 @@ class CloughTocher2DInterpolator(NDInterpolatorBase): df[2*j] = grad[simplices[isimplex,j],k,0] df[2*j+1] = grad[simplices[isimplex,j],k,1] - w = _clough_tocher_2d_single(&info, isimplex, &c[i, 0], f, df) + w = _clough_tocher_2d_single(&info, isimplex, c, f, df) out[i,k] = w return out From 348b1ffcbb7c67e999532ce43219427905dd043b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mendes?= Date: Wed, 22 May 2024 17:23:37 +0100 Subject: [PATCH 233/500] ENH: add bounds parameters to qmc PoissonDisk Co-authored-by: Diogo Pires --- scipy/stats/_qmc.py | 28 ++++++++++++++++++++++------ scipy/stats/tests/test_qmc.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/scipy/stats/_qmc.py b/scipy/stats/_qmc.py index 1aea6a694c8f..e46adb143841 100644 --- a/scipy/stats/_qmc.py +++ b/scipy/stats/_qmc.py @@ -1889,6 +1889,9 @@ class PoissonDisk(QMCEngine): If `seed` is already a ``Generator`` instance, then the provided instance is used. + 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 @@ -1969,7 +1972,9 @@ def __init__( hypersphere: Literal["volume", "surface"] = "volume", ncandidates: IntNumber = 30, optimization: Literal["random-cd", "lloyd"] | None = None, - seed: SeedType = None + seed: 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, @@ -2001,11 +2006,19 @@ def __init__( # 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(np.ones(self.d) / self.cell_size) + np.ceil(self.u_bounds / self.cell_size) ).astype(int) self._initialize_grid_pool() @@ -2025,7 +2038,7 @@ def _initialize_grid_pool(self): def _random( self, n: IntNumber = 1, *, workers: IntNumber = 1 ) -> np.ndarray: - """Draw `n` in the interval ``[0, 1]``. + """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. @@ -2045,7 +2058,10 @@ def _random( return np.empty((n, self.d)) def in_limits(sample: np.ndarray) -> bool: - return (sample.max() <= 1.) and (sample.min() >= 0.) + 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: """ @@ -2053,7 +2069,7 @@ def in_neighborhood(candidate: np.ndarray, n: int = 2) -> bool: `candidate` sample. """ indices = (candidate / self.cell_size).astype(int) - ind_min = np.maximum(indices - n, np.zeros(self.d, dtype=int)) + ind_min = np.maximum(indices - n, self.l_bounds.astype(int)) ind_max = np.minimum(indices + n + 1, self.grid_size) # Check if the center cell is empty @@ -2115,7 +2131,7 @@ def add_sample(candidate: np.ndarray) -> None: return np.array(curr_sample) def fill_space(self) -> np.ndarray: - """Draw ``n`` samples in the interval ``[0, 1]``. + """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 diff --git a/scipy/stats/tests/test_qmc.py b/scipy/stats/tests/test_qmc.py index 968e45c81966..4c79c7c0fed5 100644 --- a/scipy/stats/tests/test_qmc.py +++ b/scipy/stats/tests/test_qmc.py @@ -4,7 +4,8 @@ import pytest import numpy as np -from numpy.testing import assert_allclose, assert_equal, assert_array_equal +from numpy.testing import (assert_allclose, assert_equal, assert_array_equal, + assert_array_less) from scipy.spatial import distance from scipy.stats import shapiro @@ -937,6 +938,36 @@ def test_fill_space(self): # circle packing problem is np complex assert l2_norm(sample) >= radius + def test_sample_inside_bounds(self): + radius = 0.2 + l_bounds=[-1, -2, -1] + u_bounds=[3, 2, 1] + 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) + + def test_inconsistent_bounds(self): + radius = 0.2 + l_bounds=[-1, -2, -1] + u_bounds=[3, 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): From f4406d6d18dc186a64add1654e06ea36e0ba770c Mon Sep 17 00:00:00 2001 From: Diogo Pires Date: Wed, 22 May 2024 19:23:04 +0100 Subject: [PATCH 234/500] TST: add test for valid bounds that don't comply with sample dimension --- scipy/stats/tests/test_qmc.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scipy/stats/tests/test_qmc.py b/scipy/stats/tests/test_qmc.py index 4c79c7c0fed5..43a81adab796 100644 --- a/scipy/stats/tests/test_qmc.py +++ b/scipy/stats/tests/test_qmc.py @@ -960,8 +960,16 @@ def test_inconsistent_bound_value(self): def test_inconsistent_bounds(self): radius = 0.2 - l_bounds=[-1, -2, -1] - u_bounds=[3, 2] + l_bounds = [-1, -2, -1] + u_bounds = [3, 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) + + l_bounds = [-1, -2] + u_bounds = [3, 2] with pytest.raises( ValueError, match="'l_bounds' and 'u_bounds' must be broadcastable and respect" From 543ad0e9d483d1c49c5e594344d4d66500d25d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mendes?= Date: Wed, 22 May 2024 19:56:30 +0100 Subject: [PATCH 235/500] BENCH: add benchmark for qmc.PoissonDisk --- benchmarks/benchmarks/stats.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/benchmarks/benchmarks/stats.py b/benchmarks/benchmarks/stats.py index 3a5692c405a0..c2af6a3f4953 100644 --- a/benchmarks/benchmarks/stats.py +++ b/benchmarks/benchmarks/stats.py @@ -652,6 +652,21 @@ def time_sobol(self, d, base2): seq = stats.qmc.Sobol(d, scramble=False, bits=32, seed=self.rng) seq.random_base2(base2) +class BenchPoissonDisk(Benchmark): + param_names = ['d', 'radius', 'ncandidates', 'n'] + params = [ + [1, 5, 10], + [1, 10, 50], + [30, 60, 120], + [30, 100, 300] + ] + + def setup(self, d, radius, ncandidates, n): + self.rng = np.random.default_rng(168525179735951991038384544) + + def time_poisson_disk(self, d, radius, ncandidates, n): + seq = stats.qmc.PoissonDisk(d, radius=radius, ncandidates=ncandidates, seed=self.rng) + seq.random(n) class DistanceFunctions(Benchmark): param_names = ['n_size'] From ae1d1f68bd12e52d2decb52ec7697d609443b1c0 Mon Sep 17 00:00:00 2001 From: Diogo Pires Date: Wed, 22 May 2024 20:24:13 +0100 Subject: [PATCH 236/500] TST: parametrize bounds when testing inconsistent bound sizes in PoissonDisk --- scipy/stats/tests/test_qmc.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/scipy/stats/tests/test_qmc.py b/scipy/stats/tests/test_qmc.py index 43a81adab796..9700763461a0 100644 --- a/scipy/stats/tests/test_qmc.py +++ b/scipy/stats/tests/test_qmc.py @@ -942,7 +942,9 @@ def test_sample_inside_bounds(self): radius = 0.2 l_bounds=[-1, -2, -1] u_bounds=[3, 2, 1] - engine = self.qmce(d=3, radius=radius, l_bounds=l_bounds, u_bounds=u_bounds) + engine = self.qmce( + d=3, radius=radius, l_bounds=l_bounds, u_bounds=u_bounds + ) sample = engine.random(30) for point in sample: @@ -958,23 +960,18 @@ def test_inconsistent_bound_value(self): match="Bounds are not consistent 'l_bounds' < 'u_bounds'"): self.qmce(d=3, radius=radius, l_bounds=l_bounds, u_bounds=u_bounds) - def test_inconsistent_bounds(self): + @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 - l_bounds = [-1, -2, -1] - u_bounds = [3, 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) - - l_bounds = [-1, -2] - u_bounds = [3, 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) + 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" From c3ff5c5dd25930c02e6edd973137377177dd6628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mendes?= Date: Thu, 23 May 2024 01:22:55 +0100 Subject: [PATCH 237/500] FIX: sample point generation in PoissonDisk now correctly takes u_bounds into account --- benchmarks/benchmarks/stats.py | 3 ++- scipy/stats/_qmc.py | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/benchmarks/benchmarks/stats.py b/benchmarks/benchmarks/stats.py index c2af6a3f4953..d16e136144aa 100644 --- a/benchmarks/benchmarks/stats.py +++ b/benchmarks/benchmarks/stats.py @@ -665,7 +665,8 @@ def setup(self, d, radius, ncandidates, n): self.rng = np.random.default_rng(168525179735951991038384544) def time_poisson_disk(self, d, radius, ncandidates, n): - seq = stats.qmc.PoissonDisk(d, radius=radius, ncandidates=ncandidates, seed=self.rng) + seq = stats.qmc.PoissonDisk(d, radius=radius, ncandidates=ncandidates, + seed=self.rng) seq.random(n) class DistanceFunctions(Benchmark): diff --git a/scipy/stats/_qmc.py b/scipy/stats/_qmc.py index e46adb143841..d3ec115982f4 100644 --- a/scipy/stats/_qmc.py +++ b/scipy/stats/_qmc.py @@ -2018,7 +2018,7 @@ def __init__( with np.errstate(divide='ignore'): self.cell_size = self.radius / np.sqrt(self.d) self.grid_size = ( - np.ceil(self.u_bounds / self.cell_size) + np.ceil((self.u_bounds - self.l_bounds) / self.cell_size) ).astype(int) self._initialize_grid_pool() @@ -2068,7 +2068,7 @@ 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.cell_size).astype(int) + indices = ((candidate - self.l_bounds) / self.cell_size).astype(int) ind_min = np.maximum(indices - n, self.l_bounds.astype(int)) ind_max = np.minimum(indices + n + 1, self.grid_size) @@ -2093,7 +2093,7 @@ def in_neighborhood(candidate: np.ndarray, n: int = 2) -> bool: def add_sample(candidate: np.ndarray) -> None: self.sample_pool.append(candidate) - indices = (candidate / self.cell_size).astype(int) + indices = ((candidate - self.l_bounds) / self.cell_size).astype(int) self.sample_grid[tuple(indices)] = candidate curr_sample.append(candidate) @@ -2101,7 +2101,7 @@ def add_sample(candidate: np.ndarray) -> None: if len(self.sample_pool) == 0: # the pool is being initialized with a single random sample - add_sample(self.rng.random(self.d)) + add_sample(self.rng.uniform(self.l_bounds, self.u_bounds)) num_drawn = 1 else: num_drawn = 0 From e9e640b242555566d2989753416b264a0b1e5de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mendes?= Date: Thu, 23 May 2024 02:25:10 +0100 Subject: [PATCH 238/500] BENCH: update PoissonDisk benchmark --- benchmarks/benchmarks/stats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/benchmarks/stats.py b/benchmarks/benchmarks/stats.py index d16e136144aa..fe19929830b0 100644 --- a/benchmarks/benchmarks/stats.py +++ b/benchmarks/benchmarks/stats.py @@ -655,8 +655,8 @@ def time_sobol(self, d, base2): class BenchPoissonDisk(Benchmark): param_names = ['d', 'radius', 'ncandidates', 'n'] params = [ - [1, 5, 10], - [1, 10, 50], + [1, 3, 5], + [0.2, 0.1, 0.05], [30, 60, 120], [30, 100, 300] ] From 932244a640321064c65fcd2b83aec26383324455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mendes?= Date: Thu, 23 May 2024 02:33:55 +0100 Subject: [PATCH 239/500] TEST: more test coverage for samples inside bounds in PoissonDisk --- scipy/stats/tests/test_qmc.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/scipy/stats/tests/test_qmc.py b/scipy/stats/tests/test_qmc.py index 9700763461a0..a067615ca8c6 100644 --- a/scipy/stats/tests/test_qmc.py +++ b/scipy/stats/tests/test_qmc.py @@ -938,10 +938,23 @@ def test_fill_space(self): # circle packing problem is np complex assert l2_norm(sample) >= radius - def test_sample_inside_bounds(self): + @pytest.mark.parametrize("l_bounds", [[-1, -2, -1], [1, 2, 1]]) + def test_sample_inside_lower_bounds(self, l_bounds): radius = 0.2 - l_bounds=[-1, -2, -1] - u_bounds=[3, 2, 1] + 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 ) From 9711e31e38ab9286266e56e169b51117f63455cb Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 23 May 2024 00:11:55 -0700 Subject: [PATCH 240/500] MAINT: stats: move `multiscale_graphcorr` tests to save time (#20769) --- scipy/stats/__init__.py | 2 + scipy/stats/_mgc.py | 550 +++++++++++++++++++++++++++++++ scipy/stats/_stats_py.py | 551 +------------------------------- scipy/stats/meson.build | 1 + scipy/stats/stats.py | 2 +- scipy/stats/tests/meson.build | 1 + scipy/stats/tests/test_mgc.py | 217 +++++++++++++ scipy/stats/tests/test_stats.py | 213 +----------- 8 files changed, 778 insertions(+), 759 deletions(-) create mode 100644 scipy/stats/_mgc.py create mode 100644 scipy/stats/tests/test_mgc.py diff --git a/scipy/stats/__init__.py b/scipy/stats/__init__.py index 3866a23354a6..180f50a1756f 100644 --- a/scipy/stats/__init__.py +++ b/scipy/stats/__init__.py @@ -633,6 +633,8 @@ from ._covariance import Covariance from ._sensitivity_analysis import * from ._survival import * +from ._mgc import multiscale_graphcorr + # Deprecated namespaces, to be removed in v2.0.0 from . import ( diff --git a/scipy/stats/_mgc.py b/scipy/stats/_mgc.py new file mode 100644 index 000000000000..e8eb835cbafb --- /dev/null +++ b/scipy/stats/_mgc.py @@ -0,0 +1,550 @@ +import warnings +import numpy as np + +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'], []) + + +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') + + n, m = stat_mgc_map.shape + if m == 1 or n == 1: + # the global scale at is the statistic calculated at maximial 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 maximial 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 maximial 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/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 06c244cef2dc..91a278106771 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -35,13 +35,11 @@ from numpy import array, asarray, ma from scipy import sparse -from scipy.spatial.distance import cdist from scipy.spatial import distance_matrix -from scipy.ndimage import _measurements from scipy.optimize import milp, LinearConstraint -from scipy._lib._util import (check_random_state, MapWrapper, _get_nan, - rng_integers, _rename_parameter, _contains_nan, +from scipy._lib._util import (check_random_state, _get_nan, + _rename_parameter, _contains_nan, AxisError) import scipy.special as special @@ -52,8 +50,8 @@ from . import _mstats_basic as mstats_basic from ._stats_mstats_common import (_find_repeats, linregress, theilslopes, siegelslopes) -from ._stats import (_kendall_dis, _toint64, _weightedrankedtau, - _local_correlations) +from ._stats import _kendall_dis, _toint64, _weightedrankedtau + from dataclasses import dataclass, field from ._hypotests import _all_partitions from ._stats_pythran import _compute_outer_prob_inside_method @@ -86,7 +84,7 @@ 'sigmaclip', 'trimboth', 'trim1', 'trim_mean', 'f_oneway', 'pearsonr', 'fisher_exact', 'spearmanr', 'pointbiserialr', - 'kendalltau', 'weightedtau', 'multiscale_graphcorr', + 'kendalltau', 'weightedtau', 'linregress', 'siegelslopes', 'theilslopes', 'ttest_1samp', 'ttest_ind', 'ttest_ind_from_stats', 'ttest_rel', 'kstest', 'ks_1samp', 'ks_2samp', @@ -6219,545 +6217,6 @@ def weightedtau(x, y, rank=True, weigher=None, additive=True): return res -# 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'], []) - - -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') - - n, m = stat_mgc_map.shape - if m == 1 or n == 1: - # the global scale at is the statistic calculated at maximial 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 maximial 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 maximial 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 - - ##################################### # INFERENTIAL STATISTICS # ##################################### diff --git a/scipy/stats/meson.build b/scipy/stats/meson.build index b1eb66c9e5a0..bb43e3b2e98a 100644 --- a/scipy/stats/meson.build +++ b/scipy/stats/meson.build @@ -124,6 +124,7 @@ py3.install_sources([ '_kde.py', '_ksstats.py', '_mannwhitneyu.py', + '_mgc.py', '_morestats.py', '_mstats_basic.py', '_mstats_extras.py', diff --git a/scipy/stats/stats.py b/scipy/stats/stats.py index 1c1a5d4c0636..d5d278e209ce 100644 --- a/scipy/stats/stats.py +++ b/scipy/stats/stats.py @@ -37,5 +37,5 @@ def __dir__(): def __getattr__(name): return _sub_module_deprecation(sub_package="stats", module="stats", - private_modules=["_stats_py"], all=__all__, + private_modules=["_stats_py", "_mgc"], all=__all__, attribute=name) diff --git a/scipy/stats/tests/meson.build b/scipy/stats/tests/meson.build index 39564cf74c0d..a94e3c7cd502 100644 --- a/scipy/stats/tests/meson.build +++ b/scipy/stats/tests/meson.build @@ -16,6 +16,7 @@ py3.install_sources([ 'test_fit.py', 'test_hypotests.py', 'test_kdeoth.py', + 'test_mgc.py', 'test_morestats.py', 'test_mstats_basic.py', 'test_mstats_extras.py', diff --git a/scipy/stats/tests/test_mgc.py b/scipy/stats/tests/test_mgc.py new file mode 100644 index 000000000000..c82b81530d58 --- /dev/null +++ b/scipy/stats/tests/test_mgc.py @@ -0,0 +1,217 @@ +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 a 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 _simulations(self, samps=100, dims=1, sim_type=""): + # linear simulation + if sim_type == "linear": + x = np.random.uniform(-1, 1, size=(samps, 1)) + y = x + 0.3 * np.random.random_sample(size=(x.size, 1)) + + # spiral simulation + elif sim_type == "nonlinear": + unif = np.array(np.random.uniform(0, 5, size=(samps, 1))) + x = unif * np.cos(np.pi * unif) + y = (unif * np.sin(np.pi * unif) + + 0.4*np.random.random_sample(size=(x.size, 1))) + + # independence (tests type I simulation) + elif sim_type == "independence": + u = np.random.normal(0, 1, size=(samps, 1)) + v = np.random.normal(0, 1, size=(samps, 1)) + u_2 = np.random.binomial(1, p=0.5, size=(samps, 1)) + v_2 = np.random.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 = np.random.normal(0, 1, 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", [ + ("linear", 0.97, 1/1000), # test linear simulation + ("nonlinear", 0.163, 1/1000), # test spiral simulation + ("independence", -0.0094, 0.78) # test independence simulation + ]) + def test_oned(self, sim_type, obs_stat, obs_pvalue): + np.random.seed(12345678) + + # generate x and y + x, y = self._simulations(samps=100, dims=1, sim_type=sim_type) + + # test stat and pvalue + stat, pvalue, _ = stats.multiscale_graphcorr(x, y) + assert_approx_equal(stat, obs_stat, significant=1) + assert_approx_equal(pvalue, obs_pvalue, significant=1) + + @pytest.mark.xslow + @pytest.mark.parametrize("sim_type, obs_stat, obs_pvalue", [ + ("linear", 0.184, 1/1000), # test linear simulation + ("nonlinear", 0.0190, 0.117), # test spiral simulation + ]) + def test_fived(self, sim_type, obs_stat, obs_pvalue): + np.random.seed(12345678) + + # generate x and y + x, y = self._simulations(samps=100, dims=5, sim_type=sim_type) + + # test stat and pvalue + stat, pvalue, _ = stats.multiscale_graphcorr(x, y) + assert_approx_equal(stat, obs_stat, significant=1) + assert_approx_equal(pvalue, obs_pvalue, significant=1) + + @pytest.mark.xslow + def test_twosamp(self): + np.random.seed(12345678) + + # generate x and y + x = np.random.binomial(100, 0.5, size=(100, 5)) + y = np.random.normal(0, 1, size=(80, 5)) + + # test stat and pvalue + stat, pvalue, _ = stats.multiscale_graphcorr(x, y) + assert_approx_equal(stat, 1.0, significant=1) + assert_approx_equal(pvalue, 0.001, significant=1) + + # generate x and y + y = np.random.normal(0, 1, size=(100, 5)) + + # test stat and pvalue + stat, pvalue, _ = stats.multiscale_graphcorr(x, y, is_twosamp=True) + assert_approx_equal(stat, 1.0, significant=1) + assert_approx_equal(pvalue, 0.001, significant=1) + + @pytest.mark.xslow + def test_workers(self): + np.random.seed(12345678) + + # 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) + 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): + np.random.seed(12345678) + # 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(10) # all other tests are XSLOW; we need at least one to run + @pytest.mark.slow + def test_pvalue_literature(self): + np.random.seed(12345678) + + # 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) + + @pytest.mark.xslow + def test_alias(self): + np.random.seed(12345678) + + # 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/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index a5bb43e17c85..4d7b78ad395c 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -18,7 +18,7 @@ from numpy.testing import (assert_, assert_equal, assert_almost_equal, assert_array_almost_equal, assert_array_equal, assert_approx_equal, - assert_allclose, assert_warns, suppress_warnings, + assert_allclose, suppress_warnings, assert_array_less) import pytest from pytest import raises as assert_raises @@ -34,7 +34,6 @@ from scipy.special import binom from scipy import optimize from .common_tests import check_named_results -from scipy.spatial.distance import cdist from scipy.stats._axis_nan_policy import _broadcast_concatenate from scipy.stats._stats_py import _permutation_distribution_t, _chk_asarray, _moment from scipy._lib._util import AxisError @@ -8413,216 +8412,6 @@ def test_consistency(self): assert_equal(r1, r2) -class TestMGCErrorWarnings: - """ Tests errors and warnings derived from MGC. - """ - def test_error_notndarray(self): - # raises error if x or y is not a 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 _simulations(self, samps=100, dims=1, sim_type=""): - # linear simulation - if sim_type == "linear": - x = np.random.uniform(-1, 1, size=(samps, 1)) - y = x + 0.3 * np.random.random_sample(size=(x.size, 1)) - - # spiral simulation - elif sim_type == "nonlinear": - unif = np.array(np.random.uniform(0, 5, size=(samps, 1))) - x = unif * np.cos(np.pi * unif) - y = (unif * np.sin(np.pi * unif) + - 0.4*np.random.random_sample(size=(x.size, 1))) - - # independence (tests type I simulation) - elif sim_type == "independence": - u = np.random.normal(0, 1, size=(samps, 1)) - v = np.random.normal(0, 1, size=(samps, 1)) - u_2 = np.random.binomial(1, p=0.5, size=(samps, 1)) - v_2 = np.random.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 = np.random.normal(0, 1, 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", [ - ("linear", 0.97, 1/1000), # test linear simulation - ("nonlinear", 0.163, 1/1000), # test spiral simulation - ("independence", -0.0094, 0.78) # test independence simulation - ]) - def test_oned(self, sim_type, obs_stat, obs_pvalue): - np.random.seed(12345678) - - # generate x and y - x, y = self._simulations(samps=100, dims=1, sim_type=sim_type) - - # test stat and pvalue - stat, pvalue, _ = stats.multiscale_graphcorr(x, y) - assert_approx_equal(stat, obs_stat, significant=1) - assert_approx_equal(pvalue, obs_pvalue, significant=1) - - @pytest.mark.xslow - @pytest.mark.parametrize("sim_type, obs_stat, obs_pvalue", [ - ("linear", 0.184, 1/1000), # test linear simulation - ("nonlinear", 0.0190, 0.117), # test spiral simulation - ]) - def test_fived(self, sim_type, obs_stat, obs_pvalue): - np.random.seed(12345678) - - # generate x and y - x, y = self._simulations(samps=100, dims=5, sim_type=sim_type) - - # test stat and pvalue - stat, pvalue, _ = stats.multiscale_graphcorr(x, y) - assert_approx_equal(stat, obs_stat, significant=1) - assert_approx_equal(pvalue, obs_pvalue, significant=1) - - @pytest.mark.xslow - def test_twosamp(self): - np.random.seed(12345678) - - # generate x and y - x = np.random.binomial(100, 0.5, size=(100, 5)) - y = np.random.normal(0, 1, size=(80, 5)) - - # test stat and pvalue - stat, pvalue, _ = stats.multiscale_graphcorr(x, y) - assert_approx_equal(stat, 1.0, significant=1) - assert_approx_equal(pvalue, 0.001, significant=1) - - # generate x and y - y = np.random.normal(0, 1, size=(100, 5)) - - # test stat and pvalue - stat, pvalue, _ = stats.multiscale_graphcorr(x, y, is_twosamp=True) - assert_approx_equal(stat, 1.0, significant=1) - assert_approx_equal(pvalue, 0.001, significant=1) - - @pytest.mark.xslow - def test_workers(self): - np.random.seed(12345678) - - # 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) - 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): - np.random.seed(12345678) - # 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(10) # all other tests are XSLOW; we need at least one to run - @pytest.mark.slow - def test_pvalue_literature(self): - np.random.seed(12345678) - - # 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) - - @pytest.mark.xslow - def test_alias(self): - np.random.seed(12345678) - - # 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) - - class TestQuantileTest: r""" Test the non-parametric quantile test, including the computation of confidence intervals From b421cd64d98c16811f84efbfab7701b335f811be Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Thu, 23 May 2024 09:33:03 +0100 Subject: [PATCH 241/500] DEP: signal: raise error using medfilt and order_filter with float128 and object dtypes (#19673) * MAINT: remove more unused C code * MAINT/TST: modernise test and more error earlier * update test to skip depending on what longdouble the system has * specify dtypes that are allowed --- scipy/signal/_signaltools.py | 38 +-- scipy/signal/_sigtoolsmodule.c | 370 ------------------------- scipy/signal/tests/test_signaltools.py | 50 +--- 3 files changed, 22 insertions(+), 436 deletions(-) diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index d8c7770522f6..fdda2a9e4417 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -1496,15 +1496,11 @@ def order_filter(a, domain, rank): "should have an odd number of elements.") a = np.asarray(a) - if a.dtype in [object, 'float128']: - mesg = (f"Using order_filter with arrays of dtype {a.dtype} is " - f"deprecated in SciPy 1.11 and will be removed in SciPy 1.14") - warnings.warn(mesg, DeprecationWarning, stacklevel=2) - - result = _sigtools._order_filterND(a, domain, rank) - else: - result = ndimage.rank_filter(a, rank, footprint=domain, mode='constant') + if not (np.issubdtype(a.dtype, np.integer) + or a.dtype in [np.float32, np.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 @@ -1551,6 +1547,10 @@ def medfilt(volume, kernel_size=None): """ volume = np.atleast_1d(volume) + if not (np.issubdtype(volume.dtype, np.integer) + or volume.dtype in [np.float32, np.float64]): + raise ValueError(f"dtype={volume.dtype} is not supported by medfilt") + if kernel_size is None: kernel_size = [3] * volume.ndim kernel_size = np.asarray(kernel_size) @@ -1565,25 +1565,9 @@ def medfilt(volume, kernel_size=None): 'zero-padded.', stacklevel=2) - domain = np.ones(kernel_size, dtype=volume.dtype) - - numels = np.prod(kernel_size, axis=0) - order = numels // 2 - - if volume.dtype in [np.bool_, np.complex64, np.complex128, np.clongdouble, - np.float16]: - raise ValueError(f"dtype={volume.dtype} is not supported by medfilt") - - if volume.dtype.char in ['O', 'g']: - mesg = (f"Using medfilt with arrays of dtype {volume.dtype} is " - f"deprecated in SciPy 1.11 and will be removed in SciPy 1.14") - warnings.warn(mesg, DeprecationWarning, stacklevel=2) - - result = _sigtools._order_filterND(volume, domain, order) - else: - size = math.prod(kernel_size) - result = ndimage.rank_filter(volume, size // 2, size=kernel_size, - mode='constant') + size = math.prod(kernel_size) + result = ndimage.rank_filter(volume, size // 2, size=kernel_size, + mode='constant') return result diff --git a/scipy/signal/_sigtoolsmodule.c b/scipy/signal/_sigtoolsmodule.c index 7cfc49fe1939..eb5253159782 100644 --- a/scipy/signal/_sigtoolsmodule.c +++ b/scipy/signal/_sigtoolsmodule.c @@ -29,84 +29,6 @@ char *check_malloc(size_t size) } -/************************************************************************ - * Start of portable, non-python specific routines. * - ************************************************************************/ - -/* Some core routines are written -in a portable way so that they could be used in other applications. The -order filtering, however uses python-specific constructs in its guts -and is therefore Python dependent. This could be changed in a -straightforward way but I haven't done it for lack of time.*/ - -static int index_out_of_bounds(npy_intp *indices, npy_intp *max_indices, int ndims) { - int bad_index = 0, k = 0; - - while (!bad_index && (k++ < ndims)) { - bad_index = ((*(indices) >= *(max_indices++)) || (*(indices) < 0)); - indices++; - } - return bad_index; -} - -/* This maybe could be redone with stride information so it could be - * called with non-contiguous arrays: I think offsets is related to - * the difference between the strides. I'm not sure about init_offset - * just yet. I think it needs to be calculated because of mode_dep - * but probably with dim1 being the size of the "original, unsliced" array - */ - -static npy_intp compute_offsets (npy_uintp *offsets, npy_intp *offsets2, npy_intp *dim1, - npy_intp *dim2, npy_intp *dim3, npy_intp *mode_dep, - int nd) { - int k,i; - npy_intp init_offset = 0; - - for (k = 0; k < nd - 1; k++) - { - init_offset += mode_dep[k]; - init_offset *= dim1[k+1]; - } - init_offset += mode_dep[k] - 2; - - k = nd; - while(k--) { - offsets[k] = 0; - offsets2[k] = 0; - for (i = k + 1; i < nd - 1; i++) { - offsets[k] += dim1[i] - dim2[i]; - offsets[k] *= dim1[i+1]; - - offsets2[k] += dim1[i] - dim3[i]; - offsets2[k] *= dim1[i+1]; - } - - if (k < nd - 1) { - offsets[k] += dim1[i] - dim2[i]; - offsets2[k] += dim1[i] - dim3[i]; - } - offsets[k] += 1; - offsets2[k] += 1; - } - return init_offset; -} - -/* increment by 1 the index into an N-D array, doing the necessary - carrying when the index reaches the dimension along that axis */ -static int increment(npy_intp *ret_ind, int nd, npy_intp *max_ind) { - int k, incr = 1; - - k = nd - 1; - if (++ret_ind[k] >= max_ind[k]) { - while (k >= 0 && (ret_ind[k] >= max_ind[k]-1)) { - incr++; - ret_ind[k--] = 0; - } - if (k >= 0) ret_ind[k]++; - } - return incr; -} - /******************************************************** * * Code taken from remez.c by Erik Kvaleberg which was @@ -762,284 +684,6 @@ static int pre_remez(double *h2, int numtaps, int numbands, double *bands, /* End of python-independent routines */ /****************************************************/ -/************************/ -/* N-D Order Filtering. */ - - -static void fill_buffer(char *ip1, PyArrayObject *ap1, PyArrayObject *ap2, - char *sort_buffer, int nels2, int check, - npy_intp *loop_ind, npy_intp *temp_ind, npy_uintp *offset){ - int i, k, incr = 1; - int ndims = PyArray_NDIM(ap1); - npy_intp *dims2 = PyArray_DIMS(ap2); - npy_intp *dims1 = PyArray_DIMS(ap1); - npy_intp is1 = PyArray_ITEMSIZE(ap1); - npy_intp is2 = PyArray_ITEMSIZE(ap2); - char *ip2 = PyArray_DATA(ap2); - int elsize = PyArray_ITEMSIZE(ap1); - char *ptr; - - i = nels2; - ptr = PyArray_Zero(ap2); - temp_ind[ndims-1]--; - while (i--) { - /* Adjust index array and move ptr1 to right place */ - k = ndims - 1; - while(--incr) { - temp_ind[k] -= dims2[k] - 1; /* Return to start for these dimensions */ - k--; - } - ip1 += offset[k]*is1; /* Precomputed offset array */ - temp_ind[k]++; - - if (!(check && index_out_of_bounds(temp_ind,dims1,ndims)) && \ - memcmp(ip2, ptr, PyArray_ITEMSIZE(ap2))) { - memcpy(sort_buffer, ip1, elsize); - sort_buffer += elsize; - } - /* Returns number of N-D indices incremented. */ - incr = increment(loop_ind, ndims, dims2); - ip2 += is2; - - } - PyDataMem_FREE(ptr); - return; -} - -#define COMPARE(fname, type) \ -int fname(type *ip1, type *ip2) { return *ip1 < *ip2 ? -1 : *ip1 == *ip2 ? 0 : 1; } - -COMPARE(DOUBLE_compare, double) -COMPARE(FLOAT_compare, float) -COMPARE(LONGDOUBLE_compare, npy_longdouble) -COMPARE(BYTE_compare, npy_byte) -COMPARE(SHORT_compare, short) -COMPARE(INT_compare, int) -COMPARE(LONG_compare, long) -COMPARE(LONGLONG_compare, npy_longlong) -COMPARE(UBYTE_compare, npy_ubyte) -COMPARE(USHORT_compare, npy_ushort) -COMPARE(UINT_compare, npy_uint) -COMPARE(ULONG_compare, npy_ulong) -COMPARE(ULONGLONG_compare, npy_ulonglong) - - -int OBJECT_compare(PyObject **ip1, PyObject **ip2) { - /* PyObject_RichCompareBool returns -1 on error; not handled here */ - if(PyObject_RichCompareBool(*ip1, *ip2, Py_LT) == 1) - return -1; - else if(PyObject_RichCompareBool(*ip1, *ip2, Py_EQ) == 1) - return 0; - else - return 1; -} - -typedef int (*CompareFunction)(const void *, const void *); - -CompareFunction compare_functions[] = \ - {NULL, (CompareFunction)BYTE_compare,(CompareFunction)UBYTE_compare,\ - (CompareFunction)SHORT_compare,(CompareFunction)USHORT_compare, \ - (CompareFunction)INT_compare,(CompareFunction)UINT_compare, \ - (CompareFunction)LONG_compare,(CompareFunction)ULONG_compare, \ - (CompareFunction)LONGLONG_compare,(CompareFunction)ULONGLONG_compare, - (CompareFunction)FLOAT_compare,(CompareFunction)DOUBLE_compare, - (CompareFunction)LONGDOUBLE_compare, NULL, NULL, NULL, - (CompareFunction)OBJECT_compare, NULL, NULL, NULL}; - -PyObject *PyArray_OrderFilterND(PyObject *op1, PyObject *op2, int order) { - PyArrayObject *ap1=NULL, *ap2=NULL, *ret=NULL; - npy_intp *a_ind=NULL, *b_ind=NULL, *temp_ind=NULL, *mode_dep=NULL, *check_ind=NULL; - npy_uintp *offsets=NULL; - npy_intp *offsets2=NULL; - npy_uintp offset1; - int i, n2, n2_nonzero, k, check, incr = 1; - int typenum, bytes_in_array; - int is1, os; - char *op, *ap1_ptr, *ap2_ptr, *sort_buffer=NULL; - npy_intp *ret_ind=NULL; - CompareFunction compare_func=NULL; - char *zptr=NULL; - PyArray_CopySwapFunc *copyswap; - - /* Get Array objects from input */ - typenum = PyArray_ObjectType(op1, 0); - typenum = PyArray_ObjectType(op2, typenum); - - ap1 = (PyArrayObject *)PyArray_ContiguousFromObject(op1, typenum, 0, 0); - if (ap1 == NULL) return NULL; - ap2 = (PyArrayObject *)PyArray_ContiguousFromObject(op2, typenum, 0, 0); - if (ap2 == NULL) goto fail; - - if (PyArray_NDIM(ap1) != PyArray_NDIM(ap2)) { - PyErr_SetString(PyExc_ValueError, - "All input arrays must have the same number of dimensions."); - goto fail; - } - - n2 = PyArray_Size((PyObject *)ap2); - n2_nonzero = 0; - ap2_ptr = PyArray_DATA(ap2); - /* - * Find out the number of non-zero entries in domain (allows for - * different shapped rank-filters to be used besides just rectangles) - */ - zptr = PyArray_Zero(ap2); - if (zptr == NULL) goto fail; - for (k=0; k < n2; k++) { - n2_nonzero += (memcmp(ap2_ptr,zptr,PyArray_ITEMSIZE(ap2)) != 0); - ap2_ptr += PyArray_ITEMSIZE(ap2); - } - - if ((order >= n2_nonzero) || (order < 0)) { - PyErr_SetString(PyExc_ValueError, - "Order must be non-negative and less than number of nonzero elements in domain."); - goto fail; - } - - ret = (PyArrayObject *)PyArray_SimpleNew(PyArray_NDIM(ap1), - PyArray_DIMS(ap1), - typenum); - if (ret == NULL) goto fail; - - if (PyArray_TYPE(ap1) < sizeof(compare_functions) / sizeof(compare_functions[0])) { - compare_func = compare_functions[PyArray_TYPE(ap1)]; - } - if (compare_func == NULL) { - PyErr_SetString(PyExc_ValueError, - "order_filterND not available for this type"); - goto fail; - } - - is1 = PyArray_ITEMSIZE(ap1); - - if (!(sort_buffer = malloc(n2_nonzero*is1))) goto fail; - - os = PyArray_ITEMSIZE(ret); - op = PyArray_DATA(ret); - - copyswap = PyDataType_GetArrFuncs(PyArray_DESCR(ret))->copyswap; - - bytes_in_array = PyArray_NDIM(ap1)*sizeof(npy_intp); - mode_dep = malloc(bytes_in_array); - if (mode_dep == NULL) goto fail; - for (k = 0; k < PyArray_NDIM(ap1); k++) { - mode_dep[k] = -((PyArray_DIMS(ap2)[k]-1) >> 1); - } - - b_ind = (npy_intp *)malloc(bytes_in_array); /* loop variables */ - if (b_ind == NULL) goto fail; - memset(b_ind,0,bytes_in_array); - a_ind = (npy_intp *)malloc(bytes_in_array); - ret_ind = (npy_intp *)malloc(bytes_in_array); - if (a_ind == NULL || ret_ind == NULL) goto fail; - memset(ret_ind,0,bytes_in_array); - temp_ind = (npy_intp *)malloc(bytes_in_array); - check_ind = (npy_intp*)malloc(bytes_in_array); - offsets = (npy_uintp *)malloc(PyArray_NDIM(ap1)*sizeof(npy_uintp)); - offsets2 = (npy_intp *)malloc(PyArray_NDIM(ap1)*sizeof(npy_intp)); - if (temp_ind == NULL || check_ind == NULL || offsets == NULL || offsets2 == NULL) goto fail; - offset1 = compute_offsets(offsets, offsets2, PyArray_DIMS(ap1), - PyArray_DIMS(ap2), PyArray_DIMS(ret), - mode_dep, PyArray_NDIM(ap1)); - /* The filtering proceeds by looping through the output array - and for each value filling a buffer from the - element-by-element product of the two input arrays. The buffer - is then sorted and the order_th element is kept as output. Index - counters are used for book-keeping in the area so that we - can tell where we are in all of the arrays and be sure that - we are not trying to access areas outside the arrays definition. - - The inner loop is implemented separately but equivalently for each - datatype. The outer loop is similar in structure and form to - to the inner loop. - */ - /* Need to keep track of a ptr to place in big (first) input - array where we start the multiplication (we pass over it in the - inner loop (and not dereferenced) - if it is pointing outside dataspace) - */ - /* Calculate it once and the just move it around appropriately */ - PyDataMem_FREE(zptr); - zptr = PyArray_Zero(ap1); - if (zptr == NULL) goto fail; - ap1_ptr = (char *)PyArray_DATA(ap1) + offset1*is1; - for (k=0; k < PyArray_NDIM(ap1); k++) { - a_ind[k] = mode_dep[k]; - check_ind[k] = PyArray_DIMS(ap1)[k] - PyArray_DIMS(ap2)[k] - mode_dep[k] - 1; - } - a_ind[PyArray_NDIM(ap1)-1]--; - i = PyArray_Size((PyObject *)ret); - while (i--) { - /* - * Zero out the sort_buffer (has effect of zero-padding - * on boundaries). Treat object arrays right. - */ - ap2_ptr = sort_buffer; - for (k=0; k < n2_nonzero; k++) { - memcpy(ap2_ptr,zptr,is1); - ap2_ptr += is1; - } - - k = PyArray_NDIM(ap1) - 1; - while(--incr) { - a_ind[k] -= PyArray_DIMS(ret)[k] - 1; /* Return to start */ - k--; - } - ap1_ptr += offsets2[k]*is1; - a_ind[k]++; - memcpy(temp_ind, a_ind, bytes_in_array); - - check = 0; k = -1; - while(!check && (++k < PyArray_NDIM(ap1))) - check = (check || (ret_ind[k] < -mode_dep[k]) || - (ret_ind[k] > check_ind[k])); - - fill_buffer(ap1_ptr,ap1,ap2,sort_buffer,n2,check,b_ind,temp_ind,offsets); - qsort(sort_buffer, n2_nonzero, is1, compare_func); - - /* - * Use copyswap for correct refcounting with object arrays - * (sort_buffer has borrowed references, op owns references). Note - * also that os == PyArray_ITEMSIZE(ret) and we are copying a single - * scalar here. - */ - copyswap(op, sort_buffer + order*is1, 0, NULL); - - /* increment index counter */ - incr = increment(ret_ind,PyArray_NDIM(ret),PyArray_DIMS(ret)); - /* increment to next output index */ - op += os; - - } - free(b_ind); free(a_ind); free(ret_ind); - free(offsets); free(offsets2); free(temp_ind); - free(check_ind); free(mode_dep); - free(sort_buffer); - - PyDataMem_FREE(zptr); - Py_DECREF(ap1); - Py_DECREF(ap2); - - return PyArray_Return(ret); - -fail: - if (zptr) PyDataMem_FREE(zptr); - free(sort_buffer); - free(mode_dep); - free(b_ind); - free(a_ind); - free(ret_ind); - free(temp_ind); - free(check_ind); - free(offsets); - free(offsets2); - Py_XDECREF(ap1); - Py_XDECREF(ap2); - Py_XDECREF(ret); - return NULL; -} - - /******************************************/ static char doc_correlateND[] = "out = _correlateND(a,kernel,mode) \n\n mode = 0 - 'valid', 1 - 'same', \n 2 - 'full' (default)"; @@ -1192,19 +836,6 @@ static PyObject *_sigtools_convolve2d(PyObject *NPY_UNUSED(dummy), PyObject *arg /*******************************************************************/ -static char doc_order_filterND[] = "out = _order_filterND(a,domain,order)"; - -static PyObject *_sigtools_order_filterND(PyObject *NPY_UNUSED(dummy), - PyObject *args) { - PyObject *domain, *a0; - int order=0; - - if (!PyArg_ParseTuple(args, "OO|i", &a0, &domain, &order)) return NULL; - - return PyArray_OrderFilterND(a0, domain, order); -} - - static char doc_remez[] = "h = _remez(numtaps, bands, des, weight, type, fs, maxiter, grid_density)\n" " returns the optimal (in the Chebyshev/minimax sense) FIR filter impulse\n" @@ -1392,7 +1023,6 @@ static char doc_linear_filter[] = static struct PyMethodDef toolbox_module_methods[] = { {"_correlateND", scipy_signal__sigtools_correlateND, METH_VARARGS, doc_correlateND}, {"_convolve2d", _sigtools_convolve2d, METH_VARARGS, doc_convolve2d}, - {"_order_filterND", _sigtools_order_filterND, METH_VARARGS, doc_order_filterND}, {"_linear_filter", scipy_signal__sigtools_linear_filter, METH_VARARGS, doc_linear_filter}, {"_remez", _sigtools_remez, METH_VARARGS, doc_remez}, {"_medfilt2d", _sigtools_median2d, METH_VARARGS, doc_median2d}, diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py index 8be2308c47f9..28e9930a5578 100644 --- a/scipy/signal/tests/test_signaltools.py +++ b/scipy/signal/tests/test_signaltools.py @@ -1093,19 +1093,16 @@ def test_types(self, dtype): assert_equal(signal.medfilt(in_typed).dtype, dtype) assert_equal(signal.medfilt2d(in_typed).dtype, dtype) - def test_types_deprecated(self): - dtype = np.longdouble - in_typed = np.array(self.IN, dtype=dtype) - msg = "Using medfilt with arrays of dtype" - with pytest.deprecated_call(match=msg): - assert_equal(signal.medfilt(in_typed).dtype, dtype) - with pytest.deprecated_call(match=msg): - assert_equal(signal.medfilt2d(in_typed).dtype, dtype) - - @pytest.mark.parametrize('dtype', [np.bool_, np.complex64, np.complex128, - np.clongdouble, np.float16,]) + np.clongdouble, np.float16, np.object_, + "float96", "float128"]) def test_invalid_dtypes(self, dtype): + # 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) @@ -1115,9 +1112,9 @@ def test_invalid_dtypes(self, dtype): def test_none(self): # gh-1651, trac #1124. Ensure this does not segfault. - msg = "kernel_size exceeds volume.*|Using medfilt with arrays of dtype.*" - with pytest.warns((UserWarning, DeprecationWarning), match=msg): - assert_raises(TypeError, signal.medfilt, None) + msg = "dtype=object is not supported by medfilt" + with assert_raises(ValueError, match=msg): + signal.medfilt(None) def test_odd_strides(self): # Avoid a regression with possible contiguous @@ -1128,31 +1125,6 @@ def test_odd_strides(self): a.strides = 16 assert_(signal.medfilt(a, 1) == 5.) - def test_refcounting(self): - # Check a refcounting-related crash - a = Decimal(123) - x = np.array([a, a], dtype=object) - if hasattr(sys, 'getrefcount'): - n = 2 * sys.getrefcount(a) - else: - n = 10 - # Shouldn't segfault: - msg = "kernel_size exceeds volume.*|Using medfilt with arrays of dtype.*" - with pytest.warns((UserWarning, DeprecationWarning), match=msg): - for j in range(n): - signal.medfilt(x) - if hasattr(sys, 'getrefcount'): - assert_(sys.getrefcount(a) < n) - assert_equal(x, [a, a]) - - def test_object(self,): - msg = "Using medfilt with arrays of dtype" - with pytest.deprecated_call(match=msg): - in_object = np.array(self.IN, dtype=object) - out_object = np.array(self.OUT, dtype=object) - assert_array_equal(signal.medfilt(in_object, self.KERNEL_SIZE), - out_object) - @pytest.mark.parametrize("dtype", [np.ubyte, np.float32, np.float64]) def test_medfilt2d_parallel(self, dtype): in_typed = np.array(self.IN, dtype=dtype) From a65403806c5949513a7774eba8ad7641290b70e7 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 23 May 2024 14:20:25 -0600 Subject: [PATCH 242/500] MAINT: forward port 1.13.1 relnotes * Forward port the SciPy `1.13.1` release notes following the release yesterday. Also, try to bump the version switcher accordingly. [docs only] --- doc/source/_static/version_switcher.json | 9 +- doc/source/release.rst | 1 + doc/source/release/1.13.1-notes.rst | 102 +++++++++++++++++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 doc/source/release/1.13.1-notes.rst diff --git a/doc/source/_static/version_switcher.json b/doc/source/_static/version_switcher.json index 8fc1949c3e8b..ead99397ee43 100644 --- a/doc/source/_static/version_switcher.json +++ b/doc/source/_static/version_switcher.json @@ -5,9 +5,14 @@ "url": "https://scipy.github.io/devdocs/" }, { - "name": "1.13.0 (stable)", - "version":"1.13.0", + "name": "1.13.1 (stable)", + "version":"1.13.1", "preferred": true, + "url": "https://docs.scipy.org/doc/scipy-1.13.1/" + }, + { + "name": "1.13.0", + "version":"1.13.0", "url": "https://docs.scipy.org/doc/scipy-1.13.0/" }, { diff --git a/doc/source/release.rst b/doc/source/release.rst index 0ee4ebf88c11..1bde64f973b2 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -9,6 +9,7 @@ see the `commit logs `_. :maxdepth: 1 release/1.14.0-notes + release/1.13.1-notes release/1.13.0-notes release/1.12.0-notes release/1.11.4-notes diff --git a/doc/source/release/1.13.1-notes.rst b/doc/source/release/1.13.1-notes.rst new file mode 100644 index 000000000000..9f4ad68c8e1e --- /dev/null +++ b/doc/source/release/1.13.1-notes.rst @@ -0,0 +1,102 @@ +========================== +SciPy 1.13.1 Release Notes +========================== + +.. contents:: + +SciPy 1.13.1 is a bug-fix release with no new features +compared to 1.13.0. The version of OpenBLAS shipped with +the PyPI binaries has been increased to 0.3.27. + + + +Authors +======= +* Name (commits) +* h-vetinari (1) +* Jake Bowhay (2) +* Evgeni Burovski (6) +* Sean Cheah (2) +* Lucas Colley (2) +* DWesl (2) +* Ralf Gommers (7) +* Ben Greiner (1) + +* Matt Haberland (2) +* Gregory R. Lee (1) +* Philip Loche (1) + +* Sijo Valayakkad Manikandan (1) + +* Matti Picus (1) +* Tyler Reddy (62) +* Atsushi Sakai (1) +* Daniel Schmitz (2) +* Dan Schult (3) +* Scott Shambaugh (2) +* Edgar Andrés Margffoy Tuay (1) + +A total of 19 people contributed to this release. +People with a "+" by their names contributed a patch for the first time. +This list of names is automatically generated, and may not be fully complete. + + +Issues closed for 1.13.1 +------------------------ + +* `#19423 `__: BUG: \`scipy.ndimage.value_indices\` returns empty dict for \`intc\`/\`uintc\` dtype on Windows +* `#20264 `__: DOC, MAINT: .jupyterlite.doit.db shows up untracked +* `#20392 `__: DOC: optimize.root(method='lm') option +* `#20415 `__: BUG: csr_array can no longer be initialized with 1D array +* `#20471 `__: BUG: \`TestEig.test_falker\` fails on windows + MKL as well as... +* `#20491 `__: BUG: Cannot find \`OpenBLAS\` on Cygwin +* `#20506 `__: BUG: special.spherical_in: derivative at \`z=0, n=1\` incorrect +* `#20512 `__: BUG: \`eigh\` fails for size 1 array with driver=evd +* `#20531 `__: BUG: warning from \`optimize.least_squares\` for astropy with... +* `#20555 `__: BUG: spatial: error in \`Rotation.align_vectors()\` with an infinite... +* `#20576 `__: MAINT, TST: two types of failures observed on maintenance/1.13.x... +* `#20580 `__: BUG: scipy.special.factorial2 doesn't handle \`uint32\` dtypes +* `#20591 `__: BUG: scipy.stats.wilcoxon in 1.13 fails on 2D array with nan... +* `#20623 `__: BUG: scipy.spatial.Delaunay, scipy.interpolate.LinearNDInterpolator... +* `#20648 `__: BUG: stats.yulesimon: incorrect kurtosis values +* `#20652 `__: BUG: incorrect origin tuple handling in ndimage \`minimum_filter\`... +* `#20660 `__: BUG: spatial: \`Rotation.align_vectors()\` incorrect for anti-parallel... +* `#20670 `__: BUG: sparse matrix creation in 1.13 with indices not summing... +* `#20692 `__: BUG: stats.zipf: incorrect pmf values +* `#20714 `__: CI: scipy installation failing in umfpack tests + + +Pull requests for 1.13.1 +------------------------ + +* `#20280 `__: MAINT: added doc/source/.jupyterlite.doit.db to .gitignore See... +* `#20322 `__: BUG: sparse: align dok_array.pop() to dict.pop() for case with... +* `#20333 `__: BUG: sync pocketfft again +* `#20381 `__: REL, MAINT: prep for 1.13.1 +* `#20401 `__: DOC: optimize: fix wrong optional argument name in \`root(method="lm")\`. +* `#20435 `__: DOC: add missing deprecations from 1.13.0 release notes +* `#20437 `__: MAINT/DOC: fix syntax in 1.13.0 release notes +* `#20444 `__: BUG: sparse: Clean up 1D input handling to sparse array/matrix... +* `#20449 `__: DOC: remove spurious backtick from release notes +* `#20473 `__: BUG: linalg: fix ordering of complex conj gen eigenvalues +* `#20474 `__: TST: tolerance bumps for the conda-forge builds +* `#20484 `__: TST: compare absolute values of U and VT in pydata-sparse SVD... +* `#20505 `__: BUG: Include Python.h before system headers. +* `#20516 `__: BUG: linalg: fix eigh(1x1 array, driver='evd') f2py check +* `#20527 `__: BUG: \`spherical_in\` for \`n=0\` and \`z=0\` +* `#20530 `__: BLD: Fix error message for f2py generation fail +* `#20533 `__: TST: Adapt to \`__array__(copy=True)\` +* `#20537 `__: BLD: Move Python-including files to start of source. +* `#20567 `__: REV: 1.13.x: revert changes to f2py and tempita handling in meson.build... +* `#20569 `__: update openblas to 0.3.27 +* `#20573 `__: BUG: Fix error with 180 degree rotation in Rotation.align_vectors()... +* `#20586 `__: MAINT: optimize.linprog: fix bug when integrality is a list of... +* `#20592 `__: MAINT: stats.wilcoxon: fix failure with multidimensional \`x\`... +* `#20601 `__: MAINT: lint: temporarily disable UP031 +* `#20607 `__: BUG: handle uint arrays in factorial{,2,k} +* `#20611 `__: BUG: prevent QHull message stream being closed twice +* `#20629 `__: MAINT/DEV: lint: disable UP032 +* `#20633 `__: BUG: fix Vor/Delaunay segfaults +* `#20644 `__: BUG: ndimage.value_indices: deal with unfixed types +* `#20653 `__: BUG: ndimage: fix origin handling for \`{minimum, maximum}_filter\` +* `#20654 `__: MAINT: stats.yulesimon: fix kurtosis +* `#20687 `__: BUG: sparse: Fix summing duplicates for CSR/CSC creation from... +* `#20702 `__: BUG: stats: Fix \`zipf.pmf\` and \`zipfian.pmf\` for int32 \`k\` +* `#20727 `__: CI: pin Python for MacOS conda From 0ce8886dcebb3140e3be78f95068aa40850bd8ea Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 24 May 2024 02:37:12 -0700 Subject: [PATCH 243/500] ENH: stats: end-to-end array-API support for normality tests (#20777) --- scipy/_lib/_array_api.py | 4 +--- scipy/stats/_hypotests.py | 3 ++- scipy/stats/_morestats.py | 7 +++--- scipy/stats/_stats_py.py | 38 ++++++++++++++++++++++----------- scipy/stats/tests/test_stats.py | 3 --- 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 58885a759e64..5cce1726013f 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -460,10 +460,8 @@ def xp_minimum(x1: Array, x2: Array, /) -> Array: if hasattr(xp, 'minimum'): return xp.minimum(x1, x2) x1, x2 = xp.broadcast_arrays(x1, x2) - dtype = xp.result_type(x1.dtype, x2.dtype) - res = xp.asarray(x1, copy=True, dtype=dtype) i = (x2 < x1) | xp.isnan(x2) - res[i] = x2[i] + res = xp.where(i, x2, x1) return res[()] if res.ndim == 0 else res diff --git a/scipy/stats/_hypotests.py b/scipy/stats/_hypotests.py index 0f201403943e..835ba5c2d775 100644 --- a/scipy/stats/_hypotests.py +++ b/scipy/stats/_hypotests.py @@ -693,7 +693,8 @@ def _somers_d(A, alternative='two-sided'): with np.errstate(divide='ignore'): Z = (PA - QA)/(4*(S))**0.5 - p = scipy.stats._stats_py._get_pvalue(Z, distributions.norm, alternative) + norm = scipy.stats._stats_py._SimpleNormal() + p = scipy.stats._stats_py._get_pvalue(Z, norm, alternative) return d, p diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 9dc263099fa3..87d7485f3c42 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -17,7 +17,8 @@ from ._ansari_swilk_statistics import gscale, swilk from . import _stats_py, _wilcoxon from ._fit import FitResult -from ._stats_py import find_repeats, _get_pvalue, SignificanceResult # noqa: F401 +from ._stats_py import (find_repeats, _get_pvalue, SignificanceResult, # noqa:F401 + _SimpleNormal) from .contingency import chi2_contingency from . import distributions from ._distn_infrastructure import rv_generic @@ -2836,7 +2837,7 @@ def ansari(x, y, alternative='two-sided'): # 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) / sqrt(varAB) - pvalue = _get_pvalue(z, distributions.norm, alternative) + pvalue = _get_pvalue(z, _SimpleNormal(), alternative) return AnsariResult(AB[()], pvalue[()]) @@ -3879,7 +3880,7 @@ def mood(x, y, axis=0, alternative="two-sided"): mnM = n * (N * N - 1.0) / 12 varM = m * n * (N + 1.0) * (N + 2) * (N - 2) / 180 z = (M - mnM) / sqrt(varM) - pval = _get_pvalue(z, distributions.norm, alternative) + pval = _get_pvalue(z, _SimpleNormal(), alternative) if res_shape == (): # Return scalars, not 0-D arrays diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 91a278106771..24c4aa22f4bc 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -1423,17 +1423,19 @@ def describe(a, axis=0, ddof=1, bias=True, nan_policy='propagate'): ##################################### -def _get_pvalue(statistic, distribution, alternative, symmetric=True): +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(np.abs(statistic)) if symmetric - else np.minimum(distribution.cdf(statistic), - distribution.sf(statistic))) + pvalue = 2 * (distribution.sf(xp.abs(statistic)) if symmetric + else xp_minimum(distribution.cdf(statistic), + distribution.sf(statistic), + xp=xp)) else: message = "`alternative` must be 'less', 'greater', or 'two-sided'." raise ValueError(message) @@ -1624,9 +1626,8 @@ def skewtest(a, axis=0, nan_policy='propagate', alternative='two-sided'): y = xp.where(y == 0, xp.asarray(1, dtype=y.dtype), y) Z = delta * xp.log(y / alpha + xp.sqrt((y / alpha)**2 + 1)) - Z_np = np.asarray(Z) - pvalue = _get_pvalue(Z_np, distributions.norm, alternative) - pvalue = xp.asarray(pvalue, dtype=Z.dtype) + pvalue = _get_pvalue(Z, _SimpleNormal(), alternative) + Z = Z[()] if Z.ndim == 0 else Z pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue return SkewtestResult(Z, pvalue) @@ -1835,9 +1836,8 @@ def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): Z = (term1 - term2) / (2/(9.0*A))**0.5 # [1]_ Eq. 5 - Z_np = np.asarray(Z) - pvalue = _get_pvalue(Z_np, distributions.norm, alternative) - pvalue = xp.asarray(pvalue, dtype=Z.dtype) + pvalue = _get_pvalue(Z, _SimpleNormal(), alternative) + Z = Z[()] if Z.ndim == 0 else Z pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue return KurtosistestResult(Z, pvalue) @@ -6018,7 +6018,7 @@ def count_rank_tie(ranks): 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, distributions.norm, alternative) + pvalue = _get_pvalue(z, _SimpleNormal(), alternative) else: raise ValueError(f"Unknown method {method} specified. Use 'auto', " "'exact' or 'asymptotic'.") @@ -8814,7 +8814,7 @@ def ranksums(x, y, alternative='two-sided'): 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, distributions.norm, alternative) + pvalue = _get_pvalue(z, _SimpleNormal(), alternative) return RanksumsResult(z[()], pvalue[()]) @@ -9160,7 +9160,7 @@ def brunnermunzel(x, y, alternative="two-sided", distribution="t", distribution = distributions.t(df) elif distribution == "normal": - distribution = distributions.norm() + distribution = _SimpleNormal() else: raise ValueError( "distribution should be 't' or 'normal'") @@ -10713,3 +10713,15 @@ def first_order(t): # finding a wrong root. res = root_scalar(first_order, x0=x0, x1=x1) return res.root + + +class _SimpleNormal: + # A very simple, array-API compatible normal distribution for use in + # hypothesis tests. Will 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) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 4d7b78ad395c..9c9fe793b6a0 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6189,9 +6189,6 @@ def test_describe_empty(self, xp): stats.describe(xp.asarray([])) -@pytest.mark.skip_xp_backends(cpu_only=True, - reasons=['Uses NumPy for pvalue']) -@pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible class NormalityTests: def test_too_small(self, xp): From 561f3ac71325b8274f1d02b23029bb03e084cadf Mon Sep 17 00:00:00 2001 From: Dietrich Brunn <12721170+DietBru@users.noreply.github.com> Date: Fri, 24 May 2024 13:12:54 +0200 Subject: [PATCH 244/500] DOC: signal: Documentation improvements for `detrend` function (#20778) 1. Moved example from the tutorial file `signal.rst` into the docstring. - The tutorial only contains a plot with little explanation - so it has additional use. - Beautified the plot a little bit. 2. Added type signature to `detrend` to be more IDE friendly. 3. Explain that `numpy.polynomial.polynomial.Polynomial.fit` can be used alternatively. The `Polynomial` class has additional functionality: - The polynomial coefficients can be retrieved. - Polynomials of higher degree can be used. - Closes #17084 4. Fixed an unrelated typo in `signal.rst`. --- doc/source/tutorial/signal.rst | 38 ++------------------ scipy/signal/_signaltools.py | 63 ++++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 45 deletions(-) diff --git a/doc/source/tutorial/signal.rst b/doc/source/tutorial/signal.rst index d161f8a1d4b1..5210f90dfea1 100644 --- a/doc/source/tutorial/signal.rst +++ b/doc/source/tutorial/signal.rst @@ -1088,9 +1088,9 @@ it is the square root of the PSD. Furthermore, there is no canonical way for nam doubled spectrum. The following plot shows three different spectral representations of four sine signals -:math:`x(t)` of Eq. :math:numref:`eq_SpectA_sine` with different amplitudes durations -:math:`a` and durations :math:`\tau`. For less clutter, the spectra are centered at -:math:`f_z` and being are plotted next to each other: +:math:`x(t)` of Eq. :math:numref:`eq_SpectA_sine` with different amplitudes :math:`a` +and durations :math:`\tau`. For less clutter, the spectra are centered at :math:`f_z` +and being are plotted next to each other: .. plot:: tutorial/examples/signal_SpectralAnalysis_ContinuousSpectralRepresentations.py @@ -1992,38 +1992,6 @@ parameterizations. -Detrend -------- -.. currentmodule:: scipy.signal - -SciPy provides the function :func:`detrend` to remove a constant or linear -trend in a data series in order to see effect of higher order. - -The example below removes the constant and linear trend of a second-order -polynomial time series and plots the remaining signal components. - -.. plot:: - :alt: "This code generates an X-Y plot with no units. A red trace corresponding to the original signal curves from the bottom left to the top right. A blue trace has the constant detrend applied and is below the red trace with zero Y offset. The last black trace has the linear detrend applied and is almost flat from left to right highlighting the curve of the original signal. This last trace has an average slope of zero and looks very different." - - >>> import numpy as np - >>> import scipy.signal as signal - >>> import matplotlib.pyplot as plt - - >>> t = np.linspace(-10, 10, 20) - >>> y = 1 + t + 0.01*t**2 - >>> yconst = signal.detrend(y, type='constant') - >>> ylin = signal.detrend(y, type='linear') - - >>> plt.plot(t, y, '-rx') - >>> plt.plot(t, yconst, '-bo') - >>> plt.plot(t, ylin, '-k+') - >>> plt.grid() - >>> plt.legend(['signal', 'const. detrend', 'linear detrend']) - >>> plt.show() - - - - .. .. Filter design .. ------------- diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index fdda2a9e4417..620ae3f5ac45 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -1,11 +1,15 @@ # Author: Travis Oliphant # 1999 -- 2002 +from __future__ import annotations # Provides typing union operator `|` in Python 3.9 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 @@ -3503,9 +3507,10 @@ def vectorstrength(events, period): return strength, phase -def detrend(data, axis=-1, type='linear', bp=0, overwrite_data=False): - """ - Remove linear trend along axis from data. +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 ---------- @@ -3532,17 +3537,55 @@ def detrend(data, axis=-1, type='linear', bp=0, overwrite_data=False): ret : ndarray The detrended input data. + Notes + ----- + Detrending can be interpreted as substracting a least squares fit polyonimial: + 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 + -------- + 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 import signal - >>> rng = np.random.default_rng() - >>> npoints = 1000 - >>> noise = rng.standard_normal(npoints) - >>> x = 3 + 2*np.linspace(0, 1, npoints) + noise - >>> (signal.detrend(x) - noise).max() - 0.06 # random + >>> 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. """ if type not in ['linear', 'l', 'constant', 'c']: raise ValueError("Trend type must be 'linear' or 'constant'.") From 34fca106d85b18bd72072fed482e8ba75e301925 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 24 May 2024 07:08:29 -0700 Subject: [PATCH 245/500] TST: stats.fit: address xslow test failures (#20764) --- scipy/stats/tests/test_fit.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scipy/stats/tests/test_fit.py b/scipy/stats/tests/test_fit.py index 726657239c77..f187794d6fa3 100644 --- a/scipy/stats/tests/test_fit.py +++ b/scipy/stats/tests/test_fit.py @@ -229,8 +229,8 @@ def test_nnlf_and_related_methods(dist, params): def cases_test_fit_mle(): # These fail default test or hang - skip_basic_fit = {'argus', 'irwinhall', 'foldnorm', 'truncpareto', - 'truncweibull_min', 'ksone', 'levy_stable', + skip_basic_fit = {'argus', 'irwinhall', 'foldnorm', 'truncpareto', + 'truncweibull_min', 'ksone', 'levy_stable', 'studentized_range', 'kstwo', 'arcsine'} # Please keep this list in alphabetical order... @@ -280,7 +280,8 @@ def cases_test_fit_mse(): '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 + 'vonmises', # can have negative CDF; doesn't play nice + 'argus'} # doesn't meet tolerance; tested separately # Please keep this list in alphabetical order... slow_basic_fit = {'alpha', 'anglit', 'arcsine', 'betabinom', 'bradford', @@ -545,7 +546,8 @@ def test_arcsine(self): res = stats.fit(dist, data, shape_bounds, optimizer=self.opt) assert_nlff_less_or_close(dist, data, res.params, shapes, **self.tols) - def test_argus(self): + @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. @@ -555,7 +557,7 @@ def test_argus(self): 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) + res = stats.fit(dist, data, shape_bounds, optimizer=self.opt, method=method) assert_nlff_less_or_close(dist, data, res.params, shapes, **self.tols) @@ -697,7 +699,7 @@ 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(1963904448561) + 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)] From 0faba2c387e1fd3a4926db3d11216e80cd1eb877 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 24 May 2024 07:34:32 -0700 Subject: [PATCH 246/500] TST: stats: remove overhead of array_namespace in calls to _get_pvalue (#20781) * ENH: stats: end-to-end array-API support for normality tests * MAINT: stats: remove overhead of array_namespace in each call --- scipy/stats/_hypotests.py | 2 +- scipy/stats/_morestats.py | 4 ++-- scipy/stats/_stats_py.py | 20 ++++++++++---------- scipy/stats/_survival.py | 4 ++-- scipy/stats/_wilcoxon.py | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/scipy/stats/_hypotests.py b/scipy/stats/_hypotests.py index 835ba5c2d775..e0a1013806e1 100644 --- a/scipy/stats/_hypotests.py +++ b/scipy/stats/_hypotests.py @@ -694,7 +694,7 @@ def _somers_d(A, alternative='two-sided'): Z = (PA - QA)/(4*(S))**0.5 norm = scipy.stats._stats_py._SimpleNormal() - p = scipy.stats._stats_py._get_pvalue(Z, norm, alternative) + p = scipy.stats._stats_py._get_pvalue(Z, norm, alternative, xp=np) return d, p diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 87d7485f3c42..230617a9c8a3 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -2837,7 +2837,7 @@ def ansari(x, y, alternative='two-sided'): # 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) / sqrt(varAB) - pvalue = _get_pvalue(z, _SimpleNormal(), alternative) + pvalue = _get_pvalue(z, _SimpleNormal(), alternative, xp=np) return AnsariResult(AB[()], pvalue[()]) @@ -3880,7 +3880,7 @@ def mood(x, y, axis=0, alternative="two-sided"): mnM = n * (N * N - 1.0) / 12 varM = m * n * (N + 1.0) * (N + 2) * (N - 2) / 180 z = (M - mnM) / sqrt(varM) - pval = _get_pvalue(z, _SimpleNormal(), alternative) + pval = _get_pvalue(z, _SimpleNormal(), alternative, xp=np) if res_shape == (): # Return scalars, not 0-D arrays diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 24c4aa22f4bc..8f78d8c325bb 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -1626,7 +1626,7 @@ def skewtest(a, axis=0, nan_policy='propagate', alternative='two-sided'): y = xp.where(y == 0, xp.asarray(1, dtype=y.dtype), y) Z = delta * xp.log(y / alpha + xp.sqrt((y / alpha)**2 + 1)) - pvalue = _get_pvalue(Z, _SimpleNormal(), alternative) + pvalue = _get_pvalue(Z, _SimpleNormal(), alternative, xp=xp) Z = Z[()] if Z.ndim == 0 else Z pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue @@ -1836,7 +1836,7 @@ def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): Z = (term1 - term2) / (2/(9.0*A))**0.5 # [1]_ Eq. 5 - pvalue = _get_pvalue(Z, _SimpleNormal(), alternative) + pvalue = _get_pvalue(Z, _SimpleNormal(), alternative, xp=xp) Z = Z[()] if Z.ndim == 0 else Z pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue @@ -4952,7 +4952,7 @@ def statistic(x, y, axis): # This needs to be done with NumPy arrays given the existing infrastructure. ab = n/2 - 1 dist = stats.beta(ab, ab, loc=-1, scale=2) - pvalue = _get_pvalue(np.asarray(r), dist, alternative) + pvalue = _get_pvalue(np.asarray(r), dist, alternative, xp=np) pvalue = xp.asarray(pvalue, dtype=dtype) r = r[()] if r.ndim == 0 else r @@ -5572,7 +5572,7 @@ def spearmanr(a, b=None, axis=0, nan_policy='propagate', # errors before taking the square root t = rs * np.sqrt((dof/((rs+1.0)*(1.0-rs))).clip(0)) - prob = _get_pvalue(t, distributions.t(dof), alternative) + prob = _get_pvalue(t, distributions.t(dof), alternative, xp=np) # For backwards compatibility, return scalars when comparing 2 columns if rs.shape == (2, 2): @@ -6018,7 +6018,7 @@ def count_rank_tie(ranks): 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) + pvalue = _get_pvalue(z, _SimpleNormal(), alternative, xp=np) else: raise ValueError(f"Unknown method {method} specified. Use 'auto', " "'exact' or 'asymptotic'.") @@ -6487,7 +6487,7 @@ def ttest_1samp(a, popmean, axis=0, nan_policy='propagate', # `from_dlpack` will enable the transfer from other devices, and # `_get_pvalue` will even be reworked to support the native backend. t_np = np.asarray(t) - prob = _get_pvalue(t_np, distributions.t(df), alternative) + prob = _get_pvalue(t_np, distributions.t(df), alternative, xp=np) prob = xp.asarray(prob, dtype=t.dtype) prob = prob[()] if prob.ndim == 0 else prob @@ -6538,7 +6538,7 @@ def _ttest_ind_from_stats(mean1, mean2, denom, df, alternative): d = mean1 - mean2 with np.errstate(divide='ignore', invalid='ignore'): t = np.divide(d, denom)[()] - prob = _get_pvalue(t, distributions.t(df), alternative) + prob = _get_pvalue(t, distributions.t(df), alternative, xp=np) return (t, prob) @@ -7340,7 +7340,7 @@ def ttest_rel(a, b, axis=0, nan_policy='propagate', alternative="two-sided"): with np.errstate(divide='ignore', invalid='ignore'): t = np.divide(dm, denom)[()] - prob = _get_pvalue(t, distributions.t(df), alternative) + prob = _get_pvalue(t, distributions.t(df), alternative, xp=np) # when nan_policy='omit', `df` can be different for different axis-slices df = np.broadcast_to(df, t.shape)[()] @@ -8814,7 +8814,7 @@ def ranksums(x, y, alternative='two-sided'): 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) + pvalue = _get_pvalue(z, _SimpleNormal(), alternative, xp=np) return RanksumsResult(z[()], pvalue[()]) @@ -9165,7 +9165,7 @@ def brunnermunzel(x, y, alternative="two-sided", distribution="t", raise ValueError( "distribution should be 't' or 'normal'") - p = _get_pvalue(-wbfn, distribution, alternative) + p = _get_pvalue(-wbfn, distribution, alternative, xp=np) return BrunnerMunzelResult(wbfn, p) diff --git a/scipy/stats/_survival.py b/scipy/stats/_survival.py index f467f23522c5..702aeb108df8 100644 --- a/scipy/stats/_survival.py +++ b/scipy/stats/_survival.py @@ -8,7 +8,6 @@ from scipy import special, interpolate, stats from scipy.stats._censored_data import CensoredData from scipy.stats._common import ConfidenceInterval -from scipy.stats import norm # type: ignore[attr-defined] if TYPE_CHECKING: from typing import Literal @@ -681,6 +680,7 @@ def logrank( statistic = (n_died_x - sum_exp_deaths_x)/np.sqrt(sum_var) # Equivalent to chi2(df=1).sf(statistic**2) when alternative='two-sided' - pvalue = stats._stats_py._get_pvalue(statistic, norm, alternative) + 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/scipy/stats/_wilcoxon.py b/scipy/stats/_wilcoxon.py index 2fbdfe148031..ae85d6035245 100644 --- a/scipy/stats/_wilcoxon.py +++ b/scipy/stats/_wilcoxon.py @@ -2,7 +2,7 @@ import numpy as np from scipy import stats -from ._stats_py import _get_pvalue, _rankdata +from ._stats_py import _get_pvalue, _rankdata, _SimpleNormal from . import _morestats from ._axis_nan_policy import _broadcast_arrays from ._hypotests import _get_wilcoxon_distr @@ -212,7 +212,7 @@ def _wilcoxon_nd(x, y=None, zero_method='wilcox', correction=True, if correction: sign = _correction_sign(z, alternative) z -= sign * 0.5 / se - p = _get_pvalue(z, stats.norm, alternative) + p = _get_pvalue(z, _SimpleNormal(), alternative, xp=np) elif method == 'exact': dist = WilcoxonDistribution(count) if alternative == 'less': From 19dcd9fbaaf99cf0020f050b82b7054ef5dbccf6 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Fri, 24 May 2024 15:38:00 +0100 Subject: [PATCH 247/500] DEP: integrate.quad_vec: deprecate `quadrature="trapz"` (#20716) * MAINT: remove functions from refguide check that no longer exist * DEP integrate.quad_vec: deprecate quadrature="trapz" --------- Co-authored-by: h-vetinari --- scipy/integrate/_quad_vec.py | 7 +++++++ scipy/integrate/tests/test__quad_vec.py | 4 ++++ tools/refguide_check.py | 3 --- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/scipy/integrate/_quad_vec.py b/scipy/integrate/_quad_vec.py index 7b3382baad78..19218d196eb3 100644 --- a/scipy/integrate/_quad_vec.py +++ b/scipy/integrate/_quad_vec.py @@ -3,6 +3,7 @@ import heapq import collections import functools +import warnings import numpy as np @@ -295,6 +296,12 @@ def quad_vec(f, a, b, epsabs=1e-200, epsrel=1e-8, norm='2', cache_size=100e6, except KeyError as e: raise ValueError(f"unknown quadrature {quadrature!r}") from e + if quadrature == "trapz": + msg = ("`quadrature='trapz'` is deprecated in favour of " + "`quadrature='trapezoid' and will raise an error from SciPy 1.16.0 " + "onwards.") + warnings.warn(msg, DeprecationWarning, stacklevel=2) + # Initial interval set if points is None: initial_intervals = [(a, b)] diff --git a/scipy/integrate/tests/test__quad_vec.py b/scipy/integrate/tests/test__quad_vec.py index 9bc400640117..316b905c8e5e 100644 --- a/scipy/integrate/tests/test__quad_vec.py +++ b/scipy/integrate/tests/test__quad_vec.py @@ -207,3 +207,7 @@ def f(x): for p in interval_sets: j = np.searchsorted(sorted(points), tuple(p)) assert np.all(j == j[0]) + +def test_trapz_deprecation(): + with pytest.deprecated_call(match="`quadrature='trapz'`"): + quad_vec(lambda x: x, 0, 1, quadrature="trapz") diff --git a/tools/refguide_check.py b/tools/refguide_check.py index c895a30dbf32..e96c89cb5b05 100755 --- a/tools/refguide_check.py +++ b/tools/refguide_check.py @@ -101,9 +101,6 @@ r'scipy\.special\..*_roots', # old aliases for scipy.special.*_roots r'scipy\.special\.jn', # alias for jv r'scipy\.ndimage\.sum', # alias for sum_labels - r'scipy\.integrate\.simps', # alias for simpson - r'scipy\.integrate\.trapz', # alias for trapezoid - r'scipy\.integrate\.cumtrapz', # alias for cumulative_trapezoid r'scipy\.linalg\.solve_lyapunov', # deprecated name r'scipy\.stats\.contingency\.chi2_contingency', r'scipy\.stats\.contingency\.expected_freq', From f4feaf17dd162265bd050ebe5bfeada44504243b Mon Sep 17 00:00:00 2001 From: Kai Date: Sat, 25 May 2024 11:56:45 +0800 Subject: [PATCH 248/500] DOC: stats: gstd: Give more general formula Co-authored-by: Matt Haberland --- scipy/stats/_stats_py.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 76291d5139c7..2d89626527b8 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -3193,14 +3193,12 @@ def gstd(a, axis=0, ddof=1): .. math:: - s_G = \exp(s_y), s_y = \sqrt{ \frac{1}{n - 1} \sum_{i=1}^n (y_i - \bar y)^2 } + 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 and :math:`\bar y` denotes the - mean of the natural logarithms of the observations. - - Note that the default ``ddof=1``, which corresponds with the :math:`n - 1` term - above, is different from the default value used by similar functions, such as - `numpy.std` and `numpy.var`. + 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 From 0b3b24cd8d2075664887d68d762f85e647a15f8c Mon Sep 17 00:00:00 2001 From: Kai Date: Sat, 25 May 2024 11:57:51 +0800 Subject: [PATCH 249/500] TST: stats: gstd: Test behaviour for dof <= 0 Co-authored-by: Matt Haberland --- scipy/stats/tests/test_stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 00289d8e475c..e0dfb5e8814c 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6845,7 +6845,7 @@ def test_propagates_nan_values(self): def test_ddof_equal_to_number_of_observations(self): with pytest.warns(RuntimeWarning, match='Degrees of freedom <= 0'): - stats.gstd(self.array_1d, ddof=self.array_1d.size) + assert_equal(stats.gstd(self.array_1d, ddof=self.array_1d.size), np.inf) def test_3d_array(self): gstd_actual = stats.gstd(self.array_3d, axis=None) From 63f4f731be2db670cf1e3a785cfe99970394d37b Mon Sep 17 00:00:00 2001 From: h-vetinari Date: Sat, 25 May 2024 19:52:20 +1100 Subject: [PATCH 250/500] DEP: interpolate: replace interp2d by stub (#20497) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Melissa Weber Mendonça --- benchmarks/benchmarks/interpolate.py | 3 - .../notebooks/interp_transition_guide.md | 254 ----------------- doc/source/tutorial/index.rst | 2 - doc/source/tutorial/interpolate.rst | 2 +- .../interpolate/interp_transition_guide.md | 247 +++++++++++++++++ .../interpolate/plots/output_10_0.png | Bin 0 -> 176464 bytes .../interpolate/plots/output_12_0.png | Bin 0 -> 176464 bytes .../interpolate/plots/output_18_0.png | Bin 0 -> 98141 bytes .../interpolate/plots/output_20_0.png | Bin 0 -> 98141 bytes .../interpolate/plots/output_28_1.png | Bin 0 -> 182276 bytes .../interpolate/plots/output_34_1.png | Bin 0 -> 139535 bytes .../interpolate/plots/output_36_1.png | Bin 0 -> 129249 bytes .../interpolate/plots/output_38_1.png | Bin 0 -> 103218 bytes scipy/interpolate/_interpolate.py | 259 +----------------- scipy/interpolate/tests/test_interpolate.py | 95 +------ tools/refguide_check.py | 3 +- 16 files changed, 268 insertions(+), 597 deletions(-) delete mode 100644 doc/source/notebooks/interp_transition_guide.md create mode 100644 doc/source/tutorial/interpolate/interp_transition_guide.md create mode 100644 doc/source/tutorial/interpolate/plots/output_10_0.png create mode 100644 doc/source/tutorial/interpolate/plots/output_12_0.png create mode 100644 doc/source/tutorial/interpolate/plots/output_18_0.png create mode 100644 doc/source/tutorial/interpolate/plots/output_20_0.png create mode 100644 doc/source/tutorial/interpolate/plots/output_28_1.png create mode 100644 doc/source/tutorial/interpolate/plots/output_34_1.png create mode 100644 doc/source/tutorial/interpolate/plots/output_36_1.png create mode 100644 doc/source/tutorial/interpolate/plots/output_38_1.png diff --git a/benchmarks/benchmarks/interpolate.py b/benchmarks/benchmarks/interpolate.py index a2c85d282d60..660bb6ffeaf8 100644 --- a/benchmarks/benchmarks/interpolate.py +++ b/benchmarks/benchmarks/interpolate.py @@ -150,9 +150,6 @@ def setup(self, n_samples, method): self.xx, self.yy = np.meshgrid(self.x, self.y) self.z = np.sin(self.xx**2+self.yy**2) - def time_interpolate(self, n_samples, method): - interpolate.interp2d(self.x, self.y, self.z, kind=method) - class Rbf(Benchmark): param_names = ['n_samples', 'function'] diff --git a/doc/source/notebooks/interp_transition_guide.md b/doc/source/notebooks/interp_transition_guide.md deleted file mode 100644 index 6a1cb5476b79..000000000000 --- a/doc/source/notebooks/interp_transition_guide.md +++ /dev/null @@ -1,254 +0,0 @@ ---- -jupytext: - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.14.0 -kernelspec: - display_name: Python 3 (ipykernel) - language: python - name: python3 ---- - -# Interpolate transition guide - -This notebook contains three sets of demonstrations: - -- lower-level FITPACK replacements for {class}`scipy.interpolate.interp2d` for legacy bug-for-bug compatible {class}`scipy.interpolate.interp2d` replacements; -- recommended replacements for {class}`scipy.interpolate.interp2d` for use in new code; -- a demonstration of failure modes of 2D FITPACK-based linear interpolation and recommended replacements. - -**Note:** Since this notebook shows usage of `interp2d` (which is marked for deprecation), we will silence deprecation warnings for simplicity: - -```{code-cell} ipython3 -import warnings -warnings.filterwarnings('ignore') -``` - -## 1. How to transition away from using `interp2d` - -`interp2d` silently switches between interpolation on a 2D regular grid and interpolating 2D scattered data. The switch is based on the lengths of the (raveled) `x`, `y`, and `z` arrays. In short, for regular grid use {class}`scipy.interpolate.RectBivariateSpline`; for scattered interpolation, use the `bisprep/bisplev` combo. Below we give examples of the literal point-for-point transition, which should preserve the `interp2d` results exactly. - -+++ - -### 1.1 `interp2d` on a regular grid - -We start from the (slightly modified) docstring example. - -```{code-cell} ipython3 -import numpy as np -import matplotlib.pyplot as plt -from scipy.interpolate import interp2d, RectBivariateSpline - -x = np.arange(-5.01, 5.01, 0.25) -y = np.arange(-5.01, 7.51, 0.25) -xx, yy = np.meshgrid(x, y) -z = np.sin(xx**2 + 2.*yy**2) -f = interp2d(x, y, z, kind='cubic') -``` - -This is the "regular grid" code path, because - -```{code-cell} ipython3 -z.size == len(x) * len(y) -``` - -Also, note that `x.size != y.size`: - -```{code-cell} ipython3 -x.size, y.size -``` - -Now, let's build a convenience function to construct the interpolator and plot it. - -```{code-cell} ipython3 -def plot(f, xnew, ynew): - fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4)) - znew = f(xnew, ynew) - - ax1.plot(x, z[0, :], 'ro-', xnew, znew[0, :], 'b-') - - im = ax2.imshow(znew) - plt.colorbar(im, ax=ax2) - - plt.show() - return znew -``` - -Plotting: - -```{code-cell} ipython3 -xnew = np.arange(-5.01, 5.01, 1e-2) -ynew = np.arange(-5.01, 7.51, 1e-2) -znew_i = plot(f, xnew, ynew) -``` - -#### Replacement: Use `RectBivariateSpline`, the result is identical - -Note the transposes: first, in the constructor, second, you need to transpose the result of the evaluation. This is to undo the transposes `interp2d` does. - -```{code-cell} ipython3 -r = RectBivariateSpline(x, y, z.T) - -rt = lambda xnew, ynew: r(xnew, ynew).T -znew_r = plot(rt, xnew, ynew) -``` - -```{code-cell} ipython3 -from numpy.testing import assert_allclose -assert_allclose(znew_i, znew_r, atol=1e-14) -``` - -### 1.2. `interp2d` with full coordinates of points (scattered interpolation) - -Here, we flatten the meshgrid from the previous exercise to illustrate the functionality. - -```{code-cell} ipython3 -xxr = xx.ravel() -yyr = yy.ravel() -zzr = z.ravel() - -f = interp2d(xxr, yyr, zzr, kind='cubic') -``` - -Note that this the "not regular grid" code path, meant for scattered data, with `len(x) == len(y) == len(z)`. - -```{code-cell} ipython3 -len(xxr) == len(yyr) == len(zzr) -``` - -```{code-cell} ipython3 -xnew = np.arange(-5.01, 5.01, 1e-2) -ynew = np.arange(-5.01, 7.51, 1e-2) -znew_i = plot(f, xnew, ynew) -``` - -#### Replacement: Use {class}`scipy.interpolate.bisplrep` / {class}`scipy.interpolate.bisplev` directly - -```{code-cell} ipython3 -from scipy.interpolate import bisplrep, bisplev -tck = bisplrep(xxr, yyr, zzr, kx=3, ky=3, s=0) -# convenience: make up a callable from bisplev -ff = lambda xnew, ynew: bisplev(xnew, ynew, tck).T # Note the transpose, to mimic what interp2d does - -znew_b = plot(ff, xnew, ynew) -``` - -```{code-cell} ipython3 -assert_allclose(znew_i, znew_b, atol=1e-15) -``` - -## 2. Alternative to `interp2d`: regular grid - -For new code, the recommended alternative is `RegularGridInterpolator`. It is an independent implementation, not based on FITPACK. Supports nearest, linear interpolation and odd-order tensor product splines. - -The spline knots are guaranteed to coincide with the data points. - -Note that, here: -1. the tuple argument, is `(x, y)` -2. `z` array needs a transpose -3. the keyword name is *method*, not *kind* -4. `bounds_error` argument is `True` by default. - -```{code-cell} ipython3 -from scipy.interpolate import RegularGridInterpolator as RGI - -r = RGI((x, y), z.T, method='linear', bounds_error=False) -``` - -Evaluation: create a 2D meshgrid. Use indexing='ij' and `sparse=True` to save some memory: - -```{code-cell} ipython3 -xxnew, yynew = np.meshgrid(xnew, ynew, indexing='ij', sparse=True) -``` - -Evaluate, note the tuple argument: - -```{code-cell} ipython3 -znew_reggrid = r((xxnew, yynew)) -``` - -```{code-cell} ipython3 -fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4)) - -# Again, note the transpose to undo the `interp2d` convention -znew_reggrid_t = znew_reggrid.T - -ax1.plot(x, z[0, :], 'ro-', xnew, znew_reggrid_t[0, :], 'b-') - -im = ax2.imshow(znew_reggrid_t) -plt.colorbar(im, ax=ax2) -``` - -## 3. Scattered 2D linear interpolation: prefer `LinearNDInterpolator` to `SmoothBivariateSpline` or `bisplrep` - -For 2D scattered linear interpolation, both `SmoothBivariateSpline` and `biplrep` may either emit warnings, or fail to interpolate the data, or produce splines which with knots away from the data points. "Instead, prefer `LinearNDInterpolator`, which is based on triangulating the data via `QHull`. - -```{code-cell} ipython3 -# TestSmoothBivariateSpline::test_integral -from scipy.interpolate import SmoothBivariateSpline, LinearNDInterpolator - -x = np.array([1,1,1,2,2,2,4,4,4]) -y = np.array([1,2,3,1,2,3,1,2,3]) -z = np.array([0,7,8,3,4,7,1,3,4]) -``` - -Now, use the linear interpolation over Qhull-based triangulation of data: - -```{code-cell} ipython3 -xy = np.c_[x, y] # or just list(zip(x, y)) -lut2 = LinearNDInterpolator(xy, z) - -X = np.linspace(min(x), max(x)) -Y = np.linspace(min(y), max(y)) -X, Y = np.meshgrid(X, Y) -``` - -The result is easy to understand and interpret: - -```{code-cell} ipython3 -fig = plt.figure() -ax = fig.add_subplot(projection='3d') - -ax.plot_wireframe(X, Y, lut2(X, Y)) -ax.scatter(x, y, z, 'o', color='k', s=48) -``` - -Note that `bisplrep` does something different! It may place spline knots outside of the data. - -For illustration, consider the same data from the previous example: - -```{code-cell} ipython3 -tck = bisplrep(x, y, z, kx=1, ky=1, s=0) - -fig = plt.figure() -ax = fig.add_subplot(projection='3d') - -xx = np.linspace(min(x), max(x)) -yy = np.linspace(min(y), max(y)) -X, Y = np.meshgrid(xx, yy) -Z = bisplev(xx, yy, tck) -Z = Z.reshape(*X.shape).T - -ax.plot_wireframe(X, Y, Z, rstride=2, cstride=2) -ax.scatter(x, y, z, 'o', color='k', s=48) -``` - -Also, `SmoothBivariateSpline` fails to interpolate the data. Again, use the same data from the previous example. - -```{code-cell} ipython3 -lut = SmoothBivariateSpline(x, y, z, kx=1, ky=1, s=0) - -fig = plt.figure() -ax = fig.add_subplot(projection='3d') - -xx = np.linspace(min(x), max(x)) -yy = np.linspace(min(y), max(y)) -X, Y = np.meshgrid(xx, yy) - -ax.plot_wireframe(X, Y, lut(xx, yy).T, rstride=4, cstride=4) -ax.scatter(x, y, z, 'o', color='k', s=48) -``` - -Note that both `SmoothBivariateSpline` and `bisplrep` results have artifacts, unlike the `LinearNDInterpolator`'s. Issues illustrated here were reported for linear interpolation, however the FITPACK knot-selection mechanism does not guarantee to avoid either of these issues for higher-order (e.g. cubic) spline surfaces. diff --git a/doc/source/tutorial/index.rst b/doc/source/tutorial/index.rst index b209245083c8..c20ab5da69c7 100644 --- a/doc/source/tutorial/index.rst +++ b/doc/source/tutorial/index.rst @@ -82,8 +82,6 @@ These can be opened as Jupyter Notebooks with the help of the .. toctree:: :caption: Executable tutorials :maxdepth: 1 - - ../notebooks/interp_transition_guide .. raw:: latex diff --git a/doc/source/tutorial/interpolate.rst b/doc/source/tutorial/interpolate.rst index 3cb8bda5ddbd..982df11710cb 100644 --- a/doc/source/tutorial/interpolate.rst +++ b/doc/source/tutorial/interpolate.rst @@ -68,4 +68,4 @@ Further details are given in the links below. interpolate/ND_regular_grid interpolate/ND_unstructured interpolate/extrapolation_examples - + interpolate/interp_transition_guide.md diff --git a/doc/source/tutorial/interpolate/interp_transition_guide.md b/doc/source/tutorial/interpolate/interp_transition_guide.md new file mode 100644 index 000000000000..6fdf9f71793b --- /dev/null +++ b/doc/source/tutorial/interpolate/interp_transition_guide.md @@ -0,0 +1,247 @@ +(interp-transition-guide)= +# Interpolate transition guide + +This page contains three sets of demonstrations: + +- lower-level FITPACK replacements for {class}`scipy.interpolate.interp2d` for legacy bug-for-bug compatible {class}`scipy.interpolate.interp2d` replacements; +- recommended replacements for {class}`scipy.interpolate.interp2d` for use in new code; +- a demonstration of failure modes of 2D FITPACK-based linear interpolation and recommended replacements. + +## 1. How to transition away from using `interp2d` + +`interp2d` silently switches between interpolation on a 2D regular grid and interpolating 2D scattered data. The switch is based on the lengths of the (raveled) `x`, `y`, and `z` arrays. In short, for regular grid use {class}`scipy.interpolate.RectBivariateSpline`; for scattered interpolation, use the `bisprep/bisplev` combo. Below we give examples of the literal point-for-point transition, which should preserve the `interp2d` results exactly. + +### 1.1 `interp2d` on a regular grid + +We start from the (slightly modified) docstring example. + +``` +>>> import numpy as np +>>> import matplotlib.pyplot as plt +>>> from scipy.interpolate import interp2d, RectBivariateSpline + +>>> x = np.arange(-5.01, 5.01, 0.25) +>>> y = np.arange(-5.01, 7.51, 0.25) +>>> xx, yy = np.meshgrid(x, y) +>>> z = np.sin(xx**2 + 2.*yy**2) +>>> f = interp2d(x, y, z, kind='cubic') +``` + +This is the "regular grid" code path, because + +``` +>>> z.size == len(x) * len(y) +True +``` + +Also, note that `x.size != y.size`: + +``` +>>> x.size, y.size +(41, 51) +``` + +Now, let's build a convenience function to construct the interpolator and plot it. + +``` +>>> def plot(f, xnew, ynew): +... fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4)) +... znew = f(xnew, ynew) +... ax1.plot(x, z[0, :], 'ro-', xnew, znew[0, :], 'b-') +... im = ax2.imshow(znew) +... plt.colorbar(im, ax=ax2) +... plt.show() +... return znew +... +>>> xnew = np.arange(-5.01, 5.01, 1e-2) +>>> ynew = np.arange(-5.01, 7.51, 1e-2) +>>> znew_i = plot(f, xnew, ynew) +``` + +![Two plots side by side. On the left, the plot shows points with coordinates +(x, z[0, :]) as red circles, and the interpolation function generated as a blue +curve. On the right, the plot shows a 2D projection of the generated +interpolation function.](plots/output_10_0.png) + +#### Replacement: Use `RectBivariateSpline`, the result is identical + +Note the transposes: first, in the constructor, second, you need to transpose the result of the evaluation. This is to undo the transposes `interp2d` does. + +``` +>>> r = RectBivariateSpline(x, y, z.T) +>>> rt = lambda xnew, ynew: r(xnew, ynew).T +>>> znew_r = plot(rt, xnew, ynew) +``` + +![Two plots side by side. On the left, the plot shows points with coordinates +(x, z[0, :]) as red circles, and the interpolation function generated as a blue +curve. On the right, the plot shows a 2D projection of the generated +interpolation function.](plots/output_12_0.png) + +``` +>>> from numpy.testing import assert_allclose +>>> assert_allclose(znew_i, znew_r, atol=1e-14) +``` + +### 1.2. `interp2d` with full coordinates of points (scattered interpolation) + +Here, we flatten the meshgrid from the previous exercise to illustrate the functionality. + +``` +>>> xxr = xx.ravel() +>>> yyr = yy.ravel() +>>> zzr = z.ravel() +>>> f = interp2d(xxr, yyr, zzr, kind='cubic') +``` + +Note that this the "not regular grid" code path, meant for scattered data, with `len(x) == len(y) == len(z)`. + +``` +>>> len(xxr) == len(yyr) == len(zzr) +True +``` + +``` +>>> xnew = np.arange(-5.01, 5.01, 1e-2) +>>> ynew = np.arange(-5.01, 7.51, 1e-2) +>>> znew_i = plot(f, xnew, ynew) +``` + +![Two plots side by side. On the left, the plot shows points with coordinates +(x, z[0, :]) as red circles, and the interpolation function generated as a blue +curve. On the right, the plot shows a 2D projection of the generated +interpolation function.](plots/output_18_0.png) + +#### Replacement: Use {class}`scipy.interpolate.bisplrep` / {class}`scipy.interpolate.bisplev` directly + +``` +>>> from scipy.interpolate import bisplrep, bisplev +>>> tck = bisplrep(xxr, yyr, zzr, kx=3, ky=3, s=0) +# convenience: make up a callable from bisplev +>>> ff = lambda xnew, ynew: bisplev(xnew, ynew, tck).T # Note the transpose, to mimic what interp2d does +>>> znew_b = plot(ff, xnew, ynew) +``` + +![Two plots side by side. On the left, the plot shows points with coordinates +(x, z[0, :]) as red circles, and the interpolation function generated as a blue +curve. On the right, the plot shows a 2D projection of the generated +interpolation function.](plots/output_20_0.png) + +``` +>>> assert_allclose(znew_i, znew_b, atol=1e-15) +``` + +## 2. Alternative to `interp2d`: regular grid + +For new code, the recommended alternative is `RegularGridInterpolator`. It is an independent implementation, not based on FITPACK. Supports nearest, linear interpolation and odd-order tensor product splines. + +The spline knots are guaranteed to coincide with the data points. + +Note that, here: +1. the tuple argument, is `(x, y)` +2. `z` array needs a transpose +3. the keyword name is *method*, not *kind* +4. `bounds_error` argument is `True` by default. + +``` +>>> from scipy.interpolate import RegularGridInterpolator as RGI +>>> r = RGI((x, y), z.T, method='linear', bounds_error=False) +``` + +Evaluation: create a 2D meshgrid. Use indexing='ij' and `sparse=True` to save some memory: + +``` +>>> xxnew, yynew = np.meshgrid(xnew, ynew, indexing='ij', sparse=True) +``` + +Evaluate, note the tuple argument: + +``` +>>> znew_reggrid = r((xxnew, yynew)) +``` + +``` +>>> fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4)) +# Again, note the transpose to undo the `interp2d` convention +>>> znew_reggrid_t = znew_reggrid.T +>>> ax1.plot(x, z[0, :], 'ro-', xnew, znew_reggrid_t[0, :], 'b-') +>>> im = ax2.imshow(znew_reggrid_t) +>>> plt.colorbar(im, ax=ax2) +``` + +![Two plots side by side. On the left, the plot shows points with coordinates +(x, z[0, :]) as red circles, and the interpolation function generated as a blue +curve. On the right, the plot shows a 2D projection of the generated +interpolation function.](plots/output_28_1.png) + +## 3. Scattered 2D linear interpolation: prefer `LinearNDInterpolator` to `SmoothBivariateSpline` or `bisplrep` + +For 2D scattered linear interpolation, both `SmoothBivariateSpline` and `biplrep` may either emit warnings, or fail to interpolate the data, or produce splines which with knots away from the data points. Instead, prefer `LinearNDInterpolator`, which is based on triangulating the data via `QHull`. + +``` +# TestSmoothBivariateSpline::test_integral +>>> from scipy.interpolate import SmoothBivariateSpline, LinearNDInterpolator +>>> x = np.array([1,1,1,2,2,2,4,4,4]) +>>> y = np.array([1,2,3,1,2,3,1,2,3]) +>>> z = np.array([0,7,8,3,4,7,1,3,4]) +``` + +Now, use the linear interpolation over Qhull-based triangulation of data: + +``` +>>> xy = np.c_[x, y] # or just list(zip(x, y)) +>>> lut2 = LinearNDInterpolator(xy, z) +>>> X = np.linspace(min(x), max(x)) +>>> Y = np.linspace(min(y), max(y)) +>>> X, Y = np.meshgrid(X, Y) +``` + +The result is easy to understand and interpret: + +``` +>>> fig = plt.figure() +>>> ax = fig.add_subplot(projection='3d') +>>> ax.plot_wireframe(X, Y, lut2(X, Y)) +>>> ax.scatter(x, y, z, 'o', color='k', s=48) +``` + +![3D plot of a piecewise-linear surface as a blue grid, with the +(x, y, z)-coordinate points represented as black circles.](plots/output_34_1.png) + +Note that `bisplrep` does something different! It may place spline knots outside of the data. + +For illustration, consider the same data from the previous example: + +``` +>>> tck = bisplrep(x, y, z, kx=1, ky=1, s=0) +>>> fig = plt.figure() +>>> ax = fig.add_subplot(projection='3d') +>>> xx = np.linspace(min(x), max(x)) +>>> yy = np.linspace(min(y), max(y)) +>>> X, Y = np.meshgrid(xx, yy) +>>> Z = bisplev(xx, yy, tck) +>>> Z = Z.reshape(*X.shape).T +>>> ax.plot_wireframe(X, Y, Z, rstride=2, cstride=2) +>>> ax.scatter(x, y, z, 'o', color='k', s=48) +``` + +![3D plot of a piecewise-linear surface as a blue grid, with the +(x, y, z)-coordinate points represented as black circles.](plots/output_36_1.png) + +Also, `SmoothBivariateSpline` fails to interpolate the data. Again, use the same data from the previous example. + +``` +>>> lut = SmoothBivariateSpline(x, y, z, kx=1, ky=1, s=0) +>>> fig = plt.figure() +>>> ax = fig.add_subplot(projection='3d') +>>> xx = np.linspace(min(x), max(x)) +>>> yy = np.linspace(min(y), max(y)) +>>> X, Y = np.meshgrid(xx, yy) +>>> ax.plot_wireframe(X, Y, lut(xx, yy).T, rstride=4, cstride=4) +>>> ax.scatter(x, y, z, 'o', color='k', s=48) +``` + +![3D plot of a piecewise-linear surface as a blue grid, with the +(x, y, z)-coordinate points represented as black circles.](plots/output_38_1.png) + +Note that both `SmoothBivariateSpline` and `bisplrep` results have artifacts, unlike the `LinearNDInterpolator`'s. Issues illustrated here were reported for linear interpolation, however the FITPACK knot-selection mechanism does not guarantee to avoid either of these issues for higher-order (e.g. cubic) spline surfaces. diff --git a/doc/source/tutorial/interpolate/plots/output_10_0.png b/doc/source/tutorial/interpolate/plots/output_10_0.png new file mode 100644 index 0000000000000000000000000000000000000000..e815828e451edc4d539347dd92fc78f5e2863dcd GIT binary patch literal 176464 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 hhJ^p;;y4;RsM|Ep!HSO3e$8I+stD+ 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≷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 hhJ^p;;y4;RsM|Ep!HSO3e$8I+stD+ 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≷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%0D|jiO!F;$0G{Q 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 literal 0 HcmV?d00001 diff --git a/doc/source/tutorial/interpolate/plots/output_36_1.png b/doc/source/tutorial/interpolate/plots/output_36_1.png new file mode 100644 index 0000000000000000000000000000000000000000..3e0dafcd8086ae557ec7e9c6e667dad7a7fa64f6 GIT binary patch literal 129249 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⁣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

?7Qylm)6GLA+`S6x7MQMb5zYw1 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 literal 0 HcmV?d00001 diff --git a/doc/source/tutorial/interpolate/plots/output_38_1.png b/doc/source/tutorial/interpolate/plots/output_38_1.png new file mode 100644 index 0000000000000000000000000000000000000000..fc3c86a6639863d338999138188861c0d0827b1f GIT binary patch literal 103218 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

8MYOw3_-QvqzVw2-gMKjm`=QJ&^`M#Lqiw~ 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 Ci*eKd literal 0 HcmV?d00001 diff --git a/scipy/interpolate/_interpolate.py b/scipy/interpolate/_interpolate.py index fa37db0e78ae..93797be9d639 100644 --- a/scipy/interpolate/_interpolate.py +++ b/scipy/interpolate/_interpolate.py @@ -1,18 +1,15 @@ __all__ = ['interp1d', 'interp2d', 'lagrange', 'PPoly', 'BPoly', 'NdPPoly'] from math import prod -import warnings import numpy as np -from numpy import (array, transpose, searchsorted, atleast_1d, atleast_2d, - ravel, poly1d, asarray, intp) +from numpy import array, asarray, intp, poly1d, searchsorted import scipy.special as spec from scipy._lib._util import copy_if_needed from scipy.special import comb from . import _fitpack_py -from . import _dfitpack as dfitpack from ._polyint import _Interpolator1D from . import _ppoly from .interpnd import _ndim_coords_from_arrays @@ -93,8 +90,8 @@ def lagrange(x, w): # !! found, get rid of it! -dep_mesg = """\ -`interp2d` is deprecated in SciPy 1.10 and will be removed in SciPy 1.14.0. +err_mesg = """\ +`interp2d` has been removed in SciPy 1.14.0. For legacy code, nearly bug-for-bug compatible replacements are `RectBivariateSpline` on regular grids, and `bisplrep`/`bisplev` for @@ -105,7 +102,7 @@ def lagrange(x, w): `CloughTocher2DInterpolator`. For more details see -`https://scipy.github.io/devdocs/notebooks/interp_transition_guide.html` +https://scipy.github.io/devdocs/tutorial/interpolate/interp_transition_guide.html """ class interp2d: @@ -113,10 +110,9 @@ class interp2d: interp2d(x, y, z, kind='linear', copy=True, bounds_error=False, fill_value=None) - .. deprecated:: 1.10.0 + .. versionremoved:: 1.14.0 - `interp2d` is deprecated in SciPy 1.10 and will be removed in SciPy - 1.14.0. + `interp2d` has been removed in SciPy 1.14.0. For legacy code, nearly bug-for-bug compatible replacements are `RectBivariateSpline` on regular grids, and `bisplrep`/`bisplev` for @@ -126,240 +122,11 @@ class interp2d: For scattered data, prefer `LinearNDInterpolator` or `CloughTocher2DInterpolator`. - For more details see - `https://scipy.github.io/devdocs/notebooks/interp_transition_guide.html - `_ - - - Interpolate over a 2-D grid. - - `x`, `y` and `z` are arrays of values used to approximate some function - f: ``z = f(x, y)`` which returns a scalar value `z`. This class returns a - function whose call method uses spline interpolation to find the value - of new points. - - If `x` and `y` represent a regular grid, consider using - `RectBivariateSpline`. - - If `z` is a vector value, consider using `interpn`. - - Note that calling `interp2d` with NaNs present in input values, or with - decreasing values in `x` an `y` results in undefined behaviour. - - Methods - ------- - __call__ - - Parameters - ---------- - x, y : array_like - Arrays defining the data point coordinates. - The data point coordinates need to be sorted by increasing order. - - If the points lie on a regular grid, `x` can specify the column - coordinates and `y` the row coordinates, for example:: - - >>> x = [0,1,2]; y = [0,3]; z = [[1,2,3], [4,5,6]] - - Otherwise, `x` and `y` must specify the full coordinates for each - point, for example:: - - >>> x = [0,1,2,0,1,2]; y = [0,0,0,3,3,3]; z = [1,4,2,5,3,6] - - If `x` and `y` are multidimensional, they are flattened before use. - z : array_like - The values of the function to interpolate at the data points. If - `z` is a multidimensional array, it is flattened before use assuming - Fortran-ordering (order='F'). The length of a flattened `z` array - is either len(`x`)*len(`y`) if `x` and `y` specify the column and - row coordinates or ``len(z) == len(x) == len(y)`` if `x` and `y` - specify coordinates for each point. - kind : {'linear', 'cubic', 'quintic'}, optional - The kind of spline interpolation to use. Default is 'linear'. - copy : bool, optional - If True, the class makes internal copies of x, y and z. - If False, references may be used. The default is to copy. - bounds_error : bool, optional - If True, when interpolated values are requested outside of the - domain of the input data (x,y), a ValueError is raised. - If False, then `fill_value` is used. - fill_value : number, optional - If provided, the value to use for points outside of the - interpolation domain. If omitted (None), values outside - the domain are extrapolated via nearest-neighbor extrapolation. - - See Also - -------- - RectBivariateSpline : - Much faster 2-D interpolation if your input data is on a grid - bisplrep, bisplev : - Spline interpolation based on FITPACK - BivariateSpline : a more recent wrapper of the FITPACK routines - interp1d : 1-D version of this function - RegularGridInterpolator : interpolation on a regular or rectilinear grid - in arbitrary dimensions. - interpn : Multidimensional interpolation on regular grids (wraps - `RegularGridInterpolator` and `RectBivariateSpline`). - - Notes - ----- - The minimum number of data points required along the interpolation - axis is ``(k+1)**2``, with k=1 for linear, k=3 for cubic and k=5 for - quintic interpolation. - - The interpolator is constructed by `bisplrep`, with a smoothing factor - of 0. If more control over smoothing is needed, `bisplrep` should be - used directly. - - The coordinates of the data points to interpolate `xnew` and `ynew` - have to be sorted by ascending order. - `interp2d` is legacy and is not - recommended for use in new code. New code should use - `RegularGridInterpolator` instead. - - Examples - -------- - Construct a 2-D grid and interpolate on it: - - >>> import numpy as np - >>> from scipy import interpolate - >>> x = np.arange(-5.01, 5.01, 0.25) - >>> y = np.arange(-5.01, 5.01, 0.25) - >>> xx, yy = np.meshgrid(x, y) - >>> z = np.sin(xx**2+yy**2) - >>> f = interpolate.interp2d(x, y, z, kind='cubic') - - Now use the obtained interpolation function and plot the result: - - >>> import matplotlib.pyplot as plt - >>> xnew = np.arange(-5.01, 5.01, 1e-2) - >>> ynew = np.arange(-5.01, 5.01, 1e-2) - >>> znew = f(xnew, ynew) - >>> plt.plot(x, z[0, :], 'ro-', xnew, znew[0, :], 'b-') - >>> plt.show() + For more details see :ref:`interp-transition-guide`. """ - def __init__(self, x, y, z, kind='linear', copy=True, bounds_error=False, fill_value=None): - warnings.warn(dep_mesg, DeprecationWarning, stacklevel=2) - - x = ravel(x) - y = ravel(y) - z = asarray(z) - - rectangular_grid = (z.size == len(x) * len(y)) - if rectangular_grid: - if z.ndim == 2: - if z.shape != (len(y), len(x)): - raise ValueError("When on a regular grid with x.size = m " - "and y.size = n, if z.ndim == 2, then z " - "must have shape (n, m)") - if not np.all(x[1:] >= x[:-1]): - j = np.argsort(x) - x = x[j] - z = z[:, j] - if not np.all(y[1:] >= y[:-1]): - j = np.argsort(y) - y = y[j] - z = z[j, :] - z = ravel(z.T) - else: - z = ravel(z) - if len(x) != len(y): - raise ValueError( - "x and y must have equal lengths for non rectangular grid") - if len(z) != len(x): - raise ValueError( - "Invalid length for input z for non rectangular grid") - - interpolation_types = {'linear': 1, 'cubic': 3, 'quintic': 5} - try: - kx = ky = interpolation_types[kind] - except KeyError as e: - raise ValueError( - f"Unsupported interpolation type {repr(kind)}, must be " - f"either of {', '.join(map(repr, interpolation_types))}." - ) from e - - if not rectangular_grid: - # TODO: surfit is really not meant for interpolation! - self.tck = _fitpack_py.bisplrep(x, y, z, kx=kx, ky=ky, s=0.0) - else: - nx, tx, ny, ty, c, fp, ier = dfitpack.regrid_smth( - x, y, z, None, None, None, None, - kx=kx, ky=ky, s=0.0) - self.tck = (tx[:nx], ty[:ny], c[:(nx - kx - 1) * (ny - ky - 1)], - kx, ky) - - self.bounds_error = bounds_error - self.fill_value = fill_value - self.x, self.y, self.z = (array(a, copy=copy) for a in (x, y, z)) - - self.x_min, self.x_max = np.amin(x), np.amax(x) - self.y_min, self.y_max = np.amin(y), np.amax(y) - - def __call__(self, x, y, dx=0, dy=0, assume_sorted=False): - """Interpolate the function. - - Parameters - ---------- - x : 1-D array - x-coordinates of the mesh on which to interpolate. - y : 1-D array - y-coordinates of the mesh on which to interpolate. - dx : int >= 0, < kx - Order of partial derivatives in x. - dy : int >= 0, < ky - Order of partial derivatives in y. - assume_sorted : bool, optional - If False, values of `x` and `y` can be in any order and they are - sorted first. - If True, `x` and `y` have to be arrays of monotonically - increasing values. - - Returns - ------- - z : 2-D array with shape (len(y), len(x)) - The interpolated values. - """ - warnings.warn(dep_mesg, DeprecationWarning, stacklevel=2) - - x = atleast_1d(x) - y = atleast_1d(y) - - if x.ndim != 1 or y.ndim != 1: - raise ValueError("x and y should both be 1-D arrays") - - if not assume_sorted: - x = np.sort(x, kind="mergesort") - y = np.sort(y, kind="mergesort") - - if self.bounds_error or self.fill_value is not None: - out_of_bounds_x = (x < self.x_min) | (x > self.x_max) - out_of_bounds_y = (y < self.y_min) | (y > self.y_max) - - any_out_of_bounds_x = np.any(out_of_bounds_x) - any_out_of_bounds_y = np.any(out_of_bounds_y) - - if self.bounds_error and (any_out_of_bounds_x or any_out_of_bounds_y): - raise ValueError( - f"Values out of range; x must be in {(self.x_min, self.x_max)!r}, " - f"y in {(self.y_min, self.y_max)!r}" - ) - - z = _fitpack_py.bisplev(x, y, self.tck, dx, dy) - z = atleast_2d(z) - z = transpose(z) - - if self.fill_value is not None: - if any_out_of_bounds_x: - z[:, out_of_bounds_x] = self.fill_value - if any_out_of_bounds_y: - z[out_of_bounds_y, :] = self.fill_value - - if len(z) == 1: - z = z[0] - return array(z) + raise NotImplementedError(err_mesg) def _check_broadcast_up_to(arr_from, shape_to, name): @@ -915,8 +682,9 @@ def extend(self, c, x): if x.shape[0] != c.shape[1]: raise ValueError(f"Shapes of x {x.shape} and c {c.shape} are incompatible") if c.shape[2:] != self.c.shape[2:] or c.ndim != self.c.ndim: - raise ValueError("Shapes of c {} and self.c {} are incompatible" - .format(c.shape, self.c.shape)) + raise ValueError( + f"Shapes of c {c.shape} and self.c {self.c.shape} are incompatible" + ) if c.size == 0: return @@ -1969,8 +1737,9 @@ def _construct_from_derivatives(xa, xb, ya, yb): """ ya, yb = np.asarray(ya), np.asarray(yb) if ya.shape[1:] != yb.shape[1:]: - raise ValueError('Shapes of ya {} and yb {} are incompatible' - .format(ya.shape, yb.shape)) + raise ValueError( + f"Shapes of ya {ya.shape} and yb {yb.shape} are incompatible" + ) dta, dtb = ya.dtype, yb.dtype if (np.issubdtype(dta, np.complexfloating) or diff --git a/scipy/interpolate/tests/test_interpolate.py b/scipy/interpolate/tests/test_interpolate.py index ed1ca8b46e68..82bddb7f0319 100644 --- a/scipy/interpolate/tests/test_interpolate.py +++ b/scipy/interpolate/tests/test_interpolate.py @@ -1,10 +1,10 @@ from numpy.testing import (assert_, assert_equal, assert_almost_equal, assert_array_almost_equal, assert_array_equal, - assert_allclose, suppress_warnings) + assert_allclose) from pytest import raises as assert_raises import pytest -from numpy import mgrid, pi, sin, ogrid, poly1d, linspace +from numpy import mgrid, pi, sin, poly1d import numpy as np from scipy.interpolate import (interp1d, interp2d, lagrange, PPoly, BPoly, @@ -26,95 +26,8 @@ class TestInterp2D: def test_interp2d(self): y, x = mgrid[0:2:20j, 0:pi:21j] z = sin(x+0.5*y) - with suppress_warnings() as sup: - sup.filter(DeprecationWarning) - II = interp2d(x, y, z) - assert_almost_equal(II(1.0, 2.0), sin(2.0), decimal=2) - - v, u = ogrid[0:2:24j, 0:pi:25j] - assert_almost_equal(II(u.ravel(), v.ravel()), - sin(u+0.5*v), decimal=2) - - def test_interp2d_meshgrid_input(self): - # Ticket #703 - x = linspace(0, 2, 16) - y = linspace(0, pi, 21) - z = sin(x[None, :] + y[:, None]/2.) - with suppress_warnings() as sup: - sup.filter(DeprecationWarning) - II = interp2d(x, y, z) - assert_almost_equal(II(1.0, 2.0), sin(2.0), decimal=2) - - def test_interp2d_meshgrid_input_unsorted(self): - np.random.seed(1234) - x = linspace(0, 2, 16) - y = linspace(0, pi, 21) - - z = sin(x[None, :] + y[:, None] / 2.) - with suppress_warnings() as sup: - sup.filter(DeprecationWarning) - ip1 = interp2d(x.copy(), y.copy(), z, kind='cubic') - - np.random.shuffle(x) - z = sin(x[None, :] + y[:, None]/2.) - ip2 = interp2d(x.copy(), y.copy(), z, kind='cubic') - - np.random.shuffle(x) - np.random.shuffle(y) - z = sin(x[None, :] + y[:, None] / 2.) - ip3 = interp2d(x, y, z, kind='cubic') - - x = linspace(0, 2, 31) - y = linspace(0, pi, 30) - - assert_equal(ip1(x, y), ip2(x, y)) - assert_equal(ip1(x, y), ip3(x, y)) - - def test_interp2d_eval_unsorted(self): - y, x = mgrid[0:2:20j, 0:pi:21j] - z = sin(x + 0.5*y) - with suppress_warnings() as sup: - sup.filter(DeprecationWarning) - func = interp2d(x, y, z) - - xe = np.array([3, 4, 5]) - ye = np.array([5.3, 7.1]) - assert_allclose(func(xe, ye), func(xe, ye[::-1])) - - assert_raises(ValueError, func, xe, ye[::-1], 0, 0, True) - - def test_interp2d_linear(self): - # Ticket #898 - a = np.zeros([5, 5]) - a[2, 2] = 1.0 - x = y = np.arange(5) - with suppress_warnings() as sup: - sup.filter(DeprecationWarning) - b = interp2d(x, y, a, 'linear') - assert_almost_equal(b(2.0, 1.5), np.array([0.5]), decimal=2) - assert_almost_equal(b(2.0, 2.5), np.array([0.5]), decimal=2) - - def test_interp2d_bounds(self): - x = np.linspace(0, 1, 5) - y = np.linspace(0, 2, 7) - z = x[None, :]**2 + y[:, None] - - ix = np.linspace(-1, 3, 31) - iy = np.linspace(-1, 3, 33) - - with suppress_warnings() as sup: - sup.filter(DeprecationWarning) - - b = interp2d(x, y, z, bounds_error=True) - assert_raises(ValueError, b, ix, iy) - - b = interp2d(x, y, z, fill_value=np.nan) - iz = b(ix, iy) - mx = (ix < 0) | (ix > 1) - my = (iy < 0) | (iy > 2) - assert_(np.isnan(iz[my, :]).all()) - assert_(np.isnan(iz[:, mx]).all()) - assert_(np.isfinite(iz[~my, :][:, ~mx]).all()) + with assert_raises(NotImplementedError): + interp2d(x, y, z) class TestInterp1D: diff --git a/tools/refguide_check.py b/tools/refguide_check.py index e96c89cb5b05..3f8418af669f 100755 --- a/tools/refguide_check.py +++ b/tools/refguide_check.py @@ -308,7 +308,8 @@ def validate_rst_syntax(text, name, dots=True): 'mod', 'currentmodule', 'autosummary', 'data', 'legacy', 'obj', 'versionadded', 'versionchanged', 'module', 'class', 'meth', 'ref', 'func', 'toctree', 'moduleauthor', 'deprecated', - 'sectionauthor', 'codeauthor', 'eq', 'doi', 'DOI', 'arXiv', 'arxiv' + 'sectionauthor', 'codeauthor', 'eq', 'doi', 'DOI', 'arXiv', 'arxiv', + 'versionremoved', ]) # Run through docutils From cdc251d1b7c4827e67c7432e37cdf1eaaebca654 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 25 May 2024 03:10:20 -0700 Subject: [PATCH 251/500] ENH: stats: end-to-end array-API support for NHSTs with chi-squared null distribution (#20782) * ENH: stats.special.chdtrc: add array API support * ENH: stats: end-to-end array-API support for tests using chi2.sf * MAINT: stats: X2 -> x2 * TST: stats.power_divergence: use xp_assert_close in array API compatible test * MAINT: stats: pass symmetric=False to _get_pvalue where appropriate --- scipy/special/__init__.py | 2 +- .../special/_support_alternative_backends.py | 84 ++++++++++++------- scipy/stats/_hypotests.py | 9 +- scipy/stats/_morestats.py | 17 ++-- scipy/stats/_stats_py.py | 67 ++++++++++----- scipy/stats/tests/test_morestats.py | 2 - scipy/stats/tests/test_mstats_basic.py | 1 + scipy/stats/tests/test_stats.py | 9 +- 8 files changed, 118 insertions(+), 73 deletions(-) diff --git a/scipy/special/__init__.py b/scipy/special/__init__.py index f6d854c86db6..a86d6f9f69f8 100644 --- a/scipy/special/__init__.py +++ b/scipy/special/__init__.py @@ -810,7 +810,7 @@ def _load_libsf_error_state(): # Replace some function definitions from _ufuncs to add Array API support from ._support_alternative_backends import ( log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, - gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy) + gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy, chdtrc) from . import _basic from ._basic import * diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 3feeea13b6dc..1be09d29cc2f 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -11,8 +11,10 @@ # These don't really need to be imported, but otherwise IDEs might not realize # that these are defined in this file / report an error in __init__.py from ._ufuncs import ( - log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, # noqa: F401 - gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy) # noqa: F401 + log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, # noqa: F401 + gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy, # noqa: F401 + chdtrc # noqa: F401 +) _SCIPY_ARRAY_API = os.environ.get("SCIPY_ARRAY_API", False) array_api_compat_prefix = "scipy._lib.array_api_compat" @@ -33,19 +35,18 @@ def get_array_special_func(f_name, xp, n_array_args): # if generic array-API implementation is available, use that; # otherwise, fall back to NumPy/SciPy - # Use of ` _f=_f, _xp=xp` is to avoid late-binding gotcha if f_name in _generic_implementations: - _f = _generic_implementations[f_name] - def f(*args, _f=_f, _xp=xp, **kwargs): - return _f(*args, xp=_xp, **kwargs) - else: - _f = getattr(_ufuncs, f_name, None) - def f(*args, _f=_f, _xp=xp, **kwargs): - array_args = args[:n_array_args] - other_args = args[n_array_args:] - array_args = [np.asarray(arg) for arg in array_args] - out = _f(*array_args, *other_args, **kwargs) - return _xp.asarray(out) + _f = _generic_implementations[f_name](xp=xp, spx=spx) + if _f is not None: + return _f + + _f = getattr(_ufuncs, f_name, None) + def f(*args, _f=_f, _xp=xp, **kwargs): + array_args = args[:n_array_args] + other_args = args[n_array_args:] + array_args = [np.asarray(arg) for arg in array_args] + out = _f(*array_args, *other_args, **kwargs) + return _xp.asarray(out) return f @@ -60,24 +61,48 @@ def _get_shape_dtype(*args, xp): return args, shape, dtype -def _rel_entr(x, y, *, xp): - args, shape, dtype = _get_shape_dtype(x, y, xp=xp) - x, y = args - res = xp.full(x.shape, xp.inf, dtype=dtype) - res[(x == 0) & (y >= 0)] = xp.asarray(0, dtype=dtype) - i = (x > 0) & (y > 0) - res[i] = x[i] * (xp.log(x[i]) - xp.log(y[i])) - return res - - -def _xlogy(x, y, *, xp): - with np.errstate(divide='ignore', invalid='ignore'): - temp = x * xp.log(y) - return xp.where(x == 0., xp.asarray(0., dtype=temp.dtype), temp) +def _rel_entr(xp, spx): + def __rel_entr(x, y, *, xp=xp): + args, shape, dtype = _get_shape_dtype(x, y, xp=xp) + x, y = args + res = xp.full(x.shape, xp.inf, dtype=dtype) + res[(x == 0) & (y >= 0)] = xp.asarray(0, dtype=dtype) + i = (x > 0) & (y > 0) + res[i] = x[i] * (xp.log(x[i]) - xp.log(y[i])) + return res + return __rel_entr + + +def _xlogy(xp, spx): + def __xlogy(x, y, *, xp=xp): + with np.errstate(divide='ignore', invalid='ignore'): + temp = x * xp.log(y) + return xp.where(x == 0., xp.asarray(0., dtype=temp.dtype), temp) + return __xlogy + + +def _chdtrc(xp, spx): + # 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 = getattr(spx, 'gammaincc', None) # noqa: F811 + if gammaincc is None and hasattr(xp, 'special'): + gammaincc = getattr(xp.special, 'gammaincc', None) + 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) + res = xp.where(i_nan, xp.nan, res) + return res + return __chdtrc _generic_implementations = {'rel_entr': _rel_entr, - 'xlogy': _xlogy} + 'xlogy': _xlogy, + 'chdtrc': _chdtrc} # functools.wraps doesn't work because: @@ -112,6 +137,7 @@ def wrapped(*args, **kwargs): 'entr': 1, 'rel_entr': 2, 'xlogy': 2, + 'chdtrc': 2, } for f_name, n_array_args in array_special_func_map.items(): diff --git a/scipy/stats/_hypotests.py b/scipy/stats/_hypotests.py index e0a1013806e1..2bd5539cdfbf 100644 --- a/scipy/stats/_hypotests.py +++ b/scipy/stats/_hypotests.py @@ -8,7 +8,7 @@ from scipy.optimize import shgo from . import distributions from ._common import ConfidenceInterval -from ._continuous_distns import chi2, norm +from ._continuous_distns import norm from scipy.special import gamma, kv, gammaln from scipy.fft import ifft from ._stats_pythran import _a_ij_Aij_Dij2 @@ -141,7 +141,8 @@ def epps_singleton_2samp(x, y, t=(0.4, 0.8)): corr = 1.0/(1.0 + n**(-0.45) + 10.1*(nx**(-1.7) + ny**(-1.7))) w = corr * w - p = chi2.sf(w, r) + chi2 = _stats_py._SimpleChi2(r) + p = _stats_py._get_pvalue(w, chi2, alternative='greater', symmetric=False, xp=np) return Epps_Singleton_2sampResult(w, p) @@ -693,8 +694,8 @@ def _somers_d(A, alternative='two-sided'): with np.errstate(divide='ignore'): Z = (PA - QA)/(4*(S))**0.5 - norm = scipy.stats._stats_py._SimpleNormal() - p = scipy.stats._stats_py._get_pvalue(Z, norm, alternative, xp=np) + norm = _stats_py._SimpleNormal() + p = _stats_py._get_pvalue(Z, norm, alternative, xp=np) return d, p diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 230617a9c8a3..712515db52af 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -18,7 +18,7 @@ from . import _stats_py, _wilcoxon from ._fit import FitResult from ._stats_py import (find_repeats, _get_pvalue, SignificanceResult, # noqa:F401 - _SimpleNormal) + _SimpleNormal, _SimpleChi2) from .contingency import chi2_contingency from . import distributions from ._distn_infrastructure import rv_generic @@ -265,7 +265,7 @@ def kstat(data, n=2, *, axis=None): .. math:: S_r \equiv \sum_{i=1}^n X_i^r, - + and :math:`X_i` is the :math:`i` th data point. References @@ -3083,9 +3083,9 @@ def bartlett(*samples, axis=0): denom = 1 + 1/(3*(k - 1)) * ((xp.sum(1/(Ni - 1), axis=0)) - 1/(Ntot - k)) T = numer / denom - T_np = np.asarray(T) - pvalue = distributions.chi2.sf(T_np, k-1) - pvalue = xp.asarray(pvalue, dtype=T.dtype) + chi2 = _SimpleChi2(xp.asarray(k-1)) + pvalue = _get_pvalue(T, chi2, alternative='greater', symmetric=False, xp=xp) + T = T[()] if T.ndim == 0 else T pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue @@ -3659,9 +3659,10 @@ def func(x): Aibar = _apply_func(sample, g, np.sum) / Ni anbar = np.mean(sample, axis=0) varsq = np.var(sample, axis=0, ddof=1) - Xsq = np.sum(Ni * (asarray(Aibar) - anbar)**2.0, axis=0) / varsq - pval = distributions.chi2.sf(Xsq, k - 1) # 1 - cdf - return FlignerResult(Xsq, pval) + statistic = np.sum(Ni * (asarray(Aibar) - anbar)**2.0, axis=0) / varsq + chi2 = _SimpleChi2(k-1) + pval = _get_pvalue(statistic, chi2, alternative='greater', symmetric=False, xp=np) + return FlignerResult(statistic, pval) @_axis_nan_policy_factory(lambda x1: (x1,), n_samples=4, n_outputs=1) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 8f78d8c325bb..f5ec6faa1d47 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -1995,18 +1995,19 @@ def normaltest(a, axis=0, nan_policy='propagate'): hypothesis [5]_. """ + xp = array_namespace(a) + s, _ = skewtest(a, axis) k, _ = kurtosistest(a, axis) - k2 = s*s + k*k + statistic = s*s + k*k + + chi2 = _SimpleChi2(xp.asarray(2.)) + pvalue = _get_pvalue(statistic, chi2, alternative='greater', symmetric=False, xp=xp) - xp = array_namespace(k2) - k2_np = np.asarray(k2) - pvalue = distributions.chi2.sf(k2_np, 2) - pvalue = xp.asarray(pvalue, dtype=k2.dtype) - k2 = k2[()] if k2.ndim == 0 else k2 + statistic = statistic[()] if statistic.ndim == 0 else statistic pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue - return NormaltestResult(k2, pvalue) + return NormaltestResult(statistic, pvalue) @_axis_nan_policy_factory(SignificanceResult, default_axis=None) @@ -2168,15 +2169,15 @@ def jarque_bera(x, *, axis=None): diffx = x - mu s = skew(diffx, axis=axis, _no_deco=True) k = kurtosis(diffx, axis=axis, _no_deco=True) - k2 = n / 6 * (s**2 + k**2 / 4) + statistic = n / 6 * (s**2 + k**2 / 4) - k2_np = np.asarray(k2) - pvalue = distributions.chi2.sf(k2_np, df=2) - pvalue = xp.asarray(pvalue, dtype=k2.dtype) - k2 = k2[()] if k2.ndim == 0 else k2 + chi2 = _SimpleChi2(xp.asarray(2.)) + 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(k2, pvalue) + return SignificanceResult(statistic, pvalue) ##################################### @@ -4386,7 +4387,9 @@ def alexandergovern(*samples, nan_policy='propagate'): # "[the p value is determined from] central chi-square random deviates # with k - 1 degrees of freedom". Alexander, Govern (94) - p = distributions.chi2.sf(A, len(samples) - 1) + df = len(samples) - 1 + chi2 = _SimpleChi2(df) + p = _get_pvalue(A, chi2, alternative='greater', symmetric=False, xp=np) return AlexanderGovernResult(A, p) @@ -7656,9 +7659,10 @@ def warn_masked(arg): num_obs = _m_count(terms, axis=axis, xp=xp) ddof = xp.asarray(ddof) - stat_np = np.asarray(stat) - pvalue = distributions.chi2.sf(stat_np, num_obs - 1 - ddof) - pvalue = xp.asarray(pvalue, dtype=stat.dtype) + df = xp.asarray(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 @@ -8939,7 +8943,9 @@ def kruskal(*samples, nan_policy='propagate'): df = num_groups - 1 h /= ties - return KruskalResult(h, distributions.chi2.sf(h, df)) + chi2 = _SimpleChi2(df) + pvalue = _get_pvalue(h, chi2, alternative='greater', symmetric=False, xp=np) + return KruskalResult(h, pvalue) FriedmanchisquareResult = namedtuple('FriedmanchisquareResult', @@ -9036,9 +9042,11 @@ def friedmanchisquare(*samples): c = 1 - ties / (k*(k*k - 1)*n) ssbn = np.sum(data.sum(axis=0)**2) - chisq = (12.0 / (k*n*(k+1)) * ssbn - 3*n*(k+1)) / c + statistic = (12.0 / (k*n*(k+1)) * ssbn - 3*n*(k+1)) / c - return FriedmanchisquareResult(chisq, distributions.chi2.sf(chisq, k - 1)) + chi2 = _SimpleChi2(k - 1) + pvalue = _get_pvalue(statistic, chi2, alternative='greater', symmetric=False, xp=np) + return FriedmanchisquareResult(statistic, pvalue) BrunnerMunzelResult = namedtuple('BrunnerMunzelResult', @@ -9298,9 +9306,13 @@ def combine_pvalues(pvalues, method='fisher', weights=None): if method == 'fisher': statistic = -2 * np.sum(np.log(pvalues)) - pval = distributions.chi2.sf(statistic, 2 * len(pvalues)) + chi2 = _SimpleChi2(2 * len(pvalues)) + pval = _get_pvalue(statistic, chi2, alternative='greater', + symmetric=False, xp=np) elif method == 'pearson': statistic = 2 * np.sum(np.log1p(-pvalues)) + # _SimpleChi2 doesn't have `cdf` yet; + # add it when `combine_pvalues` is converted to array API pval = distributions.chi2.cdf(-statistic, 2 * len(pvalues)) elif method == 'mudholkar_george': normalizing_factor = np.sqrt(3/len(pvalues))/np.pi @@ -10717,7 +10729,7 @@ def first_order(t): class _SimpleNormal: # A very simple, array-API compatible normal distribution for use in - # hypothesis tests. Will be replaced by new infrastructure Normal + # hypothesis tests. May be replaced by new infrastructure Normal # distribution in due time. def cdf(self, x): @@ -10725,3 +10737,14 @@ def cdf(self, x): def sf(self, x): return special.ndtr(-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 sf(self, x): + return special.chdtrc(self.df, x) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 3f7d2bff6816..4ba64039ac05 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -736,8 +736,6 @@ def test_alternative_approx(self): @array_api_compatible -@pytest.mark.usefixtures("skip_xp_backends") -@pytest.mark.skip_xp_backends(cpu_only=True, reasons=['Uses NumPy for pvalue']) class TestBartlett: def test_data(self, xp): # https://www.itl.nist.gov/div898/handbook/eda/section3/eda357.htm diff --git a/scipy/stats/tests/test_mstats_basic.py b/scipy/stats/tests/test_mstats_basic.py index 729a30fef1aa..c31c965e0e30 100644 --- a/scipy/stats/tests/test_mstats_basic.py +++ b/scipy/stats/tests/test_mstats_basic.py @@ -1085,6 +1085,7 @@ def test_plotting_positions(): 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): diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 9c9fe793b6a0..b74882d9ea2a 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -4006,8 +4006,6 @@ def test_nd(self, shape): @array_api_compatible -@pytest.mark.usefixtures("skip_xp_backends") -@pytest.mark.skip_xp_backends(cpu_only=True, reasons=['Uses NumPy for pvalue']) class TestPowerDivergence: def check_power_divergence(self, f_obs, f_exp, ddof, axis, lambda_, @@ -4206,12 +4204,10 @@ def test_power_divergence_against_cressie_read_data(self, xp): lambda_, expected_stat = table5[i, 0], table5[i, 1] stat, p = stats.power_divergence(table4[:,0], table4[:,1], lambda_=lambda_) - assert_allclose(stat, expected_stat, rtol=5e-3) + xp_assert_close(stat, expected_stat, rtol=5e-3) @array_api_compatible -@pytest.mark.usefixtures("skip_xp_backends") -@pytest.mark.skip_xp_backends(cpu_only=True, reasons=['Uses NumPy for pvalue']) class TestChisquare: def test_gh_chisquare_12282(self, xp): # Currently `chisquare` is implemented via power_divergence @@ -6322,8 +6318,6 @@ def test_input_validation(self): stats.ranksums(self.x, self.y, alternative='foobar') -@pytest.mark.usefixtures("skip_xp_backends") -@skip_xp_backends(cpu_only=True) @array_api_compatible class TestJarqueBera: def test_jarque_bera_against_R(self, xp): @@ -6340,6 +6334,7 @@ def test_jarque_bera_against_R(self, xp): xp_assert_close(res.pvalue, ref[1]) @skip_xp_backends(np_only=True) + @pytest.mark.usefixtures("skip_xp_backends") def test_jarque_bera_array_like(self): # array-like only relevant for NumPy np.random.seed(987654321) From 643327f521475178a15470bfcb4ac66a44b96a2d Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Sat, 25 May 2024 20:49:28 +1000 Subject: [PATCH 252/500] MAINT: update cobyqa submodule, with improved performance --- scipy/_lib/cobyqa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/_lib/cobyqa b/scipy/_lib/cobyqa index dee1a92b5f3f..c516987f947c 160000 --- a/scipy/_lib/cobyqa +++ b/scipy/_lib/cobyqa @@ -1 +1 @@ -Subproject commit dee1a92b5f3fa50c6fce42a38a52ad2f4ebddc4c +Subproject commit c516987f947ccef9a16869b9633c20f9b8e0f4fe From 08633c41eef81222a405b57e01282c8d297d067f Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Sat, 25 May 2024 20:50:59 +1000 Subject: [PATCH 253/500] MAINT: direct users to use Constraint objects for cobyqa --- scipy/optimize/_minimize.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scipy/optimize/_minimize.py b/scipy/optimize/_minimize.py index 7f3ca4aa1ed1..195e31f23e22 100644 --- a/scipy/optimize/_minimize.py +++ b/scipy/optimize/_minimize.py @@ -159,8 +159,8 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, constraints : {Constraint, dict} or List of {Constraint, dict}, optional Constraints definition. Only for COBYLA, COBYQA, SLSQP and trust-constr. - Constraints for 'trust-constr' are defined as a single object or a - list of objects specifying constraints to the optimization problem. + Constraints for 'trust-constr' and 'cobyqa' are defined as a single object + or a list of objects specifying constraints to the optimization problem. Available constraints are: - `LinearConstraint` @@ -182,7 +182,6 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, be zero whereas inequality means that it is to be non-negative. Note that COBYLA only supports inequality constraints. - Constraints for COBYQA are defined as any of the above. tol : float, optional Tolerance for termination. When `tol` is specified, the selected minimization algorithm sets some relevant solver-specific tolerance(s) @@ -1060,7 +1059,7 @@ def standardize_constraints(constraints, x0, meth): else: constraints = list(constraints) # ensure it's a mutable sequence - if meth in ['trust-constr', 'new']: + if meth in ['trust-constr', 'cobyqa', 'new']: for i, con in enumerate(constraints): if not isinstance(con, new_constraint_types): constraints[i] = old_constraint_to_new(i, con) From e967c97d7e47181ee2273a16ac9bd0104cfa77f0 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 25 May 2024 13:57:57 +0300 Subject: [PATCH 254/500] DOC: interpolate: mention default kinds in interp2d transition guide closes gh-20620 [docs only] --- .../interpolate/interp_transition_guide.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/doc/source/tutorial/interpolate/interp_transition_guide.md b/doc/source/tutorial/interpolate/interp_transition_guide.md index 6fdf9f71793b..0bac09fdbe9e 100644 --- a/doc/source/tutorial/interpolate/interp_transition_guide.md +++ b/doc/source/tutorial/interpolate/interp_transition_guide.md @@ -83,6 +83,22 @@ interpolation function.](plots/output_12_0.png) >>> assert_allclose(znew_i, znew_r, atol=1e-14) ``` +#### Interpolation order: linear, cubic etc + + +`interp2d` defaults to `kind="linear"`, which is linear in both directions, `x-` and `y-`. +`RectBivariateSpline`, on the other hand, defaults to cubic interpolation, +`kx=3, ky=3`. + +Here is the exact equivalence: + +| interp2d | RectBivariateSpline | +|----------|--------------------| +| no kwargs | kx = 1, ky = 1 | +| kind='linear' | kx = 1, ky = 1 | +| kind='cubic' | kx = 3, ky = 3 | + + ### 1.2. `interp2d` with full coordinates of points (scattered interpolation) Here, we flatten the meshgrid from the previous exercise to illustrate the functionality. @@ -131,6 +147,22 @@ interpolation function.](plots/output_20_0.png) >>> assert_allclose(znew_i, znew_b, atol=1e-15) ``` +#### Interpolation order: linear, cubic etc + + +`interp2d` defaults to `kind="linear"`, which is linear in both directions, `x-` and `y-`. +`bisplrep`, on the other hand, defaults to cubic interpolation, +`kx=3, ky=3`. + +Here is the exact equivalence: + +| `interp2d` | `bisplrep` | +|----------|--------------------| +| no kwargs | kx = 1, ky = 1 | +| kind='linear' | kx = 1, ky = 1 | +| kind='cubic' | kx = 3, ky = 3 | + + ## 2. Alternative to `interp2d`: regular grid For new code, the recommended alternative is `RegularGridInterpolator`. It is an independent implementation, not based on FITPACK. Supports nearest, linear interpolation and odd-order tensor product splines. From 5200e017c9fce1d1ac1a2d1cc720280c8ea41247 Mon Sep 17 00:00:00 2001 From: Matti Picus Date: Sat, 25 May 2024 21:49:02 +0300 Subject: [PATCH 255/500] BUG: special: remove redundant `Py_Initialize` (#20790) --- scipy/special/ufunc.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scipy/special/ufunc.h b/scipy/special/ufunc.h index 0482100ab8ff..4105dbbfb91d 100644 --- a/scipy/special/ufunc.h +++ b/scipy/special/ufunc.h @@ -17,10 +17,8 @@ #include "sf_error.h" #include "special/mdspan.h" -// Initializes Python and NumPy. +// Initializes NumPy. inline bool SpecFun_Initialize() { - Py_Initialize(); - import_array(); if (PyErr_Occurred() != nullptr) { return false; // import array failed From 6a22e8f1ec36c870814b9387de8c9a7cf0e1b433 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sat, 25 May 2024 21:38:57 +0200 Subject: [PATCH 256/500] TST: optimize: fix failing tests for `_bracket_minimum` Follows up on failures reported on gh-20563. The solution coincides with an endpoint, so it looks to me like it is valid - hence asserting `success` must be False seems incorrect. [skip ci] --- scipy/optimize/tests/test_bracket.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scipy/optimize/tests/test_bracket.py b/scipy/optimize/tests/test_bracket.py index 370efe79e545..dc39b5fe5286 100644 --- a/scipy/optimize/tests/test_bracket.py +++ b/scipy/optimize/tests/test_bracket.py @@ -778,7 +778,6 @@ def f(x): return -((log_b - log_a)*x)**-1 result = _bracket_minimum(f, 0.5535723499480897, xmin=xmin, xmax=xmax) - assert not result.success assert xmin == result.xl def test_gh_20562_right(self): @@ -791,5 +790,4 @@ def f(x): return ((log_b - log_a)*x)**-1 result = _bracket_minimum(f, -0.5535723499480897, xmin=xmin, xmax=xmax) - assert not result.success assert xmax == result.xr From a4bbcb5d75e921bc47289da2e5f9ff728b3d433e Mon Sep 17 00:00:00 2001 From: CJ Carey Date: Sat, 25 May 2024 15:48:18 -0400 Subject: [PATCH 257/500] ENH: sparse: Speed up `_add_sparse` for DIA format (#20722) --- scipy/sparse/_dia.py | 68 ++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index 73b55908466d..ab6d5dcdccad 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -64,7 +64,7 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): self.offsets = np.atleast_1d(offsets) self._shape = check_shape(shape) else: - #must be dense, convert to COO first, then to DIA + # must be dense, convert to COO first, then to DIA try: arg1 = np.asarray(arg1) except Exception as e: @@ -80,7 +80,7 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): if dtype is not None: self.data = self.data.astype(dtype) - #check format + # check format if self.offsets.ndim != 1: raise ValueError('offsets array must have rank 1') @@ -176,30 +176,41 @@ def sum(self, axis=None, dtype=None, out=None): sum.__doc__ = _spbase.sum.__doc__ def _add_sparse(self, other): + # 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) + + # 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)] + new_data[other_idx, :] += other.data + elif self_d == other_d and len(new_offsets) == len(other.offsets): + 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]) - # Check if other is also of type dia_array - if not isinstance(other, type(self)): - # If other is not of type dia_array, default to - # converting to csr_matrix, as is done in the _add_sparse - # method of parent class _spbase - return self.tocsr()._add_sparse(other) - - # The task is to compute m = self + other - # Start by making a copy of self, of the datatype - # that should result from adding self and other - dtype = np.promote_types(self.dtype, other.dtype) - m = self.astype(dtype, copy=True) - - # Then, add all the stored diagonals of other. - for d in other.offsets: - # Check if the diagonal has already been added. - if d in m.offsets: - # If the diagonal is already there, we need to take - # the sum of the existing and the new - m.setdiag(m.diagonal(d) + other.diagonal(d), d) - else: - m.setdiag(other.diagonal(d), d) - return m + # 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] + new_data[other_idx, :other_d] += other.data[:, :d] + return self._dia_container((new_data, new_offsets), shape=self.shape) def _mul_scalar(self, other): return self._with_data(self.data * other) @@ -390,6 +401,13 @@ def resize(self, *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? From e7aaeeed892a54b06a1a2390aa798f359e0b4595 Mon Sep 17 00:00:00 2001 From: Tim Beyer <35711942+TimFelixBeyer@users.noreply.github.com> Date: Sat, 25 May 2024 22:56:54 +0200 Subject: [PATCH 258/500] ENH: io: Read and write wav files of size > 4GB (#20079) --- .../data/test-44100Hz-le-1ch-4bytes-rf64.wav | Bin 0 -> 17756 bytes .../data/test-8000Hz-le-3ch-5S-24bit-rf64.wav | Bin 0 -> 126 bytes scipy/io/tests/meson.build | 2 + scipy/io/tests/test_wavfile.py | 33 ++++++- scipy/io/wavfile.py | 85 ++++++++++++++---- 5 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 scipy/io/tests/data/test-44100Hz-le-1ch-4bytes-rf64.wav create mode 100644 scipy/io/tests/data/test-8000Hz-le-3ch-5S-24bit-rf64.wav diff --git a/scipy/io/tests/data/test-44100Hz-le-1ch-4bytes-rf64.wav b/scipy/io/tests/data/test-44100Hz-le-1ch-4bytes-rf64.wav new file mode 100644 index 0000000000000000000000000000000000000000..0bc723300b760e338a28c85c27224ae7d768ed89 GIT binary patch literal 17756 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 XO0}bkVSNJbRpVa*q8IoAv5bEKyvj!% literal 0 HcmV?d00001 diff --git a/scipy/io/tests/data/test-8000Hz-le-3ch-5S-24bit-rf64.wav b/scipy/io/tests/data/test-8000Hz-le-3ch-5S-24bit-rf64.wav new file mode 100644 index 0000000000000000000000000000000000000000..8f5a81d4bad96fbc2f4db341c64724c666de9fdb GIT binary patch literal 126 zcmWG?Gc)-Q1mTWht|`T4CNc~R3}rwJ0=f{2ffY)FsI=S?1py$<$iU3tAkVLPX1g_oT|9^W%28RFt>w$;~C=3Am*&r7H literal 0 HcmV?d00001 diff --git a/scipy/io/tests/meson.build b/scipy/io/tests/meson.build index 5caa5a64f72a..98099371a3d9 100644 --- a/scipy/io/tests/meson.build +++ b/scipy/io/tests/meson.build @@ -82,6 +82,7 @@ py3.install_sources([ 'data/test-44100Hz-le-1ch-4bytes-early-eof-no-data.wav', 'data/test-44100Hz-le-1ch-4bytes-early-eof.wav', 'data/test-44100Hz-le-1ch-4bytes-incomplete-chunk.wav', + 'data/test-44100Hz-le-1ch-4bytes-rf64.wav', 'data/test-44100Hz-le-1ch-4bytes.wav', 'data/test-48000Hz-2ch-64bit-float-le-wavex.wav', 'data/test-8000Hz-be-3ch-5S-24bit.wav', @@ -89,6 +90,7 @@ py3.install_sources([ 'data/test-8000Hz-le-1ch-1byte-ulaw.wav', 'data/test-8000Hz-le-2ch-1byteu.wav', 'data/test-8000Hz-le-3ch-5S-24bit-inconsistent.wav', + 'data/test-8000Hz-le-3ch-5S-24bit-rf64.wav', 'data/test-8000Hz-le-3ch-5S-24bit.wav', 'data/test-8000Hz-le-3ch-5S-36bit.wav', 'data/test-8000Hz-le-3ch-5S-45bit.wav', diff --git a/scipy/io/tests/test_wavfile.py b/scipy/io/tests/test_wavfile.py index 0a048fad1f98..5acb6da94e09 100644 --- a/scipy/io/tests/test_wavfile.py +++ b/scipy/io/tests/test_wavfile.py @@ -302,12 +302,43 @@ def test_rifx(): assert_equal(data1, data2) +def test_rf64(): + # Compare equivalent RF64 and RIFF files + for rf64, riff in {('test-44100Hz-le-1ch-4bytes-rf64.wav', + 'test-44100Hz-le-1ch-4bytes.wav'), + ('test-8000Hz-le-3ch-5S-24bit-rf64.wav', + 'test-8000Hz-le-3ch-5S-24bit.wav')}: + rate1, data1 = wavfile.read(datafile(rf64), mmap=False) + rate2, data2 = wavfile.read(datafile(riff), mmap=False) + assert_array_equal(rate1, rate2) + assert_array_equal(data1, data2) + + +@pytest.mark.xslow +def test_write_roundtrip_rf64(tmpdir): + dtype = np.dtype(" 0xFFFFFFFF + if is_rf64: + header_data = b'' + header_data += b'RF64' + header_data += b'\xFF\xFF\xFF\xFF' + header_data += b'WAVE' + header_data += b'ds64' + # size of ds64 chunk + header_data += struct.pack(' 0xFFFFFFFF: - raise ValueError("Data exceeds wave file size limit") - fid.write(header_data) # data chunk fid.write(b'data') - fid.write(struct.pack('' or (data.dtype.byteorder == '=' and sys.byteorder == 'big'): data = data.byteswap() _array_tofile(fid, data) # Determine file size and place it in correct - # position at start of the file. + # position at start of the file or the data chunk. size = fid.tell() - fid.seek(4) - fid.write(struct.pack(' Date: Sun, 26 May 2024 16:25:26 +1000 Subject: [PATCH 259/500] MAINT: optimize: remove circular reference in `ScalarFunction` (#20770) * MAINT: ScalarFunction, remove circular reference --------- Co-authored-by: Nick ODell --- scipy/optimize/_differentiable_functions.py | 269 ++++++++++-------- .../tests/test_differentiable_functions.py | 45 +++ 2 files changed, 203 insertions(+), 111 deletions(-) diff --git a/scipy/optimize/_differentiable_functions.py b/scipy/optimize/_differentiable_functions.py index 5df1196abf56..9370aff56f7d 100644 --- a/scipy/optimize/_differentiable_functions.py +++ b/scipy/optimize/_differentiable_functions.py @@ -9,6 +9,84 @@ FD_METHODS = ('2-point', '3-point', 'cs') +def _wrapper_fun(fun, args=()): + ncalls = [0] + + def wrapped(x): + ncalls[0] += 1 + # Send a copy because the user may overwrite it. + # Overwriting results in undefined behaviour because + # fun(self.x) will change self.x, with the two no longer linked. + fx = fun(np.copy(x), *args) + # Make sure the function returns a true scalar + if not np.isscalar(fx): + try: + fx = np.asarray(fx).item() + except (TypeError, ValueError) as e: + raise ValueError( + "The user-provided objective function " + "must return a scalar value." + ) from e + return fx + return wrapped, ncalls + + +def _wrapper_grad(grad, fun=None, args=(), finite_diff_options=None): + ncalls = [0] + + if callable(grad): + def wrapped(x, **kwds): + # kwds present to give function same signature as numdiff variant + ncalls[0] += 1 + return np.atleast_1d(grad(np.copy(x), *args)) + return wrapped, ncalls + + elif grad in FD_METHODS: + def wrapped1(x, f0=None): + ncalls[0] += 1 + return approx_derivative( + fun, x, f0=f0, **finite_diff_options + ) + + return wrapped1, ncalls + + +def _wrapper_hess(hess, grad=None, x0=None, args=(), finite_diff_options=None): + if callable(hess): + H = hess(np.copy(x0), *args) + ncalls = [1] + + if sps.issparse(H): + def wrapped(x, **kwds): + ncalls[0] += 1 + return sps.csr_matrix(hess(np.copy(x), *args)) + + H = sps.csr_matrix(H) + + elif isinstance(H, LinearOperator): + def wrapped(x, **kwds): + ncalls[0] += 1 + return hess(np.copy(x), *args) + + else: # dense + def wrapped(x, **kwds): + ncalls[0] += 1 + return np.atleast_2d(np.asarray(hess(np.copy(x), *args))) + + H = np.atleast_2d(np.asarray(H)) + + return wrapped, ncalls, H + elif hess in FD_METHODS: + ncalls = [0] + + def wrapped1(x, f0=None): + return approx_derivative( + grad, x, f0=f0, **finite_diff_options + ) + + return wrapped1, ncalls, None + + class ScalarFunction: """Scalar function and its derivatives. @@ -110,13 +188,17 @@ def __init__(self, fun, x0, args, grad, hess, finite_diff_rel_step, if xp.isdtype(_x.dtype, "real floating"): _dtype = _x.dtype + # original arguments + self._wrapped_fun, self._nfev = _wrapper_fun(fun, args=args) + self._orig_fun = fun + self._orig_grad = grad + self._orig_hess = hess + self._args = args + # promotes to floating self.x = xp.astype(_x, _dtype) self.x_dtype = _dtype self.n = self.x.size - self.nfev = 0 - self.ngev = 0 - self.nhev = 0 self.f_updated = False self.g_updated = False self.H_updated = False @@ -136,88 +218,33 @@ def __init__(self, fun, x0, args, grad, hess, finite_diff_rel_step, finite_diff_options["abs_step"] = epsilon finite_diff_options["as_linear_operator"] = True - # Function evaluation - def fun_wrapped(x): - self.nfev += 1 - # Send a copy because the user may overwrite it. - # Overwriting results in undefined behaviour because - # fun(self.x) will change self.x, with the two no longer linked. - fx = fun(np.copy(x), *args) - # Make sure the function returns a true scalar - if not np.isscalar(fx): - try: - fx = np.asarray(fx).item() - except (TypeError, ValueError) as e: - raise ValueError( - "The user-provided objective function " - "must return a scalar value." - ) from e - - if fx < self._lowest_f: - self._lowest_x = x - self._lowest_f = fx - - return fx - - def update_fun(): - self.f = fun_wrapped(self.x) - - self._update_fun_impl = update_fun + # Initial function evaluation self._update_fun() - # Gradient evaluation - if callable(grad): - def grad_wrapped(x): - self.ngev += 1 - return np.atleast_1d(grad(np.copy(x), *args)) - - def update_grad(): - self.g = grad_wrapped(self.x) - - elif grad in FD_METHODS: - def update_grad(): - self._update_fun() - self.ngev += 1 - self.g = approx_derivative(fun_wrapped, self.x, f0=self.f, - **finite_diff_options) - - self._update_grad_impl = update_grad + # Initial gradient evaluation + self._wrapped_grad, self._ngev = _wrapper_grad( + grad, + fun=self._wrapped_fun, + args=args, + finite_diff_options=finite_diff_options + ) self._update_grad() - # Hessian Evaluation + # Hessian evaluation if callable(hess): - self.H = hess(np.copy(x0), *args) + self._wrapped_hess, self._nhev, self.H = _wrapper_hess( + hess, x0=x0, args=args + ) self.H_updated = True - self.nhev += 1 - - if sps.issparse(self.H): - def hess_wrapped(x): - self.nhev += 1 - return sps.csr_matrix(hess(np.copy(x), *args)) - self.H = sps.csr_matrix(self.H) - - elif isinstance(self.H, LinearOperator): - def hess_wrapped(x): - self.nhev += 1 - return hess(np.copy(x), *args) - - else: - def hess_wrapped(x): - self.nhev += 1 - return np.atleast_2d(np.asarray(hess(np.copy(x), *args))) - self.H = np.atleast_2d(np.asarray(self.H)) - - def update_hess(): - self.H = hess_wrapped(self.x) - elif hess in FD_METHODS: - def update_hess(): - self._update_grad() - self.H = approx_derivative(grad_wrapped, self.x, f0=self.g, - **finite_diff_options) - return self.H - - update_hess() + self._wrapped_hess, self._nhev, self.H = _wrapper_hess( + hess, + grad=self._wrapped_grad, + x0=x0, + finite_diff_options=finite_diff_options + ) + self._update_grad() + self.H = self._wrapped_hess(self.x, f0=self.g) self.H_updated = True elif isinstance(hess, HessianUpdateStrategy): self.H = hess @@ -225,74 +252,94 @@ def update_hess(): self.H_updated = True self.x_prev = None self.g_prev = None + self._nhev = [0] - def update_hess(): - self._update_grad() - self.H.update(self.x - self.x_prev, self.g - self.g_prev) + @property + def nfev(self): + return self._nfev[0] - self._update_hess_impl = update_hess + @property + def ngev(self): + return self._ngev[0] - if isinstance(hess, HessianUpdateStrategy): - def update_x(x): - self._update_grad() - self.x_prev = self.x - self.g_prev = self.g - # ensure that self.x is a copy of x. Don't store a reference - # otherwise the memoization doesn't work properly. + @property + def nhev(self): + return self._nhev[0] - _x = atleast_nd(x, ndim=1, xp=self.xp) - self.x = self.xp.astype(_x, self.x_dtype) - self.f_updated = False - self.g_updated = False - self.H_updated = False - self._update_hess() + def _update_x(self, x): + if isinstance(self._orig_hess, HessianUpdateStrategy): + self._update_grad() + self.x_prev = self.x + self.g_prev = self.g + # ensure that self.x is a copy of x. Don't store a reference + # otherwise the memoization doesn't work properly. + + _x = atleast_nd(x, ndim=1, xp=self.xp) + self.x = self.xp.astype(_x, self.x_dtype) + self.f_updated = False + self.g_updated = False + self.H_updated = False + self._update_hess() else: - def update_x(x): - # ensure that self.x is a copy of x. Don't store a reference - # otherwise the memoization doesn't work properly. - _x = atleast_nd(x, ndim=1, xp=self.xp) - self.x = self.xp.astype(_x, self.x_dtype) - self.f_updated = False - self.g_updated = False - self.H_updated = False - self._update_x_impl = update_x + # ensure that self.x is a copy of x. Don't store a reference + # otherwise the memoization doesn't work properly. + _x = atleast_nd(x, ndim=1, xp=self.xp) + self.x = self.xp.astype(_x, self.x_dtype) + self.f_updated = False + self.g_updated = False + self.H_updated = False def _update_fun(self): if not self.f_updated: - self._update_fun_impl() + fx = self._wrapped_fun(self.x) + if fx < self._lowest_f: + self._lowest_x = self.x + self._lowest_f = fx + + self.f = fx self.f_updated = True def _update_grad(self): if not self.g_updated: - self._update_grad_impl() + if self._orig_grad in FD_METHODS: + self._update_fun() + self.g = self._wrapped_grad(self.x, f0=self.f) self.g_updated = True def _update_hess(self): if not self.H_updated: - self._update_hess_impl() + if self._orig_hess in FD_METHODS: + self._update_grad() + self.H = self._wrapped_hess(self.x, f0=self.g) + elif isinstance(self._orig_hess, HessianUpdateStrategy): + self._update_grad() + self.H.update(self.x - self.x_prev, self.g - self.g_prev) + else: # should be callable(hess) + self.H = self._wrapped_hess(self.x) + self.H_updated = True def fun(self, x): if not np.array_equal(x, self.x): - self._update_x_impl(x) + self._update_x(x) self._update_fun() return self.f def grad(self, x): if not np.array_equal(x, self.x): - self._update_x_impl(x) + self._update_x(x) self._update_grad() return self.g def hess(self, x): if not np.array_equal(x, self.x): - self._update_x_impl(x) + self._update_x(x) self._update_hess() return self.H def fun_and_grad(self, x): if not np.array_equal(x, self.x): - self._update_x_impl(x) + self._update_x(x) self._update_fun() self._update_grad() return self.f, self.g diff --git a/scipy/optimize/tests/test_differentiable_functions.py b/scipy/optimize/tests/test_differentiable_functions.py index 80e1606a8710..7a329135f91e 100644 --- a/scipy/optimize/tests/test_differentiable_functions.py +++ b/scipy/optimize/tests/test_differentiable_functions.py @@ -1,8 +1,10 @@ import pytest +import platform import numpy as np from numpy.testing import (TestCase, assert_array_almost_equal, assert_array_equal, assert_, assert_allclose, assert_equal) +from scipy._lib._gcutils import assert_deallocated from scipy.sparse import csr_matrix from scipy.sparse.linalg import LinearOperator from scipy.optimize._differentiable_functions import (ScalarFunction, @@ -756,3 +758,46 @@ def test_IdentityVectorFunction(): assert_array_equal(f2.jac(x), np.eye(3)) assert_array_equal(f1.hess(x, v).toarray(), np.zeros((3, 3))) + + +@pytest.mark.skipif( + platform.python_implementation() == "PyPy", + reason="assert_deallocate not available on PyPy" +) +def test_ScalarFunctionNoReferenceCycle(): + """Regression test for gh-20768.""" + ex = ExScalarFunction() + x0 = np.zeros(3) + with assert_deallocated(lambda: ScalarFunction(ex.fun, x0, (), ex.grad, + ex.hess, None, (-np.inf, np.inf))): + pass + + +@pytest.mark.skipif( + platform.python_implementation() == "PyPy", + reason="assert_deallocate not available on PyPy" +) +@pytest.mark.xfail(reason="TODO remove reference cycle from VectorFunction") +def test_VectorFunctionNoReferenceCycle(): + """Regression test for gh-20768.""" + ex = ExVectorialFunction() + x0 = [1.0, 0.0] + with assert_deallocated(lambda: VectorFunction(ex.fun, x0, ex.jac, + ex.hess, None, None, (-np.inf, np.inf), None)): + pass + + +@pytest.mark.skipif( + platform.python_implementation() == "PyPy", + reason="assert_deallocate not available on PyPy" +) +def test_LinearVectorFunctionNoReferenceCycle(): + """Regression test for gh-20768.""" + A_dense = np.array([ + [-1, 2, 0], + [0, 4, 2] + ]) + x0 = np.zeros(3) + A_sparse = csr_matrix(A_dense) + with assert_deallocated(lambda: LinearVectorFunction(A_sparse, x0, None)): + pass From b863eb91269bf0c229418dd23832a2df73acbd06 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 26 May 2024 02:57:56 -0700 Subject: [PATCH 260/500] CI: fail slow tests (not `--full`) (#20672) --- .github/workflows/windows.yml | 12 +- .../contributor/continuous_integration.rst | 44 ++++++ doc/source/dev/contributor/devpy_test.rst | 21 ++- environment.yml | 1 - scipy/_lib/tests/test__util.py | 2 + scipy/datasets/tests/test_data.py | 1 + scipy/fft/tests/test_multithreading.py | 1 + scipy/fftpack/tests/test_import.py | 2 + scipy/integrate/_ivp/tests/test_ivp.py | 1 + scipy/integrate/tests/test__quad_vec.py | 2 + scipy/integrate/tests/test_quadpack.py | 2 + scipy/integrate/tests/test_quadrature.py | 3 +- scipy/integrate/tests/test_tanhsinh.py | 3 +- scipy/interpolate/tests/test_interpnd.py | 1 + scipy/interpolate/tests/test_rgi.py | 3 + scipy/io/tests/test_mmio.py | 1 + scipy/linalg/tests/test_basic.py | 134 +++++++++--------- scipy/linalg/tests/test_decomp.py | 2 + scipy/linalg/tests/test_matfuncs.py | 1 + scipy/optimize/tests/test__basinhopping.py | 1 + .../tests/test__differential_evolution.py | 11 +- scipy/optimize/tests/test__dual_annealing.py | 5 + scipy/optimize/tests/test__shgo.py | 2 + scipy/optimize/tests/test_least_squares.py | 3 + scipy/optimize/tests/test_linprog.py | 2 + scipy/optimize/tests/test_lsq_linear.py | 1 + scipy/optimize/tests/test_optimize.py | 4 + .../optimize/tests/test_trustregion_exact.py | 2 + scipy/signal/tests/test_filter_design.py | 1 + scipy/signal/tests/test_signaltools.py | 2 + .../linalg/_isolve/tests/test_iterative.py | 2 + .../sparse/linalg/tests/test_expm_multiply.py | 3 + scipy/sparse/tests/test_base.py | 9 +- scipy/spatial/tests/test_kdtree.py | 2 + scipy/spatial/tests/test_qhull.py | 2 + scipy/special/tests/test_basic.py | 4 +- scipy/special/tests/test_cython_special.py | 1 + scipy/special/tests/test_round.py | 2 + .../test_support_alternative_backends.py | 1 + scipy/stats/tests/test_axis_nan_policy.py | 2 +- scipy/stats/tests/test_continuous_basic.py | 23 ++- scipy/stats/tests/test_resampling.py | 1 + scipy/stats/tests/test_stats.py | 3 +- 43 files changed, 239 insertions(+), 87 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index ea6147acc6dc..f055d4861273 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -21,8 +21,8 @@ jobs: name: Get commit message uses: ./.github/workflows/commit_message.yml - test: - name: fast, py3.12/npAny, dev.py + fast_dev_py_fail_slow: + name: fail slow, fast, py3.12/npAny, dev.py needs: get_commit_message # Ensure (a) this doesn't run on forks by default, and # (b) it does run with Act locally (`github` doesn't exist there) @@ -49,7 +49,7 @@ jobs: - name: pip-packages run: | - pip install numpy cython pybind11 pythran meson ninja pytest pytest-xdist pytest-timeout pooch rich_click click doit pydevtool hypothesis "scipy-openblas32<=0.3.23.293.2" + pip install numpy cython pybind11 pythran meson ninja pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich_click click doit pydevtool hypothesis "scipy-openblas32<=0.3.23.293.2" - name: Build run: | @@ -57,12 +57,12 @@ jobs: - name: Test run: | - python dev.py test -j2 + python dev.py test -j2 -- --durations=0 --durations-min=0.25 --fail-slow=1.0 ############################################################################# - full_dev_py_min_numpy: - name: full, py3.10/npMin, dev.py + full_dev_py_min_numpy_fail_slow: + name: fail slow, full, py3.10/npMin, dev.py needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 diff --git a/doc/source/dev/contributor/continuous_integration.rst b/doc/source/dev/contributor/continuous_integration.rst index 28cc083b715b..d6b96a4dd7f9 100644 --- a/doc/source/dev/contributor/continuous_integration.rst +++ b/doc/source/dev/contributor/continuous_integration.rst @@ -94,6 +94,47 @@ relevant docs checks (skip Cirrus and GitHub Actions' workflows):: [docs only] +Failures due to test duration +============================= + +Some CI jobs install |pytest-fail-slow|_ and report failures when the test +execution time exceeds a threshold duration. + +- By default, all tests are subject to a 5 second limit; i.e., the option + ``--fail-slow=5.0`` is used in a "full" test job. +- All tests not marked ``slow`` (``@pytest.mark.slow``) are subject to a + 1 second limit; i.e. the option ``--fail-slow=1.0`` is used in a "fast" + test job. +- Exceptions are made using the ``pytest.mark.fail_slow`` decorator; e.g. + a test can be marked ``@pytest.mark.fail_slow(10)`` to give it a ten + second limit regardless of whether it is part of the "fast" or "full" + test suite. + +If a test fails by exceeding the time limit at any point during the +development of a PR, please adjust the test to ensure that it does +not fail in the future. Even if new tests do not fail, please check +the details of workflows that include "fail slow" in their name +before PRs merge. These include lists of tests that are approaching +(or have exceeded) their time limit. Due to variation in execution +times, tests with execution times near the threshold should be adjusted +to avoid failure even if their execution time were to increase by 50%; +typical tests should have much greater margin (at least 400%). +Adjustment options include: + +- Making the test faster. +- Marking the test as ``slow``, if it is acceptable to run the test + on a reduced set of platforms. +- Marking the test as ``xslow``, if it is acceptable to run the test + only occasionally. +- Breaking up the test or parameterizing it, and possible marking + parts of it as slow. Note that this does not reduce the total + test duration, so other options are preferred. +- For truly critical tests that are unavoidably slow, add an exception + using ``pytest.mark.fail_slow``. + +See :ref:`devpy-test` for more information about working with slow tests +locally. + Wheel builds ============ @@ -119,3 +160,6 @@ It is not advised to use cibuildwheel to build scipy wheels on your own system as it will automatically install gfortran compilers and various other dependencies. Instead, one could use an isolated Docker container to build Linux wheels. + +.. |pytest-fail-slow| replace:: ``pytest-fail-slow`` +.. _pytest-fail-slow: https://github.com/jwodder/pytest-fail-slow \ No newline at end of file diff --git a/doc/source/dev/contributor/devpy_test.rst b/doc/source/dev/contributor/devpy_test.rst index ae5c2fc3cd60..544a9daf954b 100644 --- a/doc/source/dev/contributor/devpy_test.rst +++ b/doc/source/dev/contributor/devpy_test.rst @@ -95,14 +95,23 @@ Other useful options include: - ``-j`` or ``--parallel`` *n* to engage *n* cores when building SciPy; e.g. \ ``python dev.py test -j 4`` engages four cores. As of `#10172`_ this also runs the tests on four cores if |pytest-xdist|_ is installed. -- ``-m`` or ``--mode`` ``full`` to run the full test suite, including slow - tests. For example, ``python dev.py test -m full``. +- ``-m full`` or ``--mode full`` to run the "full" test suite, including + tests marked ``slow`` (e.g. with ``@pytest.mark.slow``). Note that this + does not *run* tests marked ``xslow``; see Tips below. - ``--`` to send remaining command line arguments to ``pytest`` instead of ``dev.py test``. For instance, while ``-n`` sent to ``pytest.py`` activates the ``--no-build`` option, ``-n`` sent to ``pytest`` runs the tests on multiple cores; e.g. \ ``python dev.py test -- -n 4`` runs tests using four cores. *Note:* |pytest-xdist|_ *must be installed for testing on - multiple cores.* + multiple cores.* Common command line arguments for ``pytest`` include: + + - ``--durations=m`` to display durations of the slowest ``m`` tests. Use + ``--durations=0`` together with ``--durations-min=x`` to display + durations of all tests with durations that exceed ``x`` seconds. + - ``--fail-slow=x`` to cause test to fail if they exceed ``x`` seconds. + (*Note*: |pytest-fail-slow|_ must be installed.) + - ``--timeout=x`` to halt all test execution if any test time exceeds + ``x`` seconds. (*Note*: |pytest-timeout|_ must be installed.) For much more information about ``pytest``, see the ``pytest`` `documentation `_. @@ -138,6 +147,12 @@ configuration, e.g. adding ``max_examples=100_000``. .. |pytest-xdist| replace:: ``pytest-xdist`` .. _pytest-xdist: https://pypi.org/project/pytest-xdist/ +.. |pytest-fail-slow| replace:: ``pytest-fail-slow`` +.. _pytest-fail-slow: https://github.com/jwodder/pytest-fail-slow + +.. |pytest-timeout| replace:: ``pytest-timeout`` +.. _pytest-timeout: https://github.com/pytest-dev/pytest-timeout + .. |pytest| replace:: ``pytest`` .. _pytest: https://docs.pytest.org/en/latest/ diff --git a/environment.yml b/environment.yml index 2f8dc77fb79c..84c31d1c4a22 100644 --- a/environment.yml +++ b/environment.yml @@ -27,7 +27,6 @@ dependencies: - pytest-cov - pytest-xdist - pytest-timeout - - pytest-fail-slow - asv >=0.6 - hypothesis - array-api-strict diff --git a/scipy/_lib/tests/test__util.py b/scipy/_lib/tests/test__util.py index 1456139f331a..b1f58acf3dc9 100644 --- a/scipy/_lib/tests/test__util.py +++ b/scipy/_lib/tests/test__util.py @@ -22,6 +22,7 @@ skip_xp_backends = pytest.mark.skip_xp_backends +@pytest.mark.slow def test__aligned_zeros(): niter = 10 @@ -394,6 +395,7 @@ class TestLazywhere: p = strategies.floats(min_value=0, max_value=1) data = strategies.data() + @pytest.mark.fail_slow(5) @pytest.mark.filterwarnings('ignore::RuntimeWarning') # overflows, etc. @skip_xp_backends('jax.numpy', reasons=["JAX arrays do not support item assignment"]) diff --git a/scipy/datasets/tests/test_data.py b/scipy/datasets/tests/test_data.py index f94ebbe71b5c..d29feb72f55f 100644 --- a/scipy/datasets/tests/test_data.py +++ b/scipy/datasets/tests/test_data.py @@ -35,6 +35,7 @@ def test_download_all(self): yield + @pytest.mark.fail_slow(5) def test_existence_all(self): assert len(os.listdir(data_dir)) >= len(registry) diff --git a/scipy/fft/tests/test_multithreading.py b/scipy/fft/tests/test_multithreading.py index e771aff63b17..1a6b71b83021 100644 --- a/scipy/fft/tests/test_multithreading.py +++ b/scipy/fft/tests/test_multithreading.py @@ -29,6 +29,7 @@ def _mt_fft(x): return fft.fft(x, workers=2) +@pytest.mark.slow def test_mixed_threads_processes(x): # Test that the fft threadpool is safe to use before & after fork diff --git a/scipy/fftpack/tests/test_import.py b/scipy/fftpack/tests/test_import.py index 8a978d9651e5..e71aec9bd07c 100644 --- a/scipy/fftpack/tests/test_import.py +++ b/scipy/fftpack/tests/test_import.py @@ -13,10 +13,12 @@ from pathlib import Path import re import tokenize +import pytest from numpy.testing import assert_ import scipy class TestFFTPackImport: + @pytest.mark.slow def test_fftpack_import(self): base = Path(scipy.__file__).parent regexp = r"\s*from.+\.fftpack import .*\n" diff --git a/scipy/integrate/_ivp/tests/test_ivp.py b/scipy/integrate/_ivp/tests/test_ivp.py index 1c542c5380eb..2443553f65a9 100644 --- a/scipy/integrate/_ivp/tests/test_ivp.py +++ b/scipy/integrate/_ivp/tests/test_ivp.py @@ -260,6 +260,7 @@ def test_integration_complex(): assert np.all(e < 5) +@pytest.mark.fail_slow(2) def test_integration_sparse_difference(): n = 200 t_span = [0, 20] diff --git a/scipy/integrate/tests/test__quad_vec.py b/scipy/integrate/tests/test__quad_vec.py index 316b905c8e5e..c88650ca1010 100644 --- a/scipy/integrate/tests/test__quad_vec.py +++ b/scipy/integrate/tests/test__quad_vec.py @@ -107,6 +107,7 @@ def _lorenzian(x): return 1 / (1 + x**2) +@pytest.mark.fail_slow(5) def test_quad_vec_pool(): f = _lorenzian res, err = quad_vec(f, -np.inf, np.inf, norm='max', epsabs=1e-4, workers=4) @@ -123,6 +124,7 @@ def _func_with_args(x, a): return x * (x + a) * np.arange(3) +@pytest.mark.fail_slow(5) @pytest.mark.parametrize('extra_args', [2, (2,)]) @pytest.mark.parametrize('workers', [1, 10]) def test_quad_vec_pool_args(extra_args, workers): diff --git a/scipy/integrate/tests/test_quadpack.py b/scipy/integrate/tests/test_quadpack.py index a7f6c7d195b0..a503cb54918b 100644 --- a/scipy/integrate/tests/test_quadpack.py +++ b/scipy/integrate/tests/test_quadpack.py @@ -541,6 +541,7 @@ def tfunc(x): class TestNQuad: + @pytest.mark.fail_slow(2) def test_fixed_limits(self): def func1(x0, x1, x2, x3): val = (x0**2 + x1*x2 - x3**3 + np.sin(x0) + @@ -555,6 +556,7 @@ def opts_basic(*args): assert_quad(res[:-1], 1.5267454070738635) assert_(res[-1]['neval'] > 0 and res[-1]['neval'] < 4e5) + @pytest.mark.fail_slow(2) def test_variable_limits(self): scale = .1 diff --git a/scipy/integrate/tests/test_quadrature.py b/scipy/integrate/tests/test_quadrature.py index 87f8d248243a..9006fb414152 100644 --- a/scipy/integrate/tests/test_quadrature.py +++ b/scipy/integrate/tests/test_quadrature.py @@ -655,6 +655,7 @@ def _get_theoretical_diff_between_simps_and_cum_simps(self, y, x): # `simpson` uses the trapezoidal rule return theoretical_difference + @pytest.mark.slow @given( y=hyp_num.arrays( np.float64, @@ -684,7 +685,7 @@ def simpson_reference(y): res[..., 1:], ref[..., 1:] + theoretical_difference[..., 1:] ) - + @pytest.mark.slow @given( y=hyp_num.arrays( np.float64, diff --git a/scipy/integrate/tests/test_tanhsinh.py b/scipy/integrate/tests/test_tanhsinh.py index 2680b89c6b02..084385cf9b4a 100644 --- a/scipy/integrate/tests/test_tanhsinh.py +++ b/scipy/integrate/tests/test_tanhsinh.py @@ -216,7 +216,8 @@ def test_accuracy(self, ref, case): if distname in {'dgamma', 'dweibull', 'laplace', 'kstwo'}: # should split up interval at first-derivative discontinuity pytest.skip('tanh-sinh is not great for non-smooth integrands') - if distname in {'studentized_range'} and not int(os.getenv('SCIPY_XSLOW', 0)): + if (distname in {'studentized_range', 'levy_stable'} + and not int(os.getenv('SCIPY_XSLOW', 0))): pytest.skip('This case passes, but it is too slow.') dist = getattr(stats, distname)(*params) x = dist.interval(ref) diff --git a/scipy/interpolate/tests/test_interpnd.py b/scipy/interpolate/tests/test_interpnd.py index 2c7d52b422fb..e77bd1119a1a 100644 --- a/scipy/interpolate/tests/test_interpnd.py +++ b/scipy/interpolate/tests/test_interpnd.py @@ -311,6 +311,7 @@ def test_tripoints_input_rescale(self): yi_rescale = interpnd.CloughTocher2DInterpolator(tri.points, y, rescale=True)(x) assert_almost_equal(yi, yi_rescale) + @pytest.mark.fail_slow(2) def test_dense(self): # Should be more accurate for dense meshes funcs = [ diff --git a/scipy/interpolate/tests/test_rgi.py b/scipy/interpolate/tests/test_rgi.py index e4855d205293..d1bf037068c2 100644 --- a/scipy/interpolate/tests/test_rgi.py +++ b/scipy/interpolate/tests/test_rgi.py @@ -467,6 +467,7 @@ def f(x, y): assert_equal(res[i], np.nan) assert_equal(res[~i], interp(z[~i])) + @pytest.mark.fail_slow(5) @parametrize_rgi_interp_methods @pytest.mark.parametrize(("ndims", "func"), [ (2, lambda x, y: 2 * x ** 3 + 3 * y ** 2), @@ -526,6 +527,7 @@ def test_fill_value(self, method): method=method, bounds_error=False) assert np.isnan(interp([10])) + @pytest.mark.fail_slow(2) @parametrize_rgi_interp_methods def test_nonscalar_values(self, method): @@ -849,6 +851,7 @@ def test_xi_broadcast(self, method): method=method, bounds_error=False) assert_allclose(v1, v2.reshape(v1.shape)) + @pytest.mark.fail_slow(2) @parametrize_rgi_interp_methods def test_nonscalar_values(self, method): diff --git a/scipy/io/tests/test_mmio.py b/scipy/io/tests/test_mmio.py index 77f4224de7c3..b7178cf448c1 100644 --- a/scipy/io/tests/test_mmio.py +++ b/scipy/io/tests/test_mmio.py @@ -127,6 +127,7 @@ def test_random_rectangular_float(self): a = np.random.random(sz) self.check(a, (20, 15, 300, 'array', 'real', 'general')) + @pytest.mark.fail_slow(5) def test_bad_number_of_array_header_fields(self): s = """\ %%MatrixMarket matrix array real general diff --git a/scipy/linalg/tests/test_basic.py b/scipy/linalg/tests/test_basic.py index 4449ad83f772..8a3f641f663e 100644 --- a/scipy/linalg/tests/test_basic.py +++ b/scipy/linalg/tests/test_basic.py @@ -1269,76 +1269,78 @@ def test_simple_underdet(self): atol=25 * _eps_cast(a1.dtype), err_msg="driver: %s" % lapack_driver) - def test_random_exact(self): + @pytest.mark.parametrize("dtype", REAL_DTYPES) + @pytest.mark.parametrize("n", (20, 200)) + @pytest.mark.parametrize("lapack_driver", lapack_drivers) + @pytest.mark.parametrize("overwrite", (True, False)) + def test_random_exact(self, dtype, n, lapack_driver, overwrite): rng = np.random.RandomState(1234) - for dtype in REAL_DTYPES: - for n in (20, 200): - for lapack_driver in TestLstsq.lapack_drivers: - for overwrite in (True, False): - a = np.asarray(rng.random([n, n]), dtype=dtype) - for i in range(n): - a[i, i] = 20 * (0.1 + a[i, i]) - for i in range(4): - b = np.asarray(rng.random([n, 3]), dtype=dtype) - # Store values in case they are overwritten later - a1 = a.copy() - b1 = b.copy() - out = lstsq(a1, b1, - lapack_driver=lapack_driver, - overwrite_a=overwrite, - overwrite_b=overwrite) - x = out[0] - r = out[2] - assert_(r == n, f'expected efficient rank {n}, ' - f'got {r}') - if dtype is np.float32: - assert_allclose( - dot(a, x), b, - rtol=500 * _eps_cast(a1.dtype), - atol=500 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) - else: - assert_allclose( - dot(a, x), b, - rtol=1000 * _eps_cast(a1.dtype), - atol=1000 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + + a = np.asarray(rng.random([n, n]), dtype=dtype) + for i in range(n): + a[i, i] = 20 * (0.1 + a[i, i]) + for i in range(4): + b = np.asarray(rng.random([n, 3]), dtype=dtype) + # Store values in case they are overwritten later + a1 = a.copy() + b1 = b.copy() + out = lstsq(a1, b1, + lapack_driver=lapack_driver, + overwrite_a=overwrite, + overwrite_b=overwrite) + x = out[0] + r = out[2] + assert_(r == n, f'expected efficient rank {n}, ' + f'got {r}') + if dtype is np.float32: + assert_allclose( + dot(a, x), b, + rtol=500 * _eps_cast(a1.dtype), + atol=500 * _eps_cast(a1.dtype), + err_msg="driver: %s" % lapack_driver) + else: + assert_allclose( + dot(a, x), b, + rtol=1000 * _eps_cast(a1.dtype), + atol=1000 * _eps_cast(a1.dtype), + err_msg="driver: %s" % lapack_driver) @pytest.mark.skipif(IS_MUSL, reason="may segfault on Alpine, see gh-17630") - def test_random_complex_exact(self): + @pytest.mark.parametrize("dtype", COMPLEX_DTYPES) + @pytest.mark.parametrize("n", (20, 200)) + @pytest.mark.parametrize("lapack_driver", lapack_drivers) + @pytest.mark.parametrize("overwrite", (True, False)) + def test_random_complex_exact(self, dtype, n, lapack_driver, overwrite): rng = np.random.RandomState(1234) - for dtype in COMPLEX_DTYPES: - for n in (20, 200): - for lapack_driver in TestLstsq.lapack_drivers: - for overwrite in (True, False): - a = np.asarray(rng.random([n, n]) + 1j*rng.random([n, n]), - dtype=dtype) - for i in range(n): - a[i, i] = 20 * (0.1 + a[i, i]) - for i in range(2): - b = np.asarray(rng.random([n, 3]), dtype=dtype) - # Store values in case they are overwritten later - a1 = a.copy() - b1 = b.copy() - out = lstsq(a1, b1, lapack_driver=lapack_driver, - overwrite_a=overwrite, - overwrite_b=overwrite) - x = out[0] - r = out[2] - assert_(r == n, f'expected efficient rank {n}, ' - f'got {r}') - if dtype is np.complex64: - assert_allclose( - dot(a, x), b, - rtol=400 * _eps_cast(a1.dtype), - atol=400 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) - else: - assert_allclose( - dot(a, x), b, - rtol=1000 * _eps_cast(a1.dtype), - atol=1000 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + + a = np.asarray(rng.random([n, n]) + 1j*rng.random([n, n]), + dtype=dtype) + for i in range(n): + a[i, i] = 20 * (0.1 + a[i, i]) + for i in range(2): + b = np.asarray(rng.random([n, 3]), dtype=dtype) + # Store values in case they are overwritten later + a1 = a.copy() + b1 = b.copy() + out = lstsq(a1, b1, lapack_driver=lapack_driver, + overwrite_a=overwrite, + overwrite_b=overwrite) + x = out[0] + r = out[2] + assert_(r == n, f'expected efficient rank {n}, ' + f'got {r}') + if dtype is np.complex64: + assert_allclose( + dot(a, x), b, + rtol=400 * _eps_cast(a1.dtype), + atol=400 * _eps_cast(a1.dtype), + err_msg="driver: %s" % lapack_driver) + else: + assert_allclose( + dot(a, x), b, + rtol=1000 * _eps_cast(a1.dtype), + atol=1000 * _eps_cast(a1.dtype), + err_msg="driver: %s" % lapack_driver) def test_random_overdet(self): rng = np.random.RandomState(1234) diff --git a/scipy/linalg/tests/test_decomp.py b/scipy/linalg/tests/test_decomp.py index 5e171965a4bd..40904fea4c09 100644 --- a/scipy/linalg/tests/test_decomp.py +++ b/scipy/linalg/tests/test_decomp.py @@ -1178,6 +1178,7 @@ class TestSVD_GESVD(TestSVD_GESDD): lapack_driver = 'gesvd' +@pytest.mark.fail_slow(5) def test_svd_gesdd_nofegfault(): # svd(a) with {U,VT}.size > INT_MAX does not segfault # cf https://github.com/scipy/scipy/issues/14001 @@ -2600,6 +2601,7 @@ def test_sort_explicit(self): class TestOrdQZWorkspaceSize: + @pytest.mark.fail_slow(2) def test_decompose(self): rng = np.random.RandomState(12345) N = 202 diff --git a/scipy/linalg/tests/test_matfuncs.py b/scipy/linalg/tests/test_matfuncs.py index 89e290478415..5f17a639eff7 100644 --- a/scipy/linalg/tests/test_matfuncs.py +++ b/scipy/linalg/tests/test_matfuncs.py @@ -752,6 +752,7 @@ def test_readonly(self): a.flags.writeable = False expm(a) + @pytest.mark.fail_slow(2) def test_gh18086(self): A = np.zeros((400, 400), dtype=float) rng = np.random.default_rng(100) diff --git a/scipy/optimize/tests/test__basinhopping.py b/scipy/optimize/tests/test__basinhopping.py index 9350d533c680..4fbd376ac2c1 100644 --- a/scipy/optimize/tests/test__basinhopping.py +++ b/scipy/optimize/tests/test__basinhopping.py @@ -199,6 +199,7 @@ def test_2d_nograd(self): niter=self.niter, disp=self.disp) assert_almost_equal(res.x, self.sol[i], self.tol) + @pytest.mark.fail_slow(5) def test_all_minimizers(self): # Test 2-D minimizations with gradient. Nelder-Mead, Powell, COBYLA, and # COBYQA don't accept jac=True, so aren't included here. diff --git a/scipy/optimize/tests/test__differential_evolution.py b/scipy/optimize/tests/test__differential_evolution.py index 9b51c80ed810..3f0b8e53551c 100644 --- a/scipy/optimize/tests/test__differential_evolution.py +++ b/scipy/optimize/tests/test__differential_evolution.py @@ -712,6 +712,7 @@ def test_immediate_updating(self): pass assert s._updating == 'deferred' + @pytest.mark.fail_slow(5) def test_parallel(self): # smoke test for parallelization with deferred updating bounds = [(0., 2.), (0., 2.)] @@ -853,6 +854,7 @@ def constr_f(x): assert constr_f(res.x) <= 1.9 assert res.success + @pytest.mark.fail_slow(5) def test_impossible_constraint(self): def constr_f(x): return np.array([x[0] + x[1]]) @@ -1009,7 +1011,7 @@ def test_matrix_linear_constraint(self): xtrial = np.arange(4 * 5).reshape(4, 5) assert cw.violation(xtrial).shape == (2, 5) - + @pytest.mark.fail_slow(10) def test_L1(self): # Lampinen ([5]) test problem 1 @@ -1104,6 +1106,7 @@ def c2(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) + @pytest.mark.fail_slow(5) def test_L2(self): # Lampinen ([5]) test problem 2 @@ -1143,6 +1146,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) + @pytest.mark.fail_slow(5) def test_L3(self): # Lampinen ([5]) test problem 3 @@ -1193,6 +1197,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) + @pytest.mark.fail_slow(5) def test_L4(self): # Lampinen ([5]) test problem 4 def f(x): @@ -1245,6 +1250,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) + @pytest.mark.fail_slow(5) def test_L5(self): # Lampinen ([5]) test problem 5 @@ -1275,6 +1281,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) + @pytest.mark.fail_slow(5) def test_L6(self): # Lampinen ([5]) test problem 6 def f(x): @@ -1433,6 +1440,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) + @pytest.mark.fail_slow(5) def test_integrality(self): # test fitting discrete distribution to data rng = np.random.default_rng(6519843218105) @@ -1522,6 +1530,7 @@ def f(x): DifferentialEvolutionSolver(f, bounds=bounds, polish=False, integrality=integrality) + @pytest.mark.fail_slow(5) def test_vectorized(self): def quadratic(x): return np.sum(x**2) diff --git a/scipy/optimize/tests/test__dual_annealing.py b/scipy/optimize/tests/test__dual_annealing.py index 51b2dd4066cd..6819d7f2be52 100644 --- a/scipy/optimize/tests/test__dual_annealing.py +++ b/scipy/optimize/tests/test__dual_annealing.py @@ -110,6 +110,7 @@ def test_low_dim(self): assert_allclose(ret.fun, 0., atol=1e-12) assert ret.success + @pytest.mark.fail_slow(5) def test_high_dim(self): ret = dual_annealing(self.func, self.hd_bounds, seed=self.seed) assert_allclose(ret.fun, 0., atol=1e-12) @@ -120,6 +121,7 @@ def test_low_dim_no_ls(self): no_local_search=True, seed=self.seed) assert_allclose(ret.fun, 0., atol=1e-4) + @pytest.mark.fail_slow(5) def test_high_dim_no_ls(self): ret = dual_annealing(self.func, self.hd_bounds, no_local_search=True, seed=self.seed) @@ -138,6 +140,7 @@ def test_max_reinit(self): assert_raises(ValueError, dual_annealing, self.weirdfunc, self.ld_bounds) + @pytest.mark.fail_slow(5) def test_reproduce(self): res1 = dual_annealing(self.func, self.ld_bounds, seed=self.seed) res2 = dual_annealing(self.func, self.ld_bounds, seed=self.seed) @@ -279,6 +282,7 @@ def test_gradient_gnev(self): seed=self.seed) assert ret.njev == self.ngev + @pytest.mark.fail_slow(5) def test_from_docstring(self): def func(x): return np.sum(x * x - 10 * np.cos(2 * np.pi * x)) + 10 * np.size(x) @@ -334,6 +338,7 @@ def test_accept_reject_probabilistic( assert_allclose(rate, accept_rate) + @pytest.mark.fail_slow(5) def test_bounds_class(self): # test that result does not depend on the bounds type def func(x): diff --git a/scipy/optimize/tests/test__shgo.py b/scipy/optimize/tests/test__shgo.py index a83b8510de78..fea1fb70fbda 100644 --- a/scipy/optimize/tests/test__shgo.py +++ b/scipy/optimize/tests/test__shgo.py @@ -470,6 +470,7 @@ def test_f5_2_cons_symmetry(self): options=options, iters=1, sampling_method='simplicial') + @pytest.mark.fail_slow(5) def test_f5_3_cons_symmetry(self): """Assymmetrically constrained test function""" options = {'symmetry': [0, 0, 0, 3], @@ -777,6 +778,7 @@ def f(x): np.testing.assert_allclose(res_new_bounds.x, x_opt) np.testing.assert_allclose(res_new_bounds.x, res_old_bounds.x) + @pytest.mark.fail_slow(5) def test_19_parallelization(self): """Test the functionality to add custom sampling methods to shgo""" diff --git a/scipy/optimize/tests/test_least_squares.py b/scipy/optimize/tests/test_least_squares.py index 114e95d9a29f..68cfc421c987 100644 --- a/scipy/optimize/tests/test_least_squares.py +++ b/scipy/optimize/tests/test_least_squares.py @@ -4,6 +4,7 @@ from numpy.linalg import norm from numpy.testing import (assert_, assert_allclose, assert_equal, suppress_warnings) +import pytest from pytest import raises as assert_raises from scipy.sparse import issparse, lil_matrix from scipy.sparse.linalg import aslinearoperator @@ -467,6 +468,7 @@ def test_bounds_instances(self): bounds=Bounds(lb=[0.1, 0.1])) assert_allclose(res.x, [0.1, 0.1], atol=1e-5) + @pytest.mark.fail_slow(5) def test_rosenbrock_bounds(self): x0_1 = np.array([-2.0, 1.0]) x0_2 = np.array([2.0, 2.0]) @@ -552,6 +554,7 @@ def test_numerical_jac(self): assert_allclose(res_dense.cost, 0, atol=1e-20) assert_allclose(res_sparse.cost, 0, atol=1e-20) + @pytest.mark.fail_slow(5) def test_with_bounds(self): p = BroydenTridiagonal() for jac, jac_sparsity in product( diff --git a/scipy/optimize/tests/test_linprog.py b/scipy/optimize/tests/test_linprog.py index 3a3f88ce41ea..1e304cd038ad 100644 --- a/scipy/optimize/tests/test_linprog.py +++ b/scipy/optimize/tests/test_linprog.py @@ -1802,6 +1802,7 @@ def test_crossover(self): # there should be nonzero crossover iterations for IPM (only) assert_equal(res.crossover_nit == 0, self.method != "highs-ipm") + @pytest.mark.fail_slow(5) def test_marginals(self): # Ensure lagrange multipliers are correct by comparing the derivative # w.r.t. b_ub/b_eq/ub/lb to the reported duals. @@ -2248,6 +2249,7 @@ class TestLinprogHiGHSMIP: method = "highs" options = {} + @pytest.mark.fail_slow(5) @pytest.mark.xfail(condition=(sys.maxsize < 2 ** 32 and platform.system() == "Linux"), run=False, diff --git a/scipy/optimize/tests/test_lsq_linear.py b/scipy/optimize/tests/test_lsq_linear.py index 348f4fc1350c..a2fdd1221851 100644 --- a/scipy/optimize/tests/test_lsq_linear.py +++ b/scipy/optimize/tests/test_lsq_linear.py @@ -207,6 +207,7 @@ def test_sparse_and_LinearOperator(self): res = lsq_linear(A, b) assert_allclose(res.optimality, 0, atol=1e-6) + @pytest.mark.fail_slow(5) def test_sparse_bounds(self): m = 5000 n = 1000 diff --git a/scipy/optimize/tests/test_optimize.py b/scipy/optimize/tests/test_optimize.py index ce1983000eec..86c6ab268ee4 100644 --- a/scipy/optimize/tests/test_optimize.py +++ b/scipy/optimize/tests/test_optimize.py @@ -1261,6 +1261,7 @@ def dfunc(z): assert func(sol1.x) < func(sol2.x), \ f"{method}: {func(sol1.x)} vs. {func(sol2.x)}" + @pytest.mark.fail_slow(5) @pytest.mark.filterwarnings('ignore::UserWarning') @pytest.mark.filterwarnings('ignore::RuntimeWarning') # See gh-18547 @pytest.mark.parametrize('method', @@ -2583,6 +2584,7 @@ def f(x): optimize.brute(f, [(-1, 1)], Ns=3, finish=None) + @pytest.mark.fail_slow(5) def test_workers(self): # check that parallel evaluation works resbrute = optimize.brute(brute_func, self.rranges, args=self.params, @@ -2613,6 +2615,7 @@ def f(x, *args): assert_allclose(resbrute, 0) +@pytest.mark.fail_slow(10) def test_cobyla_threadsafe(): # Verify that cobyla is threadsafe. Will segfault if it is not. @@ -2660,6 +2663,7 @@ def slow_func(self, v): r, t = np.sqrt(v[0]**2+v[1]**2), np.arctan2(v[0], v[1]) return np.sin(r*20 + t)+r*0.5 + @pytest.mark.fail_slow(5) def test_neldermead_limit(self): self.check_limits("Nelder-Mead", 200) diff --git a/scipy/optimize/tests/test_trustregion_exact.py b/scipy/optimize/tests/test_trustregion_exact.py index ec3e42fd9152..42c649218078 100644 --- a/scipy/optimize/tests/test_trustregion_exact.py +++ b/scipy/optimize/tests/test_trustregion_exact.py @@ -5,6 +5,7 @@ nosetests test_optimize.py """ +import pytest import numpy as np from scipy.optimize._trustregion_exact import ( estimate_smallest_singular_value, @@ -274,6 +275,7 @@ def test_for_jac_very_close_to_zero(self): -0.84954934]) assert_array_almost_equal(hits_boundary, True) + @pytest.mark.fail_slow(5) def test_for_random_entries(self): # Seed np.random.seed(1) diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index f2f3ac962737..47d24323edbf 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -2493,6 +2493,7 @@ def test_invalid(self): assert_raises(ValueError, _bessel_poly, -3) assert_raises(ValueError, _bessel_poly, 3.3) + @pytest.mark.fail_slow(5) def test_fs_param(self): for norm in ('phase', 'mag', 'delay'): for fs in (900, 900.1, 1234.567): diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py index 28e9930a5578..5e7ebc1de9ef 100644 --- a/scipy/signal/tests/test_signaltools.py +++ b/scipy/signal/tests/test_signaltools.py @@ -2441,6 +2441,7 @@ def check_filtfilt_gust(b, a, shape, axis, irlen=None): assert_allclose(zg2, zo2, rtol=1e-8, atol=1e-9) +@pytest.mark.fail_slow(5) def test_choose_conv_method(): for mode in ['valid', 'same', 'full']: for ndim in [1, 2]: @@ -2472,6 +2473,7 @@ def test_choose_conv_method(): assert_equal(choose_conv_method(x, h, mode=mode), 'direct') +@pytest.mark.fail_slow(5) def test_filtfilt_gust(): # Design a filter. z, p, k = signal.ellip(3, 0.01, 120, 0.0875, output='zpk') diff --git a/scipy/sparse/linalg/_isolve/tests/test_iterative.py b/scipy/sparse/linalg/_isolve/tests/test_iterative.py index f401f2c0271a..2fda70fcb319 100644 --- a/scipy/sparse/linalg/_isolve/tests/test_iterative.py +++ b/scipy/sparse/linalg/_isolve/tests/test_iterative.py @@ -308,6 +308,7 @@ def identity(b, which=None): # Specific test for poisson1d and poisson2d cases +@pytest.mark.fail_slow(5) @pytest.mark.parametrize('case', [x for x in IterativeParams().cases if x.name in ('poisson1d', 'poisson2d')], ids=['poisson1d', 'poisson2d']) @@ -684,6 +685,7 @@ def test_abi(self): assert_allclose(r_x, x) assert r_info == info + @pytest.mark.fail_slow(5) def test_atol_legacy(self): A = eye(2) diff --git a/scipy/sparse/linalg/tests/test_expm_multiply.py b/scipy/sparse/linalg/tests/test_expm_multiply.py index 03677bea159c..858ce11b9d4b 100644 --- a/scipy/sparse/linalg/tests/test_expm_multiply.py +++ b/scipy/sparse/linalg/tests/test_expm_multiply.py @@ -179,6 +179,7 @@ def test_complex(self): class TestExpmActionInterval: + @pytest.mark.fail_slow(5) def test_sparse_expm_multiply_interval(self): np.random.seed(1234) start = 0.1 @@ -204,6 +205,7 @@ def test_sparse_expm_multiply_interval(self): for solution, t in zip(X, samples): assert_allclose(solution, sp_expm(t*A).dot(target)) + @pytest.mark.fail_slow(5) def test_expm_multiply_interval_vector(self): np.random.seed(1234) interval = {'start': 0.1, 'stop': 3.2, 'endpoint': True} @@ -230,6 +232,7 @@ def test_expm_multiply_interval_vector(self): assert_allclose(sol_given, correct) assert_allclose(sol_wrong, correct) + @pytest.mark.fail_slow(5) def test_expm_multiply_interval_matrix(self): np.random.seed(1234) interval = {'start': 0.1, 'stop': 3.2, 'endpoint': True} diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index 3415c70be2be..1aa76b8ff26e 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -16,6 +16,9 @@ class for generic tests" section. import itertools import sys +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, @@ -24,8 +27,7 @@ class for generic tests" section. import random from numpy.testing import (assert_equal, assert_array_equal, assert_array_almost_equal, assert_almost_equal, assert_, - assert_allclose,suppress_warnings) -from pytest import raises as assert_raises + assert_allclose, suppress_warnings) import scipy.linalg @@ -41,8 +43,6 @@ class for generic tests" section. from scipy._lib.decorator import decorator from scipy._lib._util import ComplexWarning -import pytest - IS_COLAB = ('google.colab' in sys.modules) @@ -3383,6 +3383,7 @@ def __arith_init(self): self.__Asp = self.spcreator(self.__A) self.__Bsp = self.spcreator(self.__B) + @pytest.mark.fail_slow(5) def test_add_sub(self): self.__arith_init() diff --git a/scipy/spatial/tests/test_kdtree.py b/scipy/spatial/tests/test_kdtree.py index faf87872ea3b..b710c9df6a8f 100644 --- a/scipy/spatial/tests/test_kdtree.py +++ b/scipy/spatial/tests/test_kdtree.py @@ -971,6 +971,7 @@ def test_kdtree_list_k(kdtree_type): assert_equal(dd, np.ravel(dd1)) assert_equal(ii, np.ravel(ii1)) +@pytest.mark.fail_slow(5) def test_kdtree_box(kdtree_type): # check ckdtree periodic boundary n = 2000 @@ -1145,6 +1146,7 @@ def test_kdtree_weights(kdtree_type): assert_raises(ValueError, tree1.count_neighbors, tree2, np.linspace(0, 10, 100), weights=w1) +@pytest.mark.fail_slow(5) def test_kdtree_count_neighbous_multiple_r(kdtree_type): n = 2000 m = 2 diff --git a/scipy/spatial/tests/test_qhull.py b/scipy/spatial/tests/test_qhull.py index 7efaa5525896..94ed6c40a8dd 100644 --- a/scipy/spatial/tests/test_qhull.py +++ b/scipy/spatial/tests/test_qhull.py @@ -327,6 +327,7 @@ def barycentric_transform(tr, x): ok = (j != -1) | at_boundary assert_(ok.all(), f"{err_msg} {np.nonzero(~ok)}") + @pytest.mark.fail_slow(5) def test_degenerate_barycentric_transforms(self): # The triangulation should not produce invalid barycentric # transforms that stump the simplex finding @@ -961,6 +962,7 @@ def test_furthest_site_flag(self): vor = Voronoi(points,furthest_site=True) assert_equal(vor.furthest_site,True) + @pytest.mark.fail_slow(5) @pytest.mark.parametrize("name", sorted(INCREMENTAL_DATASETS)) def test_incremental(self, name): # Test incremental construction of the triangulation diff --git a/scipy/special/tests/test_basic.py b/scipy/special/tests/test_basic.py index 9284110e937e..f8f681e7301b 100644 --- a/scipy/special/tests/test_basic.py +++ b/scipy/special/tests/test_basic.py @@ -1063,6 +1063,7 @@ def test_ai_zeros(self): array([0.5357]), array([0.7012])),4) + @pytest.mark.fail_slow(2) def test_ai_zeros_big(self): z, zp, ai_zpx, aip_zx = special.ai_zeros(50000) ai_z, aip_z, _, _ = special.airy(z) @@ -1087,6 +1088,7 @@ def test_ai_zeros_big(self): [-1.0187929716, -3.2481975822, -4.8200992112, -6.1633073556, -7.3721772550, -8.4884867340], rtol=1e-10) + @pytest.mark.fail_slow(2) def test_bi_zeros_big(self): z, zp, bi_zpx, bip_zx = special.bi_zeros(50000) _, _, bi_z, bip_z = special.airy(z) @@ -1469,7 +1471,7 @@ def test_perm_zeros(self): 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.]) - + class TestTrigonometric: def test_cbrt(self): diff --git a/scipy/special/tests/test_cython_special.py b/scipy/special/tests/test_cython_special.py index 4d26311d3fe2..bfd28744747c 100644 --- a/scipy/special/tests/test_cython_special.py +++ b/scipy/special/tests/test_cython_special.py @@ -320,6 +320,7 @@ def test_cython_api_completeness(): raise RuntimeError(f"{name} missing from tests!") +@pytest.mark.fail_slow(5) @pytest.mark.parametrize("param", PARAMS, ids=IDS) def test_cython_api(param): pyfunc, cyfunc, specializations, knownfailure = param diff --git a/scipy/special/tests/test_round.py b/scipy/special/tests/test_round.py index ec27e7eed2fe..07d3850f1084 100644 --- a/scipy/special/tests/test_round.py +++ b/scipy/special/tests/test_round.py @@ -4,12 +4,14 @@ from scipy.special import _test_internal +@pytest.mark.fail_slow(5) @pytest.mark.skipif(not _test_internal.have_fenv(), reason="no fenv()") def test_add_round_up(): np.random.seed(1234) _test_internal.test_add_round(10**5, 'up') +@pytest.mark.fail_slow(5) @pytest.mark.skipif(not _test_internal.have_fenv(), reason="no fenv()") def test_add_round_down(): np.random.seed(1234) diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index 742c3313ce34..6d0798409e51 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -47,6 +47,7 @@ def test_rel_entr_generic(dtype): xp_assert_close(res, xp.asarray(ref), xp=xp) +@pytest.mark.fail_slow(2) @array_api_compatible @given(data=strategies.data()) @pytest.mark.parametrize('f_name_n_args', array_special_func_map.items()) diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index b9c73666186e..14a3fba372e0 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -249,7 +249,7 @@ def nan_policy_1d(hypotest, data1d, unpacker, *args, n_outputs=2, def test_axis_nan_policy_fast(hypotest, args, kwds, n_samples, n_outputs, paired, unpacker, nan_policy, axis, data_generator): - if hypotest in {stats.cramervonmises_2samp} and not SCIPY_XSLOW: + if hypotest in {stats.cramervonmises_2samp, 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) diff --git a/scipy/stats/tests/test_continuous_basic.py b/scipy/stats/tests/test_continuous_basic.py index 9f93fb662350..100eae2f5287 100644 --- a/scipy/stats/tests/test_continuous_basic.py +++ b/scipy/stats/tests/test_continuous_basic.py @@ -51,9 +51,12 @@ 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', 'trapezoid', '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', 'halfgennorm', 'johnsonsb', 'kstwobign', 'ncx2', 'norminvgauss', 'truncnorm', @@ -86,6 +89,9 @@ '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 @@ -200,6 +206,7 @@ def test_cont_basic(distname, arg, sn): 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).") @@ -207,6 +214,9 @@ def cases_test_cont_basic_fit(): 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 @@ -216,6 +226,9 @@ def cases_test_cont_basic_fit(): 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 @@ -788,9 +801,17 @@ def check_fit_args_fix(distfn, arg, rvs, method): 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', distcont) +@pytest.mark.parametrize('distname, args', cases_test_methods_with_lists()) def test_methods_with_lists(method, distname, args): # Test that the continuous distributions can accept Python lists # as arguments. diff --git a/scipy/stats/tests/test_resampling.py b/scipy/stats/tests/test_resampling.py index 2119da896750..2d296f4d0dc7 100644 --- a/scipy/stats/tests/test_resampling.py +++ b/scipy/stats/tests/test_resampling.py @@ -1655,6 +1655,7 @@ def pvalue1d(*data): # -- Paired-Sample Tests -- # + @pytest.mark.slow @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided")) def test_against_wilcoxon(self, alternative): diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 7d4bddc60e12..356a33915c7b 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -586,6 +586,7 @@ def test_input_validation(self): with pytest.raises(ValueError, match=message): res.confidence_interval(method="exact") + @pytest.mark.fail_slow(2) @pytest.mark.skip_xp_backends(np_only=True) @pytest.mark.xfail_on_32bit("Monte Carlo method needs > a few kB of memory") @pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided')) @@ -5984,7 +5985,7 @@ def test_ttest_1samp_new(xp): @pytest.mark.usefixtures("skip_xp_backends") @array_api_compatible def test_ttest_1samp_new_omit(xp): - n1, n2, n3 = (10, 15, 20) + n1, n2, n3 = (5, 10, 15) rvn1 = stats.norm.rvs(loc=5, scale=10, size=(n1, n2, n3)) rvn1 = xp.asarray(rvn1) From 0128f44b83c72ecf3c5653310201dbfacd7724b0 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sun, 26 May 2024 14:15:19 +0200 Subject: [PATCH 261/500] BLD: optimize: silence build warnings coming from HiGHS This got annoying, and the PR that would pull in the upstream fix was reverted. Since we're close to the 1.14.x branch point, let's silence these warnings. Closes gh-19734 [skip cirrus] [skip circle] --- scipy/meson.build | 4 +++- scipy/optimize/_highs/meson.build | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/scipy/meson.build b/scipy/meson.build index a3c331ef0943..988458d21a3b 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -369,9 +369,11 @@ Wno_unused_result = cc.get_supported_arguments('-Wno-unused-result') Wno_unused_variable = cc.get_supported_arguments('-Wno-unused-variable') # C++ warning flags +_cpp_Wno_bitwise_instead_of_logical = cpp.get_supported_arguments('-Wno-bitwise-instead-of-logical') _cpp_Wno_cpp = cpp.get_supported_arguments('-Wno-cpp') -_cpp_Wno_deprecated_declarations = cpp.get_supported_arguments('-Wno-deprecated-declarations') _cpp_Wno_class_memaccess = cpp.get_supported_arguments('-Wno-class-memaccess') +_cpp_Wno_deprecated_declarations = cpp.get_supported_arguments('-Wno-deprecated-declarations') +_cpp_Wno_deprecated_builtins = cpp.get_supported_arguments('-Wno-deprecated-builtins') _cpp_Wno_format_truncation = cpp.get_supported_arguments('-Wno-format-truncation') _cpp_Wno_non_virtual_dtor = cpp.get_supported_arguments('-Wno-non-virtual-dtor') _cpp_Wno_sign_compare = cpp.get_supported_arguments('-Wno-sign-compare') diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index 8d701e5e3f67..7ef1cefc65dd 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -55,7 +55,10 @@ basiclu_lib = static_library('basiclu', ) highs_flags = [ + _cpp_Wno_bitwise_instead_of_logical, _cpp_Wno_class_memaccess, + _cpp_Wno_deprecated_builtins, + _cpp_Wno_deprecated_declarations, _cpp_Wno_format_truncation, _cpp_Wno_non_virtual_dtor, _cpp_Wno_sign_compare, From 727e8e6e35938c98497de7858a2b8cb6fe25410b Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sun, 26 May 2024 14:52:14 +0200 Subject: [PATCH 262/500] MAINT: special: fix numpy initialization, avoid build warnings The warnings were: ``` [3/6] Compiling C++ object scipy/special/_gufuncs.cpython-310-darwin.so.p/_gufuncs.cpp.o In file included from ../scipy/special/_gufuncs.cpp:1: ../scipy/special/ufunc.h:22:5: warning: implicit conversion of NULL constant to 'bool' [-Wnull-conversion] import_array(); ^~~~~~~~~~~~~~ ../../../mambaforge/envs/scipy-dev/lib/python3.10/site-packages/numpy/_core/include/numpy/__multiarray_api.h:1583:12: note: expanded from macro 'import_array' return NULL; \ ~~~~~~ ^ In file included from ../scipy/special/_gufuncs.cpp:1: ../scipy/special/ufunc.h:27:5: warning: implicit conversion of NULL constant to 'bool' [-Wnull-conversion] import_umath(); ^~~~~~~~~~~~~~ ../../../mambaforge/envs/scipy-dev/lib/python3.10/site-packages/numpy/_core/include/numpy/__ufunc_api.h:294:20: note: expanded from macro 'import_umath' return NULL;\ ~~~~~~ ^ 2 warnings generated. [5/6] Compiling C++ object scipy/special/_special_ufuncs.cpython-310-darwin.so.p/_special_ufuncs.cpp.o In file included from ../scipy/special/_special_ufuncs.cpp:1: ../scipy/special/ufunc.h:22:5: warning: implicit conversion of NULL constant to 'bool' [-Wnull-conversion] import_array(); ^~~~~~~~~~~~~~ ../../../mambaforge/envs/scipy-dev/lib/python3.10/site-packages/numpy/_core/include/numpy/__multiarray_api.h:1583:12: note: expanded from macro 'import_array' return NULL; \ ~~~~~~ ^ In file included from ../scipy/special/_special_ufuncs.cpp:1: ../scipy/special/ufunc.h:27:5: warning: implicit conversion of NULL constant to 'bool' [-Wnull-conversion] import_umath(); ^~~~~~~~~~~~~~ ../../../mambaforge/envs/scipy-dev/lib/python3.10/site-packages/numpy/_core/include/numpy/__ufunc_api.h:294:20: note: expanded from macro 'import_umath' return NULL;\ ~~~~~~ ^ 2 warnings generated. ``` [skip cirrus] [skip circle] --- scipy/special/_gufuncs.cpp | 8 +++++--- scipy/special/_special_ufuncs.cpp | 8 +++++--- scipy/special/ufunc.h | 14 -------------- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/scipy/special/_gufuncs.cpp b/scipy/special/_gufuncs.cpp index a4a624246526..90ee4d69baf7 100644 --- a/scipy/special/_gufuncs.cpp +++ b/scipy/special/_gufuncs.cpp @@ -59,13 +59,15 @@ static PyModuleDef _gufuncs_def = { }; PyMODINIT_FUNC PyInit__gufuncs() { - if (!SpecFun_Initialize()) { - return nullptr; + import_array(); + import_umath(); + if (PyErr_Occurred()) { + return NULL; } PyObject *_gufuncs = PyModule_Create(&_gufuncs_def); if (_gufuncs == nullptr) { - return nullptr; + return NULL; } PyObject *_lpn = SpecFun_NewGUFunc( diff --git a/scipy/special/_special_ufuncs.cpp b/scipy/special/_special_ufuncs.cpp index d143f3180645..b9c2dfc0672b 100644 --- a/scipy/special/_special_ufuncs.cpp +++ b/scipy/special/_special_ufuncs.cpp @@ -212,13 +212,15 @@ static PyModuleDef _special_ufuncs_def = { }; PyMODINIT_FUNC PyInit__special_ufuncs() { - if (!SpecFun_Initialize()) { - return nullptr; + import_array(); + import_umath(); + if (PyErr_Occurred()) { + return NULL; } PyObject *_special_ufuncs = PyModule_Create(&_special_ufuncs_def); if (_special_ufuncs == nullptr) { - return nullptr; + return NULL; } PyObject *_cospi = SpecFun_NewUFunc( diff --git a/scipy/special/ufunc.h b/scipy/special/ufunc.h index 4105dbbfb91d..86e3dd13a75c 100644 --- a/scipy/special/ufunc.h +++ b/scipy/special/ufunc.h @@ -17,20 +17,6 @@ #include "sf_error.h" #include "special/mdspan.h" -// Initializes NumPy. -inline bool SpecFun_Initialize() { - import_array(); - if (PyErr_Occurred() != nullptr) { - return false; // import array failed - } - - import_umath(); - if (PyErr_Occurred() != nullptr) { - return false; // import umath failed - } - - return true; -} // This is std::accumulate, but that is not constexpr until C++20 template From 3bd728c20eedc4af2d17941dfd305ad3100bf6ff Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sun, 26 May 2024 12:56:49 +0200 Subject: [PATCH 263/500] MAINT: fix warnings about `noexcept` and `except *` in Cython code The warnings were: ``` [4/12] Generating 'scipy/optimize/_highs/_highs_...on-311-x86_64-linux-gnu.so.p/_highs_wrapper.cpp' warning: /home/rgommers/code/scipy/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx:70:21: noexcept clause is ignored for function returning Python object warning: /home/rgommers/code/scipy/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx:115:18: noexcept clause is ignored for function returning Python object [6/12] Generating 'scipy/stats/_stats.cpython-311-x86_64-linux-gnu.so.p/_stats.c' performance hint: /home/rgommers/code/scipy/scipy/stats/_stats.pxd:2:28: No exception value declared for '_geninvgauss_pdf' in pxd file. Users cimporting this function and calling it without the gil will always require an exception check. Suggest adding an explicit exception value. performance hint: /home/rgommers/code/scipy/scipy/stats/_stats.pxd:8:30: No exception value declared for '_genhyperbolic_pdf' in pxd file. Users cimporting this function and calling it without the gil will always require an exception check. Suggest adding an explicit exception value. performance hint: /home/rgommers/code/scipy/scipy/stats/_stats.pxd:9:33: No exception value declared for '_genhyperbolic_logpdf' in pxd file. Users cimporting this function and calling it without the gil will always require an exception check. Suggest adding an explicit exception value. ``` Comments: - for `_stats.pyx`/`_stats.pxd`: there doesn't seem to be a way that these functions can yield an error - see discussion in issue 20795. - for `_highs_wrapper`: the `noexcept` annotations were wrong, because the first function returns a Python object (which implies `noexcept` should not be used), and the second one uses `warnings.warn` which certainly can cause an exception. These warnings are new in Cython 3.0.9 (for `noexcept`) and 3.0.10 (for `except *`). --- scipy/optimize/_highs/cython/src/_highs_wrapper.pyx | 4 ++-- scipy/stats/_stats.pxd | 7 ++++--- scipy/stats/_stats.pyx | 7 ++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx b/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx index d5da4e4fea25..b84759a2cc4a 100644 --- a/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx +++ b/scipy/optimize/_highs/cython/src/_highs_wrapper.pyx @@ -67,7 +67,7 @@ for _r in _ref_opts.records: _ref_opt_lookup[_r.name] = _r -cdef str _opt_warning(string name, val, valid_set=None) noexcept: +cdef str _opt_warning(string name, val, valid_set=None): cdef OptionRecord * r = _ref_opt_lookup[name] # BOOL @@ -112,7 +112,7 @@ cdef str _opt_warning(string name, val, valid_set=None) noexcept: 'See documentation for valid options. ' 'Using default.' % (name.decode(), str(val))) -cdef apply_options(dict options, Highs & highs) noexcept: +cdef void apply_options(dict options, Highs & highs): '''Take options from dictionary and apply to HiGHS object.''' # Initialize for error checking diff --git a/scipy/stats/_stats.pxd b/scipy/stats/_stats.pxd index a7085943001b..e01565f75fe2 100644 --- a/scipy/stats/_stats.pxd +++ b/scipy/stats/_stats.pxd @@ -1,9 +1,10 @@ # destined to be used in a LowLevelCallable -cdef double _geninvgauss_pdf(double x, void *user_data) except * nogil + +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) except * nogil -cdef double _genhyperbolic_logpdf(double x, void *user_data) except * 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/scipy/stats/_stats.pyx b/scipy/stats/_stats.pyx index 98b2ff5a8cb1..2274d114eb02 100644 --- a/scipy/stats/_stats.pyx +++ b/scipy/stats/_stats.pyx @@ -509,7 +509,7 @@ cdef double _geninvgauss_logpdf_kernel(double x, double p, double b) noexcept no return c + (p - 1)*math.log(x) - b*(x + 1/x)/2 -cdef double _geninvgauss_pdf(double x, void *user_data) except * nogil: +cdef double _geninvgauss_pdf(double x, void *user_data) noexcept nogil: # destined to be used in a LowLevelCallable cdef double p, b @@ -645,7 +645,7 @@ cpdef double genhyperbolic_pdf(double x, double p, double a, double b) noexcept return math.exp(_genhyperbolic_logpdf_kernel(x, p, a, b)) -cdef double _genhyperbolic_pdf(double x, void *user_data) except * nogil: +cdef double _genhyperbolic_pdf(double x, void *user_data) noexcept nogil: # destined to be used in a LowLevelCallable cdef double p, a, b @@ -662,7 +662,8 @@ cpdef double genhyperbolic_logpdf( return _genhyperbolic_logpdf_kernel(x, p, a, b) -cdef double _genhyperbolic_logpdf(double x, void *user_data) except * nogil: +# logpdf is always negative, so use positive exception value +cdef double _genhyperbolic_logpdf(double x, void *user_data) noexcept nogil: # destined to be used in a LowLevelCallable cdef double p, a, b From 035978d2051157ca5bac41f64c6e65acc0705851 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Sun, 26 May 2024 13:05:26 -0400 Subject: [PATCH 264/500] update grayscale morphology docstrings to make the behavior of structure clearer --- scipy/ndimage/_morphology.py | 50 ++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/scipy/ndimage/_morphology.py b/scipy/ndimage/_morphology.py index fabf0926e013..22ada0b130f9 100644 --- a/scipy/ndimage/_morphology.py +++ b/scipy/ndimage/_morphology.py @@ -1142,7 +1142,8 @@ def grey_erosion(input, size=None, footprint=None, structure=None, neighbors of the center over which the minimum is chosen. structure : array of ints, optional Structuring element used for the grayscale erosion. `structure` - may be a non-flat structuring element. + may be a non-flat structuring element. The `structure` array applies a + subtractive offset for each pixel in the neighborhood. output : array, optional An array used for storing the output of the erosion may be provided. mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional @@ -1253,7 +1254,8 @@ def grey_dilation(input, size=None, footprint=None, structure=None, neighbors of the center over which the maximum is chosen. structure : array of ints, optional Structuring element used for the grayscale dilation. `structure` - may be a non-flat structuring element. + may be a non-flat structuring element. The `structure` array applies an + additive offset for each pixel in the neighborhood. output : array, optional An array used for storing the output of the dilation may be provided. mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional @@ -1399,7 +1401,9 @@ def grey_opening(input, size=None, footprint=None, structure=None, used for the grayscale opening. structure : array of ints, optional Structuring element used for the grayscale opening. `structure` - may be a non-flat structuring element. + may be a non-flat structuring element. The `structure` array applies + offsets to the pixels in a neighborhood (the offset is additive during + dilation and subtractive during erosion). output : array, optional An array used for storing the output of the opening may be provided. mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional @@ -1484,7 +1488,9 @@ def grey_closing(input, size=None, footprint=None, structure=None, used for the grayscale closing. structure : array of ints, optional Structuring element used for the grayscale closing. `structure` - may be a non-flat structuring element. + may be a non-flat structuring element. The `structure` array applies + offsets to the pixels in a neighborhood (the offset is additive during + dilation and subtractive during erosion) output : array, optional An array used for storing the output of the closing may be provided. mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional @@ -1570,8 +1576,10 @@ def morphological_gradient(input, size=None, footprint=None, structure=None, used for the morphology operations. Larger footprints give a more blurred morphological gradient. structure : array of ints, optional - Structuring element used for the morphology operations. - `structure` may be a non-flat structuring element. + Structuring element used for the morphology operations. `structure` may + be a non-flat structuring element. The `structure` array applies + offsets to the pixels in a neighborhood (the offset is additive during + dilation and subtractive during erosion) output : array, optional An array used for storing the output of the morphological gradient may be provided. @@ -1673,12 +1681,18 @@ def morphological_laplace(input, size=None, footprint=None, ---------- input : array_like Input. - size : int or sequence of ints, optional - See `structure`. - footprint : bool or ndarray, optional - See `structure`. - structure : structure, optional - Either `size`, `footprint`, or the `structure` must be provided. + size : tuple of ints + Shape of a flat and full structuring element used for the mathematical + morphology operations. Optional if `footprint` or `structure` is + provided. + footprint : array of ints, optional + Positions of non-infinite elements of a flat structuring element + used for the morphology operations. + structure : array of ints, optional + Structuring element used for the morphology operations. `structure` may + be a non-flat structuring element. The `structure` array applies + offsets to the pixels in a neighborhood (the offset is additive during + dilation and subtractive during erosion) output : ndarray, optional An output array can optionally be provided. mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional @@ -1730,8 +1744,10 @@ def white_tophat(input, size=None, footprint=None, structure=None, Positions of elements of a flat structuring element used for the white tophat filter. structure : array of ints, optional - Structuring element used for the filter. `structure` - may be a non-flat structuring element. + Structuring element used for the filter. `structure` may be a non-flat + structuring element. The `structure` array applies offsets to the + pixels in a neighborhood (the offset is additive during dilation and + subtractive during erosion) output : array, optional An array used for storing the output of the filter may be provided. mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional @@ -1808,8 +1824,10 @@ def black_tophat(input, size=None, footprint=None, Positions of non-infinite elements of a flat structuring element used for the black tophat filter. structure : array of ints, optional - Structuring element used for the filter. `structure` - may be a non-flat structuring element. + Structuring element used for the filter. `structure` may be a non-flat + structuring element. The `structure` array applies offsets to the + pixels in a neighborhood (the offset is additive during dilation and + subtractive during erosion) output : array, optional An array used for storing the output of the filter may be provided. mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional From 49fd14b533bc848acc111fa165e5e026d1a19250 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 26 May 2024 13:10:51 -0700 Subject: [PATCH 265/500] ENH: optimize.elementwise: vectorized scalar optimization and rootfinding tools [docs only] --- scipy/optimize/__init__.py | 10 +- scipy/optimize/_elementwise.py | 662 +++++++++++++++++++++++++++++++++ scipy/optimize/elementwise.py | 38 ++ scipy/optimize/meson.build | 2 + 4 files changed, 711 insertions(+), 1 deletion(-) create mode 100644 scipy/optimize/_elementwise.py create mode 100644 scipy/optimize/elementwise.py diff --git a/scipy/optimize/__init__.py b/scipy/optimize/__init__.py index d41cb5033af1..029ad97ea150 100644 --- a/scipy/optimize/__init__.py +++ b/scipy/optimize/__init__.py @@ -116,7 +116,7 @@ shgo - Simplicial homology global optimizer. dual_annealing - Dual annealing stochastic optimizer. direct - DIRECT (Dividing Rectangles) optimizer. - + Least-squares and curve fitting =============================== @@ -240,6 +240,14 @@ optimize.root-excitingmixing optimize.root-krylov optimize.root-dfsane + +Elementwise Minimization and Rootfinding +======================================== + +.. toctree:: + :maxdepth: 4 + + optimize.elementwise Linear programming / MILP ========================= diff --git a/scipy/optimize/_elementwise.py b/scipy/optimize/_elementwise.py new file mode 100644 index 000000000000..105fe61da729 --- /dev/null +++ b/scipy/optimize/_elementwise.py @@ -0,0 +1,662 @@ +from scipy.optimize._bracket import _bracket_root, _bracket_minimum +from scipy.optimize._chandrupatla import _chandrupatla, _chandrupatla_minimize +from scipy._lib._util import _RichResult + + +def rootfind(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=None): + """Find the root of a monotonic, real-valued function of a real variable. + + For each element of the output of `f`, `rootfind` seeks the scalar + root that makes the element 0. This function currently uses Chandrupatla's + bracketing algorithm [1]_ and therefore requires argument ``init`` to + provide a bracket around the root: the function values at the two endpoints + must have opposite signs. + + Provided a valid bracket, `rootfind` is guaranteed to converge to a solution that + satisfies the provided `tolerances` under mild requirements (e.g. the function + is defined and the root exists within the bracket). + + This function works elementwise when `init` and `args` contain broadcastable arrays. + + Parameters + ---------- + f : callable + The function whose root is desired. The signature must be:: + + f(x: array, *args) -> array + + where each element of ``x`` is a finite real and ``args`` is a tuple, + which may contain an arbitrary number of arrays that are broadcastable + with `x`. + + ``f`` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. `rootfind` seeks an + array ``x`` such that ``f(x)`` is an array of zeros. + init : 2-tuple of real number arrays + The lower and upper endpoints of a bracket surrounding the desired root. + A bracket is valid if arrays ``xl, xr = init`` satisfy ``xl < xr`` and + ``sign(f(xl)) == -sign(f(xr))`` elementwise. Arrays be broadcastable with + one another and `args`. + args : tuple of real number arrays, optional + Additional positional array arguments to be passed to `f`. Arrays + must be broadcastable with one another and the arrays of `init`. + If the callable for which the root is desired requires arguments that are + not broadcastable with `x`, wrap that callable with `f` such that `f` + accepts only `x` and broadcastable ``*args``. + tolerances : dictionary of floats, optional + Absolute and relative tolerances on the root and function value. + Valid keys of the dictionary are: + + - ``xatol`` - absolute tolerance on the root + - ``xrtol`` - relative tolerance on the root + - ``fatol`` - absolute tolerance on the function value + - ``frtol`` - relative tolerance on the function value + + See Notes for default values and explicit termination conditions. + maxiter : int, optional + The maximum number of iterations of the algorithm to perform. + The default is the maximum possible number of bisections within + the (normal) floating point numbers of the relevant dtype. + callback : callable, optional + An optional user-supplied function to be called before the first + iteration and after each iteration. + Called as ``callback(res)``, where ``res`` is a ``_RichResult`` + similar to that returned by `rootfind` (but containing the current + iterate's values of all variables). If `callback` raises a + ``StopIteration``, the algorithm will terminate immediately and + `rootfind` will return a result. + + Returns + ------- + res : _RichResult + An instance of `scipy._lib._util._RichResult` 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. + - ``-1`` : The initial bracket was invalid. + - ``-2`` : The maximum number of iterations was reached. + - ``-3`` : A non-finite value was encountered. + - ``-4`` : Iteration was terminated by `callback`. + - ``1`` : The algorithm is proceeding normally (in `callback` only). + + x : float array + The root of the function, if the algorithm terminated successfully. + f_x : float array + The value of `f` evaluated at `x`. + nfev : int array + The number of abscissae at which `f` was evaluated to find the root. + This is distinct from the number of times `f` is *called* because the + the function may evaluated at multiple points in a single call. + nit : int array + The number of iterations of the algorithm that were performed. + bracket : tuple of float arrays + The lower and upper endpoints of the final bracket. + f_bracket : tuple of float arrays + The value of `f` evaluated at the lower and upper endpoints of the + bracket. + + Notes + ----- + Implemented based on Chandrupatla's original paper [1]_. + + Let: + + - ``a, b = init`` be the left and right endpoints of the initial bracket, + - ``xl`` and ``xr`` be the left and right endpoints of the final bracket, + - ``xmin = xl if abs(f(xl)) <= abs(f(xr)) else xr`` be the final bracket + endpoint with the smaller function value, and + - ``fmin0 = min(f(a), f(b))`` be the minimum of the two values of the + function evaluated at the initial bracket endpoints. + + Then the algorithm is considered to have converged when + + - ``abs(xr - xl) < xatol + abs(xmin) * xrtol`` or + - ``fun(xmin) <= fatol + abs(fmin0) * frtol``. + + This is equivalent to the termination condition described in [1]_ with + + - ``xrtol = 4e-10``, + - ``xatol = 1e-5``, and + - ``fatol = frtol = 0``. + + However, the default values of the `tolerances` dictionary are + + - ``xatol = 4*tiny``, + - ``xrtol = 4*eps``, + - ``frtol = 0``, and + - ``fatol = tiny``, + + where ``eps`` and ``tiny`` are the precision and smallest normal number + of the result ``dtype`` of function inputs and outputs. + + References + ---------- + + .. [1] 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. + https://doi.org/10.1016/s0965-9978(96)00051-8 + + See Also + -------- + bracket_root + + Examples + -------- + >>> from scipy.optimize import elementwise + >>> def f(x, c=5): + ... return x**3 - 2*x - c + >>> res_bracket = elementwise.bracket_root(f, 0) + >>> res_bracket.bracket + (2.0, 4.0) + >>> res_root = elementwise.rootfind(f, res_bracket.bracket) + >>> res_root.x + 2.0945514815423265 + + >>> c = [3, 4, 5] + >>> res_bracket = elementwise.bracket_root(f, 0, args=(c,)) + >>> res_bracket.bracket + (array([1., 1., 2.]), array([2., 2., 4.])) + >>> res_root = elementwise.rootfind(f, res_bracket.bracket, args=(c,)) + >>> res_root.x + array([1.8932892 , 2. , 2.09455148]) + + """ + + def reformat_result(res_in): + res_out = _RichResult() + res_out.status = res_in.status + res_out.success = res_in.success + res_out.x = res_in.x + res_out.f_x = res_in.fun + res_out.nfev = res_in.nfev + res_out.nit = res_in.nit + res_out.bracket = (res_in.xl, res_in.xr) + res_out.f_bracket = (res_in.fl, res_in.fl) + res_out._order_keys = ['success', 'status', 'x', 'f_x', + 'nfev', 'nit', 'bracket', 'f_bracket'] + return res_out + + xl, xr = init + default_tolerances = dict(xatol=None, xrtol=None, fatol=None, frtol=0) + tolerances = {} if tolerances is None else tolerances + tolerances.update(default_tolerances) + + if callable(callback): + def _callback(res): + return callback(reformat_result(res)) + else: + _callback = callback + + res = _chandrupatla(f, xl, xr, args=args, **tolerances, + maxiter=maxiter, callback=_callback) + return reformat_result(res) + + +def minimize(f, init, /, *, args=(), tolerances=None, maxiter=100, callback=None): + """Find the minimizer of an unimodal, real-valued function of a real variable. + + For each element of the output of `f`, `minimize` seeks the scalar minimizer + that minimizes the element. This function currently uses Chandrupatla's + bracketing minimization algorithm [1]_ and therefore requires argument ``init`` + to provide a three-point minimization bracket: + + Provided a valid bracket, `minimize` is guaranteed to converge to a local + minimizer that satisfies the provided `tolerances` under mild requirements + (e.g. the function is defined and minimum exists within the bracket). + + This function works elementwise when `init` and `args` contain broadcastable arrays. + + Parameters + ---------- + f : callable + The function whose minimizer is desired. The signature must be:: + + f(x: array, *args) -> array + + where each element of ``x`` is a finite real and ``args`` is a tuple, + which may contain an arbitrary number of arrays that are broadcastable + with `x`. + + ``f`` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. `minimize` seeks an + array ``x`` such that ``f(x)`` is an array of local minima. + init : 3-tuple of real number arrays + The abscissae of a standard scalar minimization bracket. A bracket is + valid if arrays ``x1, x2, x3 = init`` satisfy ``x1 < x2 < x3`` and + ``func(x1) > func(x2) <= func(x3)``. Arrays must be broadcastable with + one another and the arrays of `args`. + args : tuple of real number arrays, optional + Additional positional array arguments to be passed to `f`. Arrays + must be broadcastable with one another and the arrays of `init`. + If the callable for which the root is desired requires arguments that are + not broadcastable with `x`, wrap that callable with `f` such that `f` + accepts only `x` and broadcastable ``*args``. + tolerances : dictionary of floats, optional + Absolute and relative tolerances on the root and function value. + Valid keys of the dictionary are: + + - ``xatol`` - absolute tolerance on the root + - ``xrtol`` - relative tolerance on the root + - ``fatol`` - absolute tolerance on the function value + - ``frtol`` - relative tolerance on the function value + + See Notes for default values and explicit termination conditions. + maxiter : int, default: 100 + The maximum number of iterations of the algorithm to perform. + callback : callable, optional + An optional user-supplied function to be called before the first + iteration and after each iteration. + Called as ``callback(res)``, where ``res`` is a ``_RichResult`` + similar to that returned by `minimize` (but containing the current + iterate's values of all variables). If `callback` raises a + ``StopIteration``, the algorithm will terminate immediately and + `rootfind` will return a result. + + Returns + ------- + res : _RichResult + An instance of `scipy._lib._util._RichResult` 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. + - ``-1`` : The algorithm encountered an invalid bracket. + - ``-2`` : The maximum number of iterations was reached. + - ``-3`` : A non-finite value was encountered. + - ``-4`` : Iteration was terminated by `callback`. + - ``1`` : The algorithm is proceeding normally (in `callback` only). + + x : float array + The minimizer of the function, if the algorithm terminated successfully. + f_x : float array + The value of `f` evaluated at `x`. + nfev : int array + The number of abscissae at which `f` was evaluated to find the root. + This is distinct from the number of times `f` is *called* because the + the function may evaluated at multiple points in a single call. + nit : int array + The number of iterations of the algorithm that were performed. + bracket : tuple of float arrays + The final three-point bracket. + f_bracket : tuple of float arrays + The value of `f` evaluated at the bracket points. + + Notes + ----- + Implemented based on Chandrupatla's original paper [1]_. + + If ``xl < xm < xr`` are the points of the bracket and ``fl > fm <= fr`` + are the values of ``f`` evaluated at those points, then the algorithm is + considered to have converged when: + + - ``xr - xl <= abs(xm)*xrtol + xatol`` or + - ``(fl - 2*fm + fr)/2 <= abs(fm)*frtol + fatol``. + + Note that first of these differs from the termination conditions described + in [1]_. + + The default values of `xrtol` is the square root of the precision of the + appropriate dtype, and ``xatol = fatol = frtol`` is the smallest normal + number of the appropriate dtype. + + References + ---------- + + .. [1] Chandrupatla, Tirupathi R. (1998). + "An efficient quadratic fit-sectioning algorithm for minimization + without derivatives". + Computer Methods in Applied Mechanics and Engineering, 152 (1-2), + 211-217. https://doi.org/10.1016/S0045-7825(97)00190-4 + + See Also + -------- + bracket_minimum + + Examples + -------- + >>> from scipy.optimize import elementwise + >>> def f(x, c=1): + ... return (x - c)**2 + >>> res_bracket = elementwise.bracket_minimum(f, 0) + >>> res_bracket.bracket + (0.0, 0.5, 1.5) + >>> res_minimize = elementwise.minimize(f, res_bracket.bracket) + >>> res_minimize.x + 1.0 + + >>> c = [1, 1.5, 2] + >>> res_bracket = elementwise.bracket_minimum(f, 0, args=(c,)) + >>> res_bracket.bracket + (array([0. , 0.5, 0.5]), array([0.5, 1.5, 1.5]), array([1.5, 2.5, 2.5])) + >>> res_minimize = elementwise.minimize(f, res_bracket.bracket, args=(c,)) + >>> res_minimize.x + array([1. , 1.5, 2. ]) + """ + + def reformat_result(res_in): + res_out = _RichResult() + res_out.status = res_in.status + res_out.success = res_in.success + res_out.x = res_in.x + res_out.f_x = res_in.fun + res_out.nfev = res_in.nfev + res_out.nit = res_in.nit + res_out.bracket = (res_in.xl, res_in.xm, res_in.xr) + res_out.f_bracket = (res_in.fl, res_in.fm, res_in.fr) + res_out._order_keys = ['success', 'status', 'x', 'f_x', + 'nfev', 'nit', 'bracket', 'f_bracket'] + return res_out + + xl, xm, xr = init + default_tolerances = dict(xatol=None, xrtol=None, fatol=None, frtol=None) + tolerances = {} if tolerances is None else tolerances + tolerances.update(default_tolerances) + + if callable(callback): + def _callback(res): + return callback(reformat_result(res)) + else: + _callback = callback + + res = _chandrupatla_minimize(f, xl, xm, xr, args=args, **tolerances, + maxiter=maxiter, callback=_callback) + return reformat_result(res) + + +def bracket_root(f, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, args=(), + maxiter=1000): + """Bracket the root of a monotonic, real-valued function of a real variable. + + For each element of the output of `f`, `bracket_root` seeks the scalar + bracket endpoints ``xl`` and ``xr`` such that ``sign(f(xl)) == -sign(f(xr))`` + elementwise. + + This function works elementwise when `xl0`, `xr0`, `xmin`, `xmax`, `factor`, and + the elements of `args` are mutually broadcastable arrays. + + Parameters + ---------- + f : callable + The function for which the root is to be bracketed. The signature must be:: + + f(x: array, *args) -> array + + where each element of ``x`` is a finite real and ``args`` is a tuple, + which may contain an arbitrary number of arrays that are broadcastable + with `x`. + + ``f`` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. + xl0, xr0: float array + Starting guess of bracket, which need not contain a root. If `xr0` is + not provided, ``xr0 = xl0 + 1``. Must be broadcastable with all other + array inputs. + xmin, xmax : float array, optional + Minimum and maximum allowable endpoints of the bracket, inclusive. Must + be broadcastable with all other array inputs. + factor : float array, default: 2 + The factor used to grow the bracket. See Notes. + args : tuple of arrays, optional + Additional positional array arguments to be passed to `f`. + If the callable for which the root is desired requires arguments that are + not broadcastable with `x`, wrap that callable with `f` such that `f` + accepts only `x` and broadcastable ``*args``. + maxiter : int, default: 1000 + The maximum number of iterations of the algorithm to perform. + + Returns + ------- + res : _RichResult + An instance of `scipy._lib._util._RichResult` 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 produced a valid bracket. + - ``-1`` : The bracket expanded to the allowable limits without success. + - ``-2`` : The maximum number of iterations was reached. + - ``-3`` : A non-finite value was encountered. + - ``-4`` : Iteration was terminated by `callback`. + - ``-5``: The initial bracket does not satisfy`xmin <= xl0 < xr0 < xmax`. + + bracket : 2-tuple of float arrays + The lower and upper endpoints of the bracket, if the algorithm + terminated successfully. + f_bracket : 2-tuple of float arrays + The values of `f` evaluated at the endpoints of ``res.bracket``, + respectively. + nfev : int array + The number of abscissae at which `f` was evaluated to find the root. + This is distinct from the number of times `f` is *called* because the + the function may evaluated at multiple points in a single call. + nit : int array + The number of iterations of the algorithm that were performed. + + Notes + ----- + This function generalizes an algorithm found in pieces throughout the + `scipy.stats` codebase. The strategy is to iteratively grow the bracket `(l, r)` + until ``f(l) < 0 < f(r)`` or ``f(r) < 0 < f(l)``. The bracket grows to the left + as follows. + + - If `xmin` is not provided, the distance between `xl0` and `l` is iteratively + increased by `factor`. + - If `xmin` is provided, the distance between `xmin` and `l` is iteratively + decreased by `factor`. Note that this also *increases* the bracket size. + + Growth of the bracket to the right is analogous. + + Growth of the bracket in one direction stops when the endpoint is no longer + finite, the function value at the endpoint is no longer finite, or the + endpoint reaches its limiting value (`xmin` or `xmax`). Iteration terminates + when the bracket stops growing in both directions, the bracket surrounds + the root, or a root is found (by chance). + + If two brackets are found - that is, a bracket is found on both sides in + the same iteration, the smaller of the two is returned. + + If roots of the function are found, both `xl` and `xr` are set to the + leftmost root. + + See Also + -------- + rootfind + + Examples + -------- + >>> from scipy.optimize import elementwise + >>> def f(x, c=5): + ... return x**3 - 2*x - c + >>> res_bracket = elementwise.bracket_root(f, 0) + >>> res_bracket.bracket + (2.0, 4.0) + >>> res_root = elementwise.rootfind(f, res_bracket.bracket) + >>> res_root.x + 2.0945514815423265 + + >>> c = [3, 4, 5] + >>> res_bracket = elementwise.bracket_root(f, 0, args=(c,)) + >>> res_bracket.bracket + (array([1., 1., 2.]), array([2., 2., 4.])) + >>> res_root = elementwise.rootfind(f, res_bracket.bracket, args=(c,)) + >>> res_root.x + array([1.8932892 , 2. , 2.09455148]) + + """ # noqa: E501 + + res = _bracket_root(f, xl0, xr0=xr0, xmin=xmin, xmax=xmax, factor=factor, + args=args, maxiter=maxiter) + res.bracket = res.xl, res.xr + res.f_bracket = res.fl, res.fr + del res.xl + del res.xr + del res.fl + del res.fr + return res + + +def bracket_minimum(f, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, + factor=None, args=(), maxiter=1000): + """Bracket the minimum of a unimodal, real-valued function of a real variable. + + For each element of the output of `f`, `bracket_minimum` seeks the scalar + bracket points ``xl < xm < xr`` such that ``fl > fm <= fr``. + + This function works elementwise when `xm0`, `xl0`, `xr0`, `xmin`, `xmax`, `factor`, + and the elements of `args` are mutually broadcastable arrays. + + Parameters + ---------- + f : callable + The function for which the root is to be bracketed. The signature must be:: + + f(x: array, *args) -> array + + where each element of ``x`` is a finite real and ``args`` is a tuple, + which may contain an arbitrary number of arrays that are broadcastable + with `x`. + + ``f`` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. + xm0: float array_like + Starting guess for middle point of bracket. + xl0, xr0: float array_like, optional + Starting guesses for left and right endpoints of the bracket. Must + be broadcastable with all other array inputs. + xmin, xmax : float array_like, optional + Minimum and maximum allowable endpoints of the bracket, inclusive. Must + be broadcastable with all other array inputs. + factor : float array, default: 2 + The factor used to grow the bracket. See Notes. + args : tuple of arrays, optional + Additional positional array arguments to be passed to `f`. + If the callable for which the root is desired requires arguments that are + not broadcastable with `x`, wrap that callable with `f` such that `f` + accepts only `x` and broadcastable ``*args``. + maxiter : int, default: 1000 + The maximum number of iterations of the algorithm to perform. + + Returns + ------- + res : _RichResult + An instance of `scipy._lib._util._RichResult` 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 produced a valid bracket. + - ``-1`` : The bracket expanded to the allowable limits. Assuming + unimodality, this implies the endpoint at the limit is a minimizer. + - ``-2`` : The maximum number of iterations was reached. + - ``-3`` : A non-finite value was encountered. + - ``-4`` : ``None`` shall pass. + - ``-5`` : The initial bracket does not satisfy + `xmin <= xl0 < xm0 < xr0 <= xmax`. + + bracket : 3-tuple of float arrays + The left, middle, and right points of the bracket, if the algorithm + terminated successfully. + f_bracket : 3-tuple of float arrays + The function value at the left, middle, and right points of the bracket. + nfev : int array + The number of abscissae at which `f` was evaluated to find the root. + This is distinct from the number of times `f` is *called* because the + the function may evaluated at multiple points in a single call. + nit : int array + The number of iterations of the algorithm that were performed. + + Notes + ----- + Similar to `scipy.optimize.bracket`, this function seeks to find real + points ``xl < xm < xr`` such that ``f(xl) >= f(xm)`` and ``f(xr) >= f(xm)``, + where at least one of the inequalities is strict. Unlike `scipy.optimize.bracket`, + this function can operate in a vectorized manner on array input, so long as + the input arrays are broadcastable with each other. Also unlike + `scipy.optimize.bracket`, users may specify minimum and maximum endpoints + for the desired bracket. + + Given an initial trio of points ``xl = xl0``, ``xm = xm0``, ``xr = xr0``, + the algorithm checks if these points already give a valid bracket. If not, + a new endpoint, ``w`` is chosen in the "downhill" direction, ``xm`` becomes the new + opposite endpoint, and either `xl` or `xr` becomes the new middle point, + depending on which direction is downhill. The algorithm repeats from here. + + The new endpoint `w` is chosen differently depending on whether or not a + boundary `xmin` or `xmax` has been set in the downhill direction. Without + loss of generality, suppose the downhill direction is to the right, so that + ``f(xl) > f(xm) > f(xr)``. If there is no boundary to the right, then `w` + is chosen to be ``xr + factor * (xr - xm)`` where `factor` is controlled by + the user (defaults to 2.0) so that step sizes increase in geometric proportion. + If there is a boundary, `xmax` in this case, then `w` is chosen to be + ``xmax - (xmax - xr)/factor``, with steps slowing to a stop at + `xmax`. This cautious approach ensures that a minimum near but distinct from + the boundary isn't missed while also detecting whether or not the `xmax` is + a minimizer when `xmax` is reached after a finite number of steps. + + See Also + -------- + scipy.optimize.bracket + scipy.optimize.elementwise.minimize + + Examples + -------- + >>> from scipy.optimize import elementwise + >>> def f(x, c=1): + ... return (x - c)**2 + >>> res_bracket = elementwise.bracket_minimum(f, 0) + >>> res_bracket.bracket + (0.0, 0.5, 1.5) + >>> res_minimize = elementwise.minimize(f, res_bracket.bracket) + >>> res_minimize.x + 1.0 + + >>> c = [1, 1.5, 2] + >>> res_bracket = elementwise.bracket_minimum(f, 0, args=(c,)) + >>> res_bracket.bracket + (array([0. , 0.5, 0.5]), array([0.5, 1.5, 1.5]), array([1.5, 2.5, 2.5])) + >>> res_minimize = elementwise.minimize(f, res_bracket.bracket, args=(c,)) + >>> res_minimize.x + array([1. , 1.5, 2. ]) + + """ # noqa: E501 + + res = _bracket_minimum(f, xm0, xl0=xl0, xr0=xr0, xmin=xmin, xmax=xmax, + factor=factor, args=args, maxiter=maxiter) + res.bracket = res.xl, res.xm, res.xr + res.f_bracket = res.fl, res.fm, res.fr + del res.xl + del res.xm + del res.xr + del res.fl + del res.fm + del res.fr + return res diff --git a/scipy/optimize/elementwise.py b/scipy/optimize/elementwise.py new file mode 100644 index 000000000000..4c8399f273be --- /dev/null +++ b/scipy/optimize/elementwise.py @@ -0,0 +1,38 @@ +""" +=================================================================== +Elementwise Scalar Optimization (:mod:`scipy.optimize.elementwise`) +=================================================================== + +.. currentmodule:: scipy.optimize.elementwise + +This module provides a collection of functions for rootfinding and +minimization of scalar, real-valued functions of one variable. Unlike their +counterparts in the base :mod:`scipy.optimize` namespace, these functions work +elementwise, enabling the solution of many related problems in an efficient, +vectorized call. Furthermore, when environment variable ``SCIPY_ARRAY_API=1``, +these functions can accept non-NumPy, array API standard compatible arrays and +perform all calculations using the corresponding array library (e.g. PyTorch, +JAX, CuPy). + +Rootfinding +=========== + +.. autosummary:: + :toctree: generated/ + + rootfind + bracket_root + +Rootfinding +=========== + +.. autosummary:: + :toctree: generated/ + + minimize + bracket_minimum + +""" +from ._elementwise import rootfind, bracket_root, minimize, bracket_minimum # noqa: F401, E501 + +__all__ = ["rootfind", "bracket_root", "minimize", "bracket_minimum"] diff --git a/scipy/optimize/meson.build b/scipy/optimize/meson.build index d6c20d3d537b..eba92d3d781c 100644 --- a/scipy/optimize/meson.build +++ b/scipy/optimize/meson.build @@ -230,6 +230,7 @@ py3.install_sources([ '_direct_py.py', '_dcsrch.py', '_dual_annealing.py', + '_elementwise.py', '_hessian_update_strategy.py', '_isotonic.py', '_lbfgsb_py.py', @@ -265,6 +266,7 @@ py3.install_sources([ '_zeros_py.py', 'cobyla.py', 'cython_optimize.pxd', + 'elementwise.py', 'lbfgsb.py', 'linesearch.py', 'minpack.py', From 00e8c70ae21013c7822a7b5b1fd7b74ff260373b Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 26 May 2024 13:28:35 -0700 Subject: [PATCH 266/500] MAINT: stats.wilcoxon: make method='exact' symmetric w/ ties (#20765) --- scipy/stats/_wilcoxon.py | 14 +++++++++++--- scipy/stats/tests/test_morestats.py | 13 +++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/scipy/stats/_wilcoxon.py b/scipy/stats/_wilcoxon.py index ae85d6035245..c59be3100489 100644 --- a/scipy/stats/_wilcoxon.py +++ b/scipy/stats/_wilcoxon.py @@ -215,12 +215,20 @@ def _wilcoxon_nd(x, y=None, zero_method='wilcox', correction=True, p = _get_pvalue(z, _SimpleNormal(), alternative, xp=np) 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. if alternative == 'less': - p = dist.cdf(r_plus) + p = dist.cdf(np.ceil(r_plus)) elif alternative == 'greater': - p = dist.sf(r_plus) + p = dist.sf(np.floor(r_plus)) else: - p = 2 * np.minimum(dist.sf(r_plus), dist.cdf(r_plus)) + p = 2 * np.minimum(dist.sf(np.floor(r_plus)), + dist.cdf(np.ceil(r_plus))) p = np.clip(p, 0, 1) else: # `PermutationMethod` instance (already validated) p = stats.permutation_test( diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 4ba64039ac05..24e6428cd71b 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -1682,6 +1682,19 @@ def test_method_auto_nan_propagate_ND_length_gt_50_gh20591(self): assert hasattr(ref, 'zstatistic') assert not hasattr(res, 'zstatistic') + @pytest.mark.parametrize('method', ['exact', 'approx']) + def test_symmetry_gh19872_gh20752(self, method): + # 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 = [62, 66, 61, 68, 74, 62, 68, 62, 55, 59] + var2 = [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 = len(var1) * (len(var1) + 1) / 2 + assert int(res.statistic) != res.statistic + assert_allclose(max_statistic - res.statistic, ref.statistic, rtol=1e-15) + assert_allclose(res.pvalue, ref.pvalue, rtol=1e-15) + # data for k-statistics tests from # https://cran.r-project.org/web/packages/kStatistics/kStatistics.pdf From ffa08cdcc173db73727205a1414a785a4983f358 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 26 May 2024 17:07:39 -0700 Subject: [PATCH 267/500] MAINT: remove pytest-fail-slow from pyproject.toml --- pyproject.toml | 1 - requirements/test.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ab241ac2368e..5dacd58974ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,6 @@ test = [ "pytest", "pytest-cov", "pytest-timeout", - "pytest-fail-slow", "pytest-xdist", "asv", "mpmath", diff --git a/requirements/test.txt b/requirements/test.txt index e8cdf322e24e..211e83a8b472 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -3,7 +3,6 @@ pytest pytest-cov pytest-timeout -pytest-fail-slow pytest-xdist asv mpmath From b3ab50b580fc92ed3ff61c3ae7c6971680b3c1aa Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Mon, 27 May 2024 12:54:59 +1000 Subject: [PATCH 268/500] ENH: speed up for differential_evolution updating='deferred' (#20757) * ENH: speed up for differential_evolution updating='deferred' * MAINT: split into _mutate and _mutate_many * MAINT: amend comments * TST: custom strategy function with updating='deferred' * TST: an exponential crossover with updating='deferred' --- scipy/optimize/_differentialevolution.py | 116 +++++++++++++----- .../tests/test__differential_evolution.py | 31 +++-- 2 files changed, 111 insertions(+), 36 deletions(-) diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index 4d81df53971d..815d27afd072 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -7,7 +7,8 @@ import numpy as np from scipy.optimize import OptimizeResult, minimize from scipy.optimize._optimize import _status_message, _wrap_callback -from scipy._lib._util import check_random_state, MapWrapper, _FunctionWrapper +from scipy._lib._util import (check_random_state, MapWrapper, _FunctionWrapper, + rng_integers) from scipy.optimize._constraints import (Bounds, new_bounds_to_old, NonlinearConstraint, LinearConstraint) @@ -1606,8 +1607,9 @@ def __next__(self): # 'deferred' approach, vectorised form. # create trial solutions - trial_pop = np.array( - [self._mutate(i) for i in range(self.num_population_members)]) + trial_pop = self._mutate_many( + np.arange(self.num_population_members) + ) # enforce bounds self._ensure_constraint(trial_pop) @@ -1665,35 +1667,90 @@ def _ensure_constraint(self, trial): if oob := np.count_nonzero(mask): trial[mask] = self.random_number_generator.uniform(size=oob) + def _mutate_custom(self, candidate): + rng = self.random_number_generator + msg = ( + "strategy must have signature" + " f(candidate: int, population: np.ndarray, rng=None) returning an" + " array of shape (N,)" + ) + _population = self._scale_parameters(self.population) + if not len(np.shape(candidate)): + # single entry in population + trial = self.strategy(candidate, _population, rng=rng) + if trial.shape != (self.parameter_count,): + raise RuntimeError(msg) + else: + S = candidate.shape[0] + trial = np.array( + [self.strategy(c, _population, rng=rng) for c in candidate], + dtype=float + ) + if trial.shape != (S, self.parameter_count): + raise RuntimeError(msg) + return self._unscale_parameters(trial) + + def _mutate_many(self, candidates): + """Create trial vectors based on a mutation strategy.""" + rng = self.random_number_generator + + S = len(candidates) + if callable(self.strategy): + return self._mutate_custom(candidates) + + trial = np.copy(self.population[candidates]) + samples = np.array([self._select_samples(c, 5) for c in candidates]) + + if self.strategy in ['currenttobest1exp', 'currenttobest1bin']: + bprime = self.mutation_func(candidates, samples) + else: + bprime = self.mutation_func(samples) + + fill_point = rng_integers(rng, self.parameter_count, size=S) + crossovers = rng.uniform(size=(S, self.parameter_count)) + crossovers = crossovers < self.cross_over_probability + if self.strategy in self._binomial: + # the last one is always from the bprime vector for binomial + # If you fill in modulo with a loop you have to set the last one to + # true. If you don't use a loop then you can have any random entry + # be True. + i = np.arange(S) + crossovers[i, fill_point[i]] = True + trial = np.where(crossovers, bprime, trial) + return trial + + elif self.strategy in self._exponential: + crossovers[..., 0] = True + for j in range(S): + i = 0 + init_fill = fill_point[j] + while (i < self.parameter_count and crossovers[j, i]): + trial[j, init_fill] = bprime[j, init_fill] + init_fill = (init_fill + 1) % self.parameter_count + i += 1 + + return trial + def _mutate(self, candidate): """Create a trial vector based on a mutation strategy.""" rng = self.random_number_generator if callable(self.strategy): - _population = self._scale_parameters(self.population) - trial = np.array( - self.strategy(candidate, _population, rng=rng), dtype=float - ) - if trial.shape != (self.parameter_count,): - raise RuntimeError( - "strategy must have signature" - " f(candidate: int, population: np.ndarray, rng=None)" - " returning an array of shape (N,)" - ) - return self._unscale_parameters(trial) + return self._mutate_custom(candidate) + + fill_point = rng_integers(rng, self.parameter_count) + samples = self._select_samples(candidate, 5) trial = np.copy(self.population[candidate]) - fill_point = rng.choice(self.parameter_count) if self.strategy in ['currenttobest1exp', 'currenttobest1bin']: - bprime = self.mutation_func(candidate, - self._select_samples(candidate, 5)) + bprime = self.mutation_func(candidate, samples) else: - bprime = self.mutation_func(self._select_samples(candidate, 5)) + bprime = self.mutation_func(samples) + crossovers = rng.uniform(size=self.parameter_count) + crossovers = crossovers < self.cross_over_probability if self.strategy in self._binomial: - crossovers = rng.uniform(size=self.parameter_count) - crossovers = crossovers < self.cross_over_probability # the last one is always from the bprime vector for binomial # If you fill in modulo with a loop you have to set the last one to # true. If you don't use a loop then you can have any random entry @@ -1704,10 +1761,8 @@ def _mutate(self, candidate): elif self.strategy in self._exponential: i = 0 - crossovers = rng.uniform(size=self.parameter_count) - crossovers = crossovers < self.cross_over_probability crossovers[0] = True - while (i < self.parameter_count and crossovers[i]): + while i < self.parameter_count and crossovers[i]: trial[fill_point] = bprime[fill_point] fill_point = (fill_point + 1) % self.parameter_count i += 1 @@ -1716,19 +1771,22 @@ def _mutate(self, candidate): def _best1(self, samples): """best1bin, best1exp""" - r0, r1 = samples[:2] + # samples.shape == (S, 5) + # or + # samples.shape(5,) + r0, r1 = samples[..., :2].T return (self.population[0] + self.scale * (self.population[r0] - self.population[r1])) def _rand1(self, samples): """rand1bin, rand1exp""" - r0, r1, r2 = samples[:3] + r0, r1, r2 = samples[..., :3].T return (self.population[r0] + self.scale * (self.population[r1] - self.population[r2])) def _randtobest1(self, samples): """randtobest1bin, randtobest1exp""" - r0, r1, r2 = samples[:3] + r0, r1, r2 = samples[..., :3].T bprime = np.copy(self.population[r0]) bprime += self.scale * (self.population[0] - bprime) bprime += self.scale * (self.population[r1] - @@ -1737,7 +1795,7 @@ def _randtobest1(self, samples): def _currenttobest1(self, candidate, samples): """currenttobest1bin, currenttobest1exp""" - r0, r1 = samples[:2] + r0, r1 = samples[..., :2].T bprime = (self.population[candidate] + self.scale * (self.population[0] - self.population[candidate] + self.population[r0] - self.population[r1])) @@ -1745,7 +1803,7 @@ def _currenttobest1(self, candidate, samples): def _best2(self, samples): """best2bin, best2exp""" - r0, r1, r2, r3 = samples[:4] + r0, r1, r2, r3 = samples[..., :4].T bprime = (self.population[0] + self.scale * (self.population[r0] + self.population[r1] - self.population[r2] - self.population[r3])) @@ -1754,7 +1812,7 @@ def _best2(self, samples): def _rand2(self, samples): """rand2bin, rand2exp""" - r0, r1, r2, r3, r4 = samples + r0, r1, r2, r3, r4 = samples[..., :5].T bprime = (self.population[r0] + self.scale * (self.population[r1] + self.population[r2] - self.population[r3] - self.population[r4])) diff --git a/scipy/optimize/tests/test__differential_evolution.py b/scipy/optimize/tests/test__differential_evolution.py index 3f0b8e53551c..b6b1ba39a242 100644 --- a/scipy/optimize/tests/test__differential_evolution.py +++ b/scipy/optimize/tests/test__differential_evolution.py @@ -126,11 +126,11 @@ def test__strategy_resolves(self): def test__mutate1(self): # strategies */1/*, i.e. rand/1/bin, best/1/exp, etc. result = np.array([0.05]) - trial = self.dummy_solver2._best1((2, 3, 4, 5, 6)) + trial = self.dummy_solver2._best1(np.array([2, 3, 4, 5, 6])) assert_allclose(trial, result) result = np.array([0.25]) - trial = self.dummy_solver2._rand1((2, 3, 4, 5, 6)) + trial = self.dummy_solver2._rand1(np.array([2, 3, 4, 5, 6])) assert_allclose(trial, result) def test__mutate2(self): @@ -138,23 +138,26 @@ def test__mutate2(self): # [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7] result = np.array([-0.1]) - trial = self.dummy_solver2._best2((2, 3, 4, 5, 6)) + trial = self.dummy_solver2._best2(np.array([2, 3, 4, 5, 6])) assert_allclose(trial, result) result = np.array([0.1]) - trial = self.dummy_solver2._rand2((2, 3, 4, 5, 6)) + trial = self.dummy_solver2._rand2(np.array([2, 3, 4, 5, 6])) assert_allclose(trial, result) def test__randtobest1(self): # strategies randtobest/1/* result = np.array([0.15]) - trial = self.dummy_solver2._randtobest1((2, 3, 4, 5, 6)) + trial = self.dummy_solver2._randtobest1(np.array([2, 3, 4, 5, 6])) assert_allclose(trial, result) def test__currenttobest1(self): # strategies currenttobest/1/* result = np.array([0.1]) - trial = self.dummy_solver2._currenttobest1(1, (2, 3, 4, 5, 6)) + trial = self.dummy_solver2._currenttobest1( + 1, + np.array([2, 3, 4, 5, 6]) + ) assert_allclose(trial, result) def test_can_init_with_dithering(self): @@ -691,7 +694,14 @@ def test_deferred_updating(self): solver = DifferentialEvolutionSolver(rosen, bounds, updating='deferred') assert_(solver._updating == 'deferred') assert_(solver._mapwrapper._mapfunc is map) - solver.solve() + res = solver.solve() + assert res.success + + # check that deferred updating works with an exponential crossover + res = differential_evolution( + rosen, bounds, updating='deferred', strategy='best1exp' + ) + assert res.success def test_immediate_updating(self): # check setting of immediate updating, with default workers @@ -1669,6 +1679,12 @@ def custom_strategy_fn(candidate, population, rng=None): solver.solve() assert calls[0] > 0 + # check custom strategy works with updating='deferred' + res = differential_evolution( + rosen, bounds, strategy=custom_strategy_fn, updating='deferred' + ) + assert res.success + def custom_strategy_fn(candidate, population, rng=None): return np.array([1.0, 2.0]) @@ -1678,3 +1694,4 @@ def custom_strategy_fn(candidate, population, rng=None): bounds, strategy=custom_strategy_fn ) + From 1ba35d1c4f66d82e0d395cf086e2212c06d8852d Mon Sep 17 00:00:00 2001 From: tpl2go Date: Mon, 27 May 2024 14:19:58 +0800 Subject: [PATCH 269/500] ENH: fft: Add `prev_fast_len` to complement `next_fast_len` (#15321) --- scipy/fft/__init__.py | 7 +- scipy/fft/_helper.py | 66 +++++++++++++++ scipy/fft/_pocketfft/helper.py | 4 +- scipy/fft/_pocketfft/pypocketfft.cxx | 49 +++++++++++ scipy/fft/tests/test_helper.py | 117 ++++++++++++++++++++++++++- 5 files changed, 238 insertions(+), 5 deletions(-) diff --git a/scipy/fft/__init__.py b/scipy/fft/__init__.py index 7d6c3c27febc..c545a00b9fd6 100644 --- a/scipy/fft/__init__.py +++ b/scipy/fft/__init__.py @@ -66,6 +66,7 @@ rfftfreq - DFT sample frequencies (for usage with rfft, irfft) fhtoffset - Compute an optimal offset for the Fast Hankel Transform next_fast_len - Find the optimal length to zero-pad an FFT for speed + prev_fast_len - Find the maximum slice length that results in a fast FFT set_workers - Context manager to set default number of workers get_workers - Get the current default number of workers @@ -88,7 +89,9 @@ hfft, ihfft, hfft2, ihfft2, hfftn, ihfftn) from ._realtransforms import dct, idct, dst, idst, dctn, idctn, dstn, idstn from ._fftlog import fht, ifht, fhtoffset -from ._helper import next_fast_len, fftfreq, rfftfreq, fftshift, ifftshift +from ._helper import ( + next_fast_len, prev_fast_len, fftfreq, + rfftfreq, fftshift, ifftshift) from ._backend import (set_backend, skip_backend, set_global_backend, register_backend) from ._pocketfft.helper import set_workers, get_workers @@ -98,7 +101,7 @@ 'rfft', 'irfft', 'rfft2', 'irfft2', 'rfftn', 'irfftn', 'hfft', 'ihfft', 'hfft2', 'ihfft2', 'hfftn', 'ihfftn', 'fftfreq', 'rfftfreq', 'fftshift', 'ifftshift', - 'next_fast_len', + 'next_fast_len', 'prev_fast_len', 'dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn', 'fht', 'ifht', 'fhtoffset', diff --git a/scipy/fft/_helper.py b/scipy/fft/_helper.py index aaa9a107be35..76e08c4f61c8 100644 --- a/scipy/fft/_helper.py +++ b/scipy/fft/_helper.py @@ -74,6 +74,72 @@ def next_fast_len(target, real=False): next_fast_len.__signature__ = _sig +def prev_fast_len(target, real=False): + """Find the previous fast size of input data to ``fft``. + Useful for discarding a minimal number of samples before FFT. + + SciPy's FFT algorithms gain their speed by a recursive divide and conquer + strategy. This relies on efficient functions for small prime factors of the + input length. Thus, the transforms are fastest when using composites of the + prime factors handled by the fft implementation. If there are efficient + functions for all radices <= `n`, then the result will be a number `x` + <= ``target`` with only prime factors <= `n`. (Also known as `n`-smooth + numbers) + + Parameters + ---------- + target : int + Maximum length to search until. Must be a positive integer. + real : bool, optional + True if the FFT involves real input or output (e.g., `rfft` or `hfft` + but not `fft`). Defaults to False. + + Returns + ------- + out : int + The largest fast length less than or equal to ``target``. + + Notes + ----- + The result of this function may change in future as performance + considerations change, for example, if new prime factors are added. + + Calling `fft` or `ifft` with real input data performs an ``'R2C'`` + transform internally. + + In the current implementation, prev_fast_len assumes radices of + 2,3,5,7,11 for complex FFT and 2,3,5 for real FFT. + + Examples + -------- + On a particular machine, an FFT of prime length takes 16.2 ms: + + >>> from scipy import fft + >>> import numpy as np + >>> rng = np.random.default_rng() + >>> max_len = 93059 # prime length is worst case for speed + >>> a = rng.standard_normal(max_len) + >>> b = fft.fft(a) + + Performing FFT on the maximum fast length less than max_len + reduces the computation time to 1.5 ms, a speedup of 10.5 times: + + >>> fft.prev_fast_len(max_len, real=True) + 92160 + >>> c = fft.fft(a[:92160]) # discard last 899 samples + + """ + pass + + +# Directly wrap the c-function prev_good_size but take the docstring etc., +# from the prev_fast_len function above +_sig_prev_fast_len = inspect.signature(prev_fast_len) +prev_fast_len = update_wrapper(lru_cache()(_helper.prev_good_size), prev_fast_len) +prev_fast_len.__wrapped__ = _helper.prev_good_size +prev_fast_len.__signature__ = _sig_prev_fast_len + + def _init_nd_shape_and_axes(x, shape, axes): """Handle shape and axes arguments for N-D transforms. diff --git a/scipy/fft/_pocketfft/helper.py b/scipy/fft/_pocketfft/helper.py index 47e54063fe8e..ab2fbc553ccc 100644 --- a/scipy/fft/_pocketfft/helper.py +++ b/scipy/fft/_pocketfft/helper.py @@ -9,10 +9,10 @@ from scipy._lib._util import copy_if_needed # good_size is exposed (and used) from this import -from .pypocketfft import good_size +from .pypocketfft import good_size, prev_good_size -__all__ = ['good_size', 'set_workers', 'get_workers'] +__all__ = ['good_size', 'prev_good_size', 'set_workers', 'get_workers'] _config = threading.local() _cpu_count = os.cpu_count() diff --git a/scipy/fft/_pocketfft/pypocketfft.cxx b/scipy/fft/_pocketfft/pypocketfft.cxx index 6acd01a7af7f..787411133568 100644 --- a/scipy/fft/_pocketfft/pypocketfft.cxx +++ b/scipy/fft/_pocketfft/pypocketfft.cxx @@ -405,6 +405,33 @@ PyObject * good_size(PyObject * /*self*/, PyObject * args, PyObject * kwargs) real ? util::good_size_real(n) : util::good_size_cmplx(n)); } +// Export prev_good_size in raw C-API to reduce overhead +PyObject * prev_good_size(PyObject * /*self*/, PyObject * args, PyObject * kwargs) + { + Py_ssize_t n_ = -1; + int real = false; + static const char * keywords[] = {"target", "real", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "n|p:prev_good_size", + (char **) keywords, &n_, &real)) + return nullptr; + + if (n_<0) + { + PyErr_SetString(PyExc_ValueError, "Target length must be positive"); + return nullptr; + } + if ((n_-1) > static_cast(std::numeric_limits::max() / 11)) + { + PyErr_Format(PyExc_ValueError, + "Target length is too large to perform an FFT: %zi", n_); + return nullptr; + } + const auto n = static_cast(n_); + using namespace pocketfft::detail; + return PyLong_FromSize_t( + real ? util::prev_good_size_real(n) : util::prev_good_size_cmplx(n)); + } + const char *pypocketfft_DS = R"""(Fast Fourier and Hartley transforms. This module supports @@ -710,6 +737,23 @@ out : int )"""; + +const char * prev_good_size_DS = R"""(Returns the largest FFT length less than target length. + +Parameters +---------- +target : int + Maximum transform length +real : bool, optional + True if either input or output of FFT should be fully real. + +Returns +------- +out : int + The largest fast length <= n + +)"""; + } // unnamed namespace PYBIND11_MODULE(pypocketfft, m) @@ -740,4 +784,9 @@ PYBIND11_MODULE(pypocketfft, m) {{"good_size", (PyCFunction)good_size, METH_VARARGS | METH_KEYWORDS, good_size_DS}, {0}}; PyModule_AddFunctions(m.ptr(), good_size_meth); + + static PyMethodDef prev_good_size_meth[] = + {{"prev_good_size", (PyCFunction)prev_good_size, + METH_VARARGS | METH_KEYWORDS, prev_good_size_DS}, {0}}; + PyModule_AddFunctions(m.ptr(), prev_good_size_meth); } diff --git a/scipy/fft/tests/test_helper.py b/scipy/fft/tests/test_helper.py index 88367a06123a..9a102ddec901 100644 --- a/scipy/fft/tests/test_helper.py +++ b/scipy/fft/tests/test_helper.py @@ -4,7 +4,7 @@ Modified for Array API, 2023 """ -from scipy.fft._helper import next_fast_len, _init_nd_shape_and_axes +from scipy.fft._helper import next_fast_len, prev_fast_len, _init_nd_shape_and_axes from numpy.testing import assert_equal from pytest import raises as assert_raises import pytest @@ -127,6 +127,121 @@ def test_keyword_args(self): assert next_fast_len(11, real=True) == 12 assert next_fast_len(target=7, real=False) == 7 +@skip_xp_backends(np_only=True) +class TestPrevFastLen: + + def test_prev_fast_len(self): + np.random.seed(1234) + + def nums(): + yield from range(1, 1000) + yield 2**5 * 3**5 * 4**5 + 1 + + for n in nums(): + m = prev_fast_len(n) + _assert_n_smooth(m, 11) + assert m == prev_fast_len(n, False) + + m = prev_fast_len(n, True) + _assert_n_smooth(m, 5) + + def test_np_integers(self): + ITYPES = [np.int16, np.int32, np.int64, np.uint16, np.uint32, + np.uint64] + for ityp in ITYPES: + x = ityp(12345) + testN = prev_fast_len(x) + assert_equal(testN, prev_fast_len(int(x))) + + testN = prev_fast_len(x, real=True) + assert_equal(testN, prev_fast_len(int(x), real=True)) + + def testprev_fast_len_small(self): + hams = { + 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 6, 8: 8, 14: 12, 15: 15, + 16: 16, 17: 16, 1021: 1000, 1536: 1536, 51200000: 51200000 + } + for x, y in hams.items(): + assert_equal(prev_fast_len(x, True), y) + + hams = { + 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, + 11: 11, 12: 12, 13: 12, 14: 14, 15: 15, 16: 16, 17: 16, 18: 18, + 19: 18, 20: 20, 21: 21, 22: 22, 120: 120, 121: 121, 122: 121, + 1021: 1008, 1536: 1536, 51200000: 51200000 + } + for x, y in hams.items(): + assert_equal(prev_fast_len(x, False), y) + + @pytest.mark.xfail(sys.maxsize < 2**32, + reason="Hamming Numbers too large for 32-bit", + raises=ValueError, strict=True) + def testprev_fast_len_big(self): + hams = { + # 2**6 * 3**13 * 5**1 + 510183360: 510183360, + 510183360 + 1: 510183360, + 510183360 - 1: 509607936, # 2**21 * 3**5 + # 2**6 * 5**6 * 7**1 * 73**1 + 511000000: 510183360, + 511000000 + 1: 510183360, + 511000000 - 1: 510183360, # 2**6 * 3**13 * 5**1 + # 3**7 * 5**8 + 854296875: 854296875, + 854296875 + 1: 854296875, + 854296875 - 1: 850305600, # 2**6 * 3**12 * 5**2 + # 2**22 * 3**1 * 5**6 + 196608000000: 196608000000, + 196608000000 + 1: 196608000000, + 196608000000 - 1: 195910410240, # 2**13 * 3**14 * 5**1 + # 2**5 * 3**2 * 5**15 + 8789062500000: 8789062500000, + 8789062500000 + 1: 8789062500000, + 8789062500000 - 1: 8748000000000, # 2**11 * 3**7 * 5**9 + # 2**24 * 3**9 * 5**4 + 206391214080000: 206391214080000, + 206391214080000 + 1: 206391214080000, + 206391214080000 - 1: 206158430208000, # 2**39 * 3**1 * 5**3 + # 2**18 * 3**15 * 5**3 + 470184984576000: 470184984576000, + 470184984576000 + 1: 470184984576000, + 470184984576000 - 1: 469654673817600, # 2**33 * 3**7 **5**2 + # 2**25 * 3**16 * 5**1 + 7222041363087360: 7222041363087360, + 7222041363087360 + 1: 7222041363087360, + 7222041363087360 - 1: 7213895789838336, # 2**40 * 3**8 + # power of 5 5**23 + 11920928955078125: 11920928955078125, + 11920928955078125 + 1: 11920928955078125, + 11920928955078125 - 1: 11901557422080000, # 2**14 * 3**19 * 5**4 + # power of 3 3**34 + 16677181699666569: 16677181699666569, + 16677181699666569 + 1: 16677181699666569, + 16677181699666569 - 1: 16607531250000000, # 2**7 * 3**12 * 5**12 + # power of 2 2**54 + 18014398509481984: 18014398509481984, + 18014398509481984 + 1: 18014398509481984, + 18014398509481984 - 1: 18000000000000000, # 2**16 * 3**2 * 5**15 + # 2**20 * 3**1 * 5**14 + 19200000000000000: 19200000000000000, + 19200000000000000 + 1: 19200000000000000, + 19200000000000000 - 1: 19131876000000000, # 2**11 * 3**14 * 5**9 + # 2**58 + 288230376151711744: 288230376151711744, + 288230376151711744 + 1: 288230376151711744, + 288230376151711744 - 1: 288000000000000000, # 2**20 * 3**2 * 5**15 + # 2**5 * 3**10 * 5**16 + 288325195312500000: 288325195312500000, + 288325195312500000 + 1: 288325195312500000, + 288325195312500000 - 1: 288230376151711744, # 2**58 + } + for x, y in hams.items(): + assert_equal(prev_fast_len(x, True), y) + + def test_keyword_args(self): + assert prev_fast_len(11, real=True) == 10 + assert prev_fast_len(target=7, real=False) == 7 + @skip_xp_backends(cpu_only=True) class Test_init_nd_shape_and_axes: From dc9cbc03c0393fcd50b2f496216ad871490a5cf0 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 27 May 2024 07:57:42 -0700 Subject: [PATCH 270/500] TST: stats.nbinom: adjust cdf-ppf roundtrip test (#20807) --- scipy/stats/tests/test_discrete_basic.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scipy/stats/tests/test_discrete_basic.py b/scipy/stats/tests/test_discrete_basic.py index 012e68a8da2d..1ebc9371c075 100644 --- a/scipy/stats/tests/test_discrete_basic.py +++ b/scipy/stats/tests/test_discrete_basic.py @@ -22,6 +22,8 @@ # For these distributions, test_discrete_basic only runs with test mode full distslow = {'zipfian', 'nhypergeom'} +# Override number of ULPs adjustment for `check_cdf_ppf` +roundtrip_cdf_ppf_exceptions = {'nbinom': 30} def cases_test_discrete_basic(): seen = set() @@ -193,8 +195,9 @@ def check_cdf_ppf(distfn, arg, supp, msg): 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 15 ULPs help to avoid this. - cdf_supp0 = cdf_supp - 15*np.spacing(cdf_supp) + # 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. From 64601941a19ab8d765d8f64e33ec95d58692f457 Mon Sep 17 00:00:00 2001 From: Warren Weckesser Date: Mon, 27 May 2024 12:03:35 -0400 Subject: [PATCH 271/500] BUG: special: Restore missing line of code in the function cchg(). (#20805) * BUG: special: Restore missing line of code in the function cchg(). The line appears to have gone missing in the translation from Fortran to C++. Added new tests, which revealed that hyp1f1 can still be very inaccurate. Closes gh-20797. * MAINT: Change a couple commented out test cases to xfail cases. Co-authored-by: Albert Steppi * BUG/TST: Fix a test reference value. The 'j' suffix was missing in a complex value. --------- Co-authored-by: Albert Steppi --- scipy/special/special/specfun/specfun.h | 3 +- scipy/special/tests/test_hypergeometric.py | 32 ++++++++++++++++++++++ scipy/special/tests/test_specfun.py | 5 ---- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/scipy/special/special/specfun/specfun.h b/scipy/special/special/specfun/specfun.h index e5a536b6cdea..51c7d2bcfd78 100644 --- a/scipy/special/special/specfun/specfun.h +++ b/scipy/special/special/specfun/specfun.h @@ -556,7 +556,7 @@ inline std::complex cchg(double a, double b, std::complex z) { int i, j, k, la, m, n, nl, ns; double a0, a1, phi, x0, x, y; - std::complex cfac, cg1, cg2, cg3, chg, chg1, chg2, chw, cr, cr1, cr2, cs1,\ + std::complex cfac, cg1, cg2, cg3, chg, chg1, chg2, chw, cr, cr1, cr2, cs1, cs2, crg, cy0, cy1, z0; const double pi = 3.141592653589793; const std::complex ci(0.0, 1.0); @@ -602,6 +602,7 @@ inline std::complex cchg(double a, double b, std::complex z) { crg = 1.0; for (j = 1; j < 501; j++) { crg = crg * (a+j-1.0)/(j*(b+j-1.0))*z; + chg += crg; if (std::abs((chg-chw)/chg) < 1e-15) { break; } chw = chg; } diff --git a/scipy/special/tests/test_hypergeometric.py b/scipy/special/tests/test_hypergeometric.py index 749a7e357417..77c8454192d5 100644 --- a/scipy/special/tests/test_hypergeometric.py +++ b/scipy/special/tests/test_hypergeometric.py @@ -117,6 +117,38 @@ def test_gh_11099(self, a, b, x, desired): 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 diff --git a/scipy/special/tests/test_specfun.py b/scipy/special/tests/test_specfun.py index 891a08d3bce0..aa9c839f6c3e 100644 --- a/scipy/special/tests/test_specfun.py +++ b/scipy/special/tests/test_specfun.py @@ -7,11 +7,6 @@ from scipy import special -def test_cchg_branches(): - res = special.hyp1f1(0.1, 1, 7.0-24.0j) - assert_allclose(res, (-3.7659844658568016+4.970311359851648j)) - - 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])) From 2fb51ea31aa751f571a7a0911eb68c82bdeede79 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 27 May 2024 21:20:43 +0200 Subject: [PATCH 272/500] DOC: extend "building reproducible binaries" page Add details on: 1. Build and runtime dependencies 2. How to strip the test suite (follows up on PR 20712) [skip actions] [skip cirrus] --- .../building/redistributable_binaries.rst | 86 ++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/doc/source/building/redistributable_binaries.rst b/doc/source/building/redistributable_binaries.rst index 62f38f38ff6b..14c203187cbc 100644 --- a/doc/source/building/redistributable_binaries.rst +++ b/doc/source/building/redistributable_binaries.rst @@ -1,6 +1,10 @@ Building redistributable binaries ================================= +*This intended audience for this section is anyone who wants to build SciPy and +deploy it anywhere else than their own machine - from distro packagers to users +who want to build wheels to deploy to their production environment* + When ``python -m build`` or ``pip wheel`` is used to build a SciPy wheel, that wheel will rely on external shared libraries (at least for BLAS/LAPACK and a Fortran compiler runtime library, perhaps other libraries). Such wheels @@ -10,8 +14,9 @@ artifacts" =2.12.0,<2.13.0", + + # ... + # 3. The <2.3 upper bound is for matching the numpy deprecation policy, + # it should not be loosened. + "numpy>=2.0.0rc1,<2.3", + +Non-Python build requirements are: + +- C, C++ and Fortran compilers +- BLAS and LAPACK libraries +- ``ninja`` +- ``pkg-config`` + +Minimum versions of common compilers are enforced in the top-level +``meson.build`` file. The minimum LAPACK version is currently 3.7.1. +More detailed information on these build dependencies can be found in +:ref:`toolchain-roadmap`. + + +Stripping the test suite from a wheel or installed package +---------------------------------------------------------- + +By default, an installed version of ``scipy`` includes the full test suite. +That test suite, including data files and compiled extension modules that are +test-only, takes up about 4.5 MB in a wheel (for x86-64, as of v1.14.0), and +more than that on disk. In cases where binary size matters, packagers may want +to remove the test suite. As of SciPy 1.14.0, there is a convenient way of +doing this, making use of +`Meson's install tags `__ +functionality. It is a one-liner:: + + $ python -m build -wnx -Cinstall-args=--tags=runtime,python-runtime,devel + +.. note:: + + Note that in the above command ``-wnx`` means ``--wheel --no-isolation + --skip-dependency-check``. It assumes that the packager has already set up + the build environment, which is usually the case for distro packaging. The + install tags feature works equally well with isolated builds (e.g. ``pip + install numpy --no-binary -Cinstall-args=--tags=runtime,python-runtime,devel``). + +If you want to produce a separate package for the tests themselves, say under +the name ``scipy-tests``, then edit ``pyproject.toml`` to change the project +name: + +.. code:: toml + + [project] + name = "scipy-tests" + +And then build with:: + + $ python -m build -wnx -Cinstall-args=--tags=tests + +The above would build the whole package twice; in order to rebuild in a cached +fashion, use the ``-Cbuild-dir=build`` build option:: + + $ $ # apply patch to change the project name in pyproject.toml + $ python -m build -wnx -Cbuild-dir=build -Cinstall-args=--tags=tests + +The end result will look something like:: + + $ ls -lh dist/*.whl + ... 20M ... dist/scipy-1.14.0-cp311-cp311-linux_x86_64.whl + ... 4,5M ... dist/scipy_tests-1.14.0-cp311-cp311-linux_x86_64.whl From 5510e681d501a491703d52d4601c18f462344c17 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 27 May 2024 21:52:12 +0200 Subject: [PATCH 273/500] Apply suggestions from code review [skip ci] Co-authored-by: Tyler Reddy --- doc/source/building/redistributable_binaries.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/building/redistributable_binaries.rst b/doc/source/building/redistributable_binaries.rst index 14c203187cbc..d53750e81602 100644 --- a/doc/source/building/redistributable_binaries.rst +++ b/doc/source/building/redistributable_binaries.rst @@ -1,7 +1,7 @@ Building redistributable binaries ================================= -*This intended audience for this section is anyone who wants to build SciPy and +*The intended audience for this section is anyone who wants to build SciPy and deploy it anywhere else than their own machine - from distro packagers to users who want to build wheels to deploy to their production environment* @@ -75,7 +75,7 @@ functionality. It is a one-liner:: --skip-dependency-check``. It assumes that the packager has already set up the build environment, which is usually the case for distro packaging. The install tags feature works equally well with isolated builds (e.g. ``pip - install numpy --no-binary -Cinstall-args=--tags=runtime,python-runtime,devel``). + install scipy --no-binary -Cinstall-args=--tags=runtime,python-runtime,devel``). If you want to produce a separate package for the tests themselves, say under the name ``scipy-tests``, then edit ``pyproject.toml`` to change the project From 09769690f65cfc078c860f960f084aa3689df5b4 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Mon, 27 May 2024 22:02:47 +0100 Subject: [PATCH 274/500] DEP: special.comb: deprecate `exact=True` for non-integer intputs (#20780) * DEP: special: deprecate exact=True for non-integer intputs --- scipy/special/_basic.py | 7 +++++++ scipy/special/tests/test_basic.py | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/scipy/special/_basic.py b/scipy/special/_basic.py index 2ef20eb0c1d7..ca54215b533f 100644 --- a/scipy/special/_basic.py +++ b/scipy/special/_basic.py @@ -2692,6 +2692,10 @@ def comb(N, k, *, exact=False, repetition=False): exact : bool, optional For integers, if `exact` is False, then floating point precision is used, otherwise the result is computed exactly. + + .. deprecated:: 1.14.0 + ``exact=True`` is deprecated for non-integer `N` and `k` and will raise an + error in SciPy 1.16.0 repetition : bool, optional If `repetition` is True, then the number of combinations with repetition is computed. @@ -2734,6 +2738,9 @@ def comb(N, k, *, exact=False, repetition=False): return _comb_int(N, k) # otherwise, we disregard `exact=True`; it makes no sense for # non-integral arguments + msg = ("`exact=True` is deprecated for non-integer `N` and `k` and will raise " + "an error in SciPy 1.16.0") + warnings.warn(msg, DeprecationWarning, stacklevel=2) return comb(N, k) else: k, N = asarray(k), asarray(N) diff --git a/scipy/special/tests/test_basic.py b/scipy/special/tests/test_basic.py index f8f681e7301b..ca8845f7a3e2 100644 --- a/scipy/special/tests/test_basic.py +++ b/scipy/special/tests/test_basic.py @@ -1459,6 +1459,11 @@ def test_comb_zeros(self): 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_dep(self): + msg = "`exact=True`" + with pytest.deprecated_call(match=msg): + special.comb(3.4, 4, exact=True) def test_perm(self): assert_allclose(special.perm([10, 10], [3, 4]), [720., 5040.]) From 8fe576b5a5bba579135857c3a1c49d74e22492aa Mon Sep 17 00:00:00 2001 From: fancidev Date: Wed, 22 May 2024 21:54:46 +0800 Subject: [PATCH 275/500] Prevent loss of numerical precision if high == 2*pi. --- scipy/stats/_morestats.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 712515db52af..cd9dbba2fd05 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -4373,8 +4373,8 @@ def _circfuncs_common(samples, high, low, xp=None): # Recast samples as radians that range between 0 and 2 pi and calculate # the sine and cosine - sin_samp = xp.sin((samples - low)*2.*xp.pi / (high - low)) - cos_samp = xp.cos((samples - low)*2.*xp.pi / (high - low)) + sin_samp = xp.sin((samples - low) * (2.*pi / (high - low))) + cos_samp = xp.cos((samples - low) * (2.*pi / (high - low))) return samples, sin_samp, cos_samp @@ -4464,7 +4464,7 @@ def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): res = xp.atan2(sin_sum, cos_sum) % (2*xp.pi) res = res[()] if res.ndim == 0 else res - return res*(high - low)/2.0/xp.pi + low + return res * ((high-low)/(2.0*pi)) + low @_axis_nan_policy_factory( @@ -4663,7 +4663,7 @@ def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, res = xp.sqrt(-2*xp.log(R)) if not normalize: - res *= (high-low)/(2.*xp.pi) # [1] (2.3.14) w/ (2.3.7) + res *= (high-low)/(2.*pi) # [1] (2.3.14) w/ (2.3.7) return res From 27072a9af7cdfb2a992fad45b2a8101ff106256e Mon Sep 17 00:00:00 2001 From: fancidev Date: Wed, 22 May 2024 21:56:53 +0800 Subject: [PATCH 276/500] Remove redundant errstate guard. --- scipy/stats/_morestats.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index cd9dbba2fd05..639587ca0c39 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -4554,8 +4554,7 @@ def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): 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 - with np.errstate(invalid='ignore'): - R = xp_minimum(xp.asarray(1.), hypotenuse) + R = xp_minimum(xp.asarray(1.), hypotenuse) res = 1. - R return res @@ -4658,8 +4657,7 @@ def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, 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 - with np.errstate(invalid='ignore'): - R = xp_minimum(xp.asarray(1.), hypotenuse) # [1] (2.2.4) + R = xp_minimum(xp.asarray(1.), hypotenuse) # [1] (2.2.4) res = xp.sqrt(-2*xp.log(R)) if not normalize: From 42a143087d895914bb9b758f13f58bc7a127d2b7 Mon Sep 17 00:00:00 2001 From: fancidev Date: Fri, 24 May 2024 06:26:53 +0800 Subject: [PATCH 277/500] circstd: return +0.0 instead of -0.0. --- scipy/stats/_morestats.py | 2 +- scipy/stats/tests/test_morestats.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 639587ca0c39..e351798c7b10 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -4659,7 +4659,7 @@ def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, # hypotenuse can go slightly above 1 due to rounding errors R = xp_minimum(xp.asarray(1.), hypotenuse) # [1] (2.2.4) - res = xp.sqrt(-2*xp.log(R)) + res = (-2*xp.log(R))**0.5 # do not use sqrt to avoid -0.0 if R==1 if not normalize: res *= (high-low)/(2.*pi) # [1] (2.3.14) w/ (2.3.7) return res diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 24e6428cd71b..201330a641e8 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2674,6 +2674,11 @@ def test_circfuncs_uint8(self, xp): 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])) + xp_assert_equal(xp.signbit(y), xp.asarray(False)) + class TestCircFuncsNanPolicy: # `nan_policy` is implemented by the `_axis_nan_policy` decorator, which is From 1f7daecff34fa2154676a605e5c218279a1085df Mon Sep 17 00:00:00 2001 From: fancidev Date: Tue, 28 May 2024 06:42:35 +0800 Subject: [PATCH 278/500] Skip problematic xp backends. --- scipy/stats/tests/test_morestats.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 201330a641e8..bbad4ee51d4d 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2674,6 +2674,10 @@ def test_circfuncs_uint8(self, xp): xp_assert_close(stats.circvar(x, high=180), xp.asarray(0.2339555554617)) xp_assert_close(stats.circstd(x, high=180), xp.asarray(20.91551378)) + @skip_xp_backends("array_api_strict", "torch", reasons=[ + "array_api_strict does not yet support xp.signbit", + "pytorch's pow is non-compliant and may return -0", + ]) def test_circstd_zero(self, xp): # circstd() of a single number should return positive zero. y = stats.circstd(xp.asarray([0])) From 96be3156c225e5a25604acfa085eda691165d323 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 27 May 2024 17:07:57 -0700 Subject: [PATCH 279/500] TST: optimize.differential_evolution: add fail-slow exception --- scipy/optimize/tests/test__differential_evolution.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/optimize/tests/test__differential_evolution.py b/scipy/optimize/tests/test__differential_evolution.py index b6b1ba39a242..1c71d332ee65 100644 --- a/scipy/optimize/tests/test__differential_evolution.py +++ b/scipy/optimize/tests/test__differential_evolution.py @@ -1632,6 +1632,7 @@ def func(x): # "MAXCV = 0.". assert "MAXCV = 0.4" in result.message + @pytest.mark.fail_slow(10) # fail-slow exception by request - see gh-20806 def test_strategy_fn(self): # examines ability to customize strategy by mimicking one of the # in-built strategies From 28429263c79135899ef06c1e0dcd5919c9a70fc5 Mon Sep 17 00:00:00 2001 From: Warren Weckesser Date: Tue, 28 May 2024 00:17:04 -0400 Subject: [PATCH 280/500] DOC: integrate: odeint user functions must not modify y. (#20815) [docs only] --- scipy/integrate/_odepack_py.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scipy/integrate/_odepack_py.py b/scipy/integrate/_odepack_py.py index b868c0ced8c2..20993e5bb516 100644 --- a/scipy/integrate/_odepack_py.py +++ b/scipy/integrate/_odepack_py.py @@ -59,6 +59,8 @@ def odeint(func, y0, t, args=(), Dfun=None, col_deriv=0, full_output=0, Computes the derivative of y at t. If the signature is ``callable(t, y, ...)``, then the argument `tfirst` must be set ``True``. + `func` must not modify the data in `y`, as it is a + view of the data used internally by the ODE solver. y0 : array Initial condition on y (can be a vector). t : array @@ -72,6 +74,8 @@ def odeint(func, y0, t, args=(), Dfun=None, col_deriv=0, full_output=0, Gradient (Jacobian) of `func`. If the signature is ``callable(t, y, ...)``, then the argument `tfirst` must be set ``True``. + `Dfun` must not modify the data in `y`, as it is a + view of the data used internally by the ODE solver. col_deriv : bool, optional True if `Dfun` defines derivatives down columns (faster), otherwise `Dfun` should define derivatives across rows. From 8593006952672919a0a8dd970c702b1b0007c300 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 27 May 2024 22:10:47 -0700 Subject: [PATCH 281/500] MAINT: stats: make reducing functions emit consistent warning when sample is too small or empty (#20694) --- doc/source/conf.py | 2 +- scipy/_lib/tests/test_warnings.py | 1 + scipy/stats/_axis_nan_policy.py | 50 +++- scipy/stats/_hypotests.py | 6 +- scipy/stats/_morestats.py | 31 +- scipy/stats/_stats_py.py | 82 +++--- scipy/stats/tests/test_axis_nan_policy.py | 331 ++++++++++++++-------- scipy/stats/tests/test_hypotests.py | 51 ++-- scipy/stats/tests/test_morestats.py | 119 +++++--- scipy/stats/tests/test_mstats_basic.py | 6 +- scipy/stats/tests/test_stats.py | 246 ++++++++++------ scipy/stats/tests/test_variation.py | 37 ++- 12 files changed, 596 insertions(+), 366 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index ce309446eec2..7afb898c9a3d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -350,7 +350,7 @@ for key in ( 'interp2d` is deprecated', # Deprecation of scipy.interpolate.interp2d 'scipy.misc', # scipy.misc deprecated in v1.10.0; use scipy.datasets - '`kurtosistest` p-value may be', # intentionally "bad" excample in docstring + '`kurtosistest` p-value may be', # intentionally "bad" example in docstring 'scipy.signal.daub is deprecated', 'scipy.signal.qmf is deprecated', 'scipy.signal.cascade is deprecated', diff --git a/scipy/_lib/tests/test_warnings.py b/scipy/_lib/tests/test_warnings.py index aad199d4bc10..24245d9dd6e8 100644 --- a/scipy/_lib/tests/test_warnings.py +++ b/scipy/_lib/tests/test_warnings.py @@ -118,6 +118,7 @@ def test_warning_calls_filters(warning_calls): os.path.join('stats', '_discrete_distns.py'), # gh-14901 os.path.join('stats', '_continuous_distns.py'), os.path.join('stats', '_binned_statistic.py'), # gh-19345 + os.path.join('stats', 'tests', 'test_axis_nan_policy.py'), # gh-20694 os.path.join('_lib', '_util.py'), # gh-19341 "conftest.py", ) diff --git a/scipy/stats/_axis_nan_policy.py b/scipy/stats/_axis_nan_policy.py index 24e36bfa7d14..ab7c3da69a4b 100644 --- a/scipy/stats/_axis_nan_policy.py +++ b/scipy/stats/_axis_nan_policy.py @@ -4,13 +4,39 @@ # that support `axis` and `nan_policy`, including a decorator that # automatically adds `axis` and `nan_policy` arguments to a function. +import warnings import numpy as np from functools import wraps 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 + 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): """ @@ -363,7 +389,6 @@ def _axis_nan_policy_factory(tuple_to_result, default_axis=0, 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'``. - (See `scipy.stats.mode`, for example.) """ # Specify which existing behaviors the decorator must override temp = override or {} @@ -529,33 +554,36 @@ def hypotest_fun_out(*samples, **kwds): 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) - - # ideally, this is what the behavior would be: - # if is_too_small(samples): - # return tuple_to_result(NaN, NaN) - # but some existing functions raise exceptions, and changing - # behavior of those would break backward compatibility. + 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 = np.full(n_out, NaN) + res = _add_reduced_axes(res, reduced_axes, keepdims) + return tuple_to_result(*res) + res = hypotest_fun_out(*samples, **kwds) res = result_to_tuple(res) res = _add_reduced_axes(res, reduced_axes, keepdims) return tuple_to_result(*res) # check for empty input - # ideally, move this to the top, but some existing functions raise - # exceptions for empty input, so overriding it would break - # backward compatibility. empty_output = _check_empty_inputs(samples, axis) # only return empty output if zero sized input is too small. if ( empty_output is not None and (is_too_small(samples, kwds) or empty_output.size == 0) ): + if is_too_small(samples, kwds) and empty_output.size != 0: + warnings.warn(too_small_nd_not_omit, SmallSampleWarning, + stacklevel=2) res = [empty_output.copy() for i in range(n_out)] res = _add_reduced_axes(res, reduced_axes, keepdims) return tuple_to_result(*res) @@ -586,6 +614,8 @@ def hypotest_fun(x): 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)) diff --git a/scipy/stats/_hypotests.py b/scipy/stats/_hypotests.py index 2bd5539cdfbf..d445c5494fcc 100644 --- a/scipy/stats/_hypotests.py +++ b/scipy/stats/_hypotests.py @@ -37,7 +37,8 @@ def epps_singleton_2samp(x, y, t=(0.4, 0.8)): ---------- 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. + 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 @@ -501,6 +502,7 @@ def cramervonmises(rvs, cdf, args=()): ---------- 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 @@ -1556,8 +1558,10 @@ def cramervonmises_2samp(x, y, method='auto'): ---------- 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'. diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 712515db52af..19f24929bb47 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -306,10 +306,6 @@ def kstat(data, n=2, *, axis=None): N = data.shape[axis] - # raise ValueError on empty input - if N == 0: - raise ValueError("Data input must not be empty") - S = [None] + [xp.sum(data**k, axis=axis) for k in range(1, n + 1)] if n == 1: return S[1] * 1.0/N @@ -377,10 +373,10 @@ def kstatvar(data, n=2, *, axis=None): N = data.shape[axis] if n == 1: - return kstat(data, n=2, axis=axis) * 1.0/N + return kstat(data, n=2, axis=axis, _no_deco=True) * 1.0/N elif n == 2: - k2 = kstat(data, n=2, axis=axis) - k4 = kstat(data, n=4, axis=axis) + 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.") @@ -1883,7 +1879,7 @@ def shapiro(x): Parameters ---------- x : array_like - Array of sample data. + Array of sample data. Must contain at least three observations. Returns ------- @@ -3061,17 +3057,11 @@ def bartlett(*samples, axis=0): if k < 2: raise ValueError("Must enter at least two input sample vectors.") - # Handle 1d empty input; _axis_nan_policy takes care of N-D - for sample in samples: - if xp_size(xp.asarray(sample)) == 0: - NaN = _get_nan(*samples, xp=xp) # get NaN of result_dtype of all samples - return BartlettResult(NaN, NaN) - samples = _broadcast_arrays(samples, axis=axis, xp=xp) samples = [xp_moveaxis_to_end(sample, axis, xp=xp) for sample in samples] Ni = [xp.asarray(sample.shape[-1], dtype=sample.dtype) for sample in samples] - Ni = [xp.broadcast_to(N, sample.shape[:-1]) for N in Ni] + 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] @@ -3758,7 +3748,8 @@ def mood(x, y, axis=0, alternative="two-sided"): Parameters ---------- x, y : array_like - Arrays of sample data. + 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`. @@ -4362,10 +4353,6 @@ def median_test(*samples, ties='below', correction=True, lambda_=1, def _circfuncs_common(samples, high, low, xp=None): xp = array_namespace(samples) if xp is None else xp - # Ensure samples are array-like and size is not zero - if xp_size(samples) == 0: - NaN = _get_nan(samples, xp=xp) - return NaN, NaN, NaN if xp.isdtype(samples.dtype, 'integral'): dtype = xp.asarray(1.).dtype # get default float type @@ -4458,6 +4445,10 @@ def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): """ 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) samples, sin_samp, cos_samp = _circfuncs_common(samples, high, low, xp=xp) sin_sum = xp.sum(sin_samp, axis=axis) cos_sum = xp.sum(cos_samp, axis=axis) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 34b31330d9c8..05feae9d57dd 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -60,7 +60,8 @@ _batch_generator) from ._axis_nan_policy import (_axis_nan_policy_factory, _broadcast_concatenate, - _broadcast_shapes) + _broadcast_shapes, + SmallSampleWarning) from ._binomtest import _binary_search_for_binom_tst as _binary_search from scipy._lib._bunch import _make_tuple_bunch from scipy import stats @@ -443,7 +444,7 @@ def _mode_result(mode, count): # but `count` should not be NaN; it should be zero. i = np.isnan(count) if i.shape == (): - count = count.dtype(0) if i else count + count = np.asarray(0, dtype=count.dtype)[()] if i else count else: count[i] = 0 return ModeResult(mode, count) @@ -1459,7 +1460,7 @@ def skewtest(a, axis=0, nan_policy='propagate', alternative='two-sided'): Parameters ---------- a : array - The data to be tested. + 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`. @@ -1611,12 +1612,13 @@ def skewtest(a, axis=0, nan_policy='propagate', alternative='two-sided'): xp = array_namespace(a) a, axis = _chk_asarray(a, axis, xp=xp) - b2 = skew(a, axis) + b2 = skew(a, axis, _no_deco=True) n = a.shape[axis] if n < 8: message = ("`skewtest` requires at least 8 observations; " - f"{n} observations were given.") + f"only {n=} observations were given.") raise ValueError(message) + y = b2 * math.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))) @@ -1647,7 +1649,7 @@ def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): Parameters ---------- a : array - Array of the sample data. + 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`. @@ -1814,7 +1816,7 @@ def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): message = ("`kurtosistest` p-value may be inaccurate with fewer than 20 " f"observations; only {n=} observations were given.") warnings.warn(message, stacklevel=2) - b2 = kurtosis(a, axis, fisher=False) + b2 = kurtosis(a, axis, fisher=False, _no_deco=True) 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 @@ -1858,7 +1860,8 @@ def normaltest(a, axis=0, nan_policy='propagate'): Parameters ---------- a : array_like - The array containing the sample to be tested. + 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`. @@ -1997,8 +2000,8 @@ def normaltest(a, axis=0, nan_policy='propagate'): """ xp = array_namespace(a) - s, _ = skewtest(a, axis) - k, _ = kurtosistest(a, axis) + s, _ = skewtest(a, axis, _no_deco=True) + k, _ = kurtosistest(a, axis, _no_deco=True) statistic = s*s + k*k chi2 = _SimpleChi2(xp.asarray(2.)) @@ -2807,7 +2810,7 @@ def sem(a, axis=0, ddof=1, nan_policy='propagate'): ---------- a : array_like An array containing the values for which the standard error is - returned. + 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`. @@ -3963,26 +3966,27 @@ def _first(arr, axis): def _f_oneway_is_too_small(samples, kwargs={}, 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): - msg = 'at least one input has length 0' - warnings.warn(stats.DegenerateDataWarning(msg), stacklevel=2) 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(stats.DegenerateDataWarning(msg), stacklevel=2) + warnings.warn(SmallSampleWarning(msg), stacklevel=2) return True return False @_axis_nan_policy_factory( - F_onewayResult, n_samples=None, too_small=_f_oneway_is_too_small -) + F_onewayResult, n_samples=None, too_small=_f_oneway_is_too_small) def f_oneway(*samples, axis=0): """Perform one-way ANOVA. @@ -4010,12 +4014,12 @@ def f_oneway(*samples, axis=0): Warns ----- `~scipy.stats.ConstantInputWarning` - Raised if all values within each of the input arrays are identical. + 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. - `~scipy.stats.DegenerateDataWarning` - Raised if the length of any input array is 0, or if all the input + 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. @@ -4228,7 +4232,7 @@ def alexandergovern(*samples, nan_policy='propagate'): ---------- sample1, sample2, ... : array_like The sample measurements for each group. There must be at least - two samples. + 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'): @@ -6437,6 +6441,13 @@ def ttest_1samp(a, popmean, axis=0, nan_policy='propagate', n = a.shape[axis] df = n - 1 + if n == 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) @@ -6500,6 +6511,7 @@ def _t_confidence_interval(df, t, confidence_level, alternative, dtype=None, xp= high = high[()] if high.ndim == 0 else high return low, high + def _ttest_ind_from_stats(mean1, mean2, denom, df, alternative): d = mean1 - mean2 @@ -8863,33 +8875,8 @@ def kruskal(*samples, nan_policy='propagate'): if num_groups < 2: raise ValueError("Need at least two groups in stats.kruskal()") - for sample in samples: - if sample.size == 0: - NaN = _get_nan(*samples) - return KruskalResult(NaN, NaN) - elif sample.ndim != 1: - raise ValueError("Samples must be one-dimensional.") - n = np.asarray(list(map(len, samples))) - if nan_policy not in ('propagate', 'raise', 'omit'): - raise ValueError("nan_policy must be 'propagate', 'raise' or 'omit'") - - contains_nan = False - for sample in samples: - cn = _contains_nan(sample, nan_policy) - if cn[0]: - contains_nan = True - break - - if contains_nan and nan_policy == 'omit': - for sample in samples: - sample = ma.masked_invalid(sample) - return mstats_basic.kruskal(*samples) - - if contains_nan and nan_policy == 'propagate': - return KruskalResult(np.nan, np.nan) - alldata = np.concatenate(samples) ranked = rankdata(alldata) ties = tiecorrect(ranked) @@ -9094,12 +9081,9 @@ def brunnermunzel(x, y, alternative="two-sided", distribution="t", 0.0057862086661515377 """ - nx = len(x) ny = len(y) - if nx == 0 or ny == 0: - NaN = _get_nan(x, y) - return BrunnerMunzelResult(NaN, NaN) + rankc = rankdata(np.concatenate((x, y))) rankcx = rankc[0:nx] rankcy = rankc[nx:nx+ny] diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index 14a3fba372e0..1c548fd63186 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -9,12 +9,16 @@ import re import pickle import pytest +import warnings import numpy as np -from numpy.testing import assert_allclose, assert_equal, suppress_warnings +from numpy.testing import assert_allclose, assert_equal from scipy import stats from scipy.stats import norm # type: ignore[attr-defined] -from scipy.stats._axis_nan_policy import _masked_arrays_2_sentinel_arrays +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.conftest import skip_xp_invalid_arg @@ -72,7 +76,7 @@ def ttest_ci(*args, **kwargs): (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(), 2, 2, False, 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, (norm().cdf,), dict(), 1, 4, False, @@ -115,8 +119,7 @@ def ttest_ci(*args, **kwargs): # If the message is one of those expected, put nans in # appropriate places of `statistics` and `pvalues` -too_small_messages = {"The input contains nan", # for nan_policy="raise" - "Degrees of freedom <= 0 for slice", +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", @@ -139,7 +142,9 @@ def ttest_ci(*args, **kwargs): "`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.",} + "Input sample size must be greater than one.", + "invalid value encountered", + "divide by zero encountered",} # If the message is one of these, results of the function may be inaccurate, # but NaNs are not to be placed @@ -152,6 +157,9 @@ def ttest_ci(*args, **kwargs): # For some functions, empty arrays produce non-NaN results empty_special_case_funcs = {stats.entropy} +# Some functions don't follow the usual "too small" warning rules +too_small_special_case_funcs = {stats.entropy} + def _mixed_data_generator(n_samples, n_repetitions, axis, rng, paired=False): # generate random samples to check the response of hypothesis tests to @@ -239,8 +247,24 @@ def nan_policy_1d(hypotest, data1d, unpacker, *args, n_outputs=2, return unpacker(hypotest(*data1d, *args, _no_deco=_no_deco, **kwds)) -@pytest.mark.filterwarnings('ignore::RuntimeWarning') -@pytest.mark.filterwarnings('ignore::UserWarning') +# 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")) @@ -259,8 +283,25 @@ def test_axis_nan_policy_fast(hypotest, args, kwds, n_samples, n_outputs, # 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. - @pytest.mark.filterwarnings('ignore::RuntimeWarning') - @pytest.mark.filterwarnings('ignore::UserWarning') + + # 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")) @@ -312,89 +353,89 @@ def unpacker(res): 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] - statistics = np.zeros(output_shape) - pvalues = np.zeros(output_shape) + res_1d = np.zeros(output_shape + [n_outputs]) - for i, _ in np.ndenumerate(statistics): + for i, _ in np.ndenumerate(np.zeros(output_shape)): data1d = [sample[i] for sample in data_b] - with np.errstate(divide='ignore', invalid='ignore'): - try: - res1d = nan_policy_1d(hypotest, data1d, unpacker, *args, - n_outputs=n_outputs, - nan_policy=nan_policy, - paired=paired, _no_deco=True, **kwds) - - # Eventually we'll check the results of a single, vectorized - # call of `hypotest` against the arrays `statistics` and - # `pvalues` populated using the reference `nan_policy_1d`. - # But while we're at it, check the results of a 1D call to - # `hypotest` against the reference `nan_policy_1d`. - res1db = unpacker(hypotest(*data1d, *args, - nan_policy=nan_policy, **kwds)) - assert_equal(res1db[0], res1d[0]) - if len(res1db) == 2: - assert_equal(res1db[1], res1d[1]) - - # When there is not enough data in 1D samples, many existing - # hypothesis tests raise errors instead of returning nans . - # For vectorized calls, we put nans in the corresponding elements - # of the output. - except (RuntimeWarning, UserWarning, ValueError, - ZeroDivisionError) as e: - - # whatever it is, make sure same error is raised by both - # `nan_policy_1d` and `hypotest` - with pytest.raises(type(e), match=re.escape(str(e))): - nan_policy_1d(hypotest, data1d, unpacker, *args, - n_outputs=n_outputs, nan_policy=nan_policy, - paired=paired, _no_deco=True, **kwds) - with pytest.raises(type(e), match=re.escape(str(e))): - hypotest(*data1d, *args, nan_policy=nan_policy, **kwds) - - if any([str(e).startswith(message) - for message in too_small_messages]): - res1d = np.full(n_outputs, np.nan) - elif any([str(e).startswith(message) - for message in inaccuracy_messages]): - with suppress_warnings() as sup: - sup.filter(RuntimeWarning) - sup.filter(UserWarning) - res1d = nan_policy_1d(hypotest, data1d, unpacker, - *args, n_outputs=n_outputs, - nan_policy=nan_policy, - paired=paired, _no_deco=True, - **kwds) - else: - raise e - statistics[i] = res1d[0] - if len(res1d) == 2: - pvalues[i] = res1d[1] + 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) 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 np.testing.suppress_warnings() as sup: + sup.filter(SmallSampleWarning, too_small_1d_omit) + 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_equal(res_1db, res_1da) + 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. - # If not, compare against the output against `statistics` and `pvalues` + # 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) + + assert_allclose(res_nd, res_1d, rtol=1e-14) + - else: - with suppress_warnings() as sup, \ - np.errstate(divide='ignore', invalid='ignore'): - sup.filter(RuntimeWarning, "Precision loss occurred in moment") - sup.filter(UserWarning, "Sample size too small for normal " - "approximation.") - res = unpacker(hypotest(*data, axis=axis, nan_policy=nan_policy, - *args, **kwds)) - assert_allclose(res[0], statistics, rtol=1e-14) - assert_equal(res[0].dtype, statistics.dtype) - - if len(res) == 2: - assert_allclose(res[1], pvalues, rtol=1e-14) - assert_equal(res[1].dtype, pvalues.dtype) - - -@pytest.mark.filterwarnings('ignore::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")) @@ -404,7 +445,6 @@ 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 @@ -436,45 +476,68 @@ def unpacker(res): hypotest(*data_raveled, axis=None, nan_policy=nan_policy, *args, **kwds) - else: - # behavior of reference implementation with 1d input, hypotest with 1d - # input, and hypotest with Nd input should match, whether that means - # that outputs are equal or they raise the same exception + return - ea_str, eb_str, ec_str = None, None, None - with np.errstate(divide='ignore', invalid='ignore'): - 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) as ea: - ea_str = str(ea) + # 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") - try: - res1db = unpacker(hypotest(*data_raveled, *args, - nan_policy=nan_policy, **kwds)) - except (RuntimeWarning, ValueError, ZeroDivisionError) as eb: - eb_str = str(eb) + 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) 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) + assert_equal(res1dc, res1db) + all_results = list(res1db) + list(res1dc) + + if res1da is not None: + assert_equal(res1db, res1da) + all_results += list(res1da) + + for item in all_results: + assert np.issubdtype(item.dtype, np.number) + assert np.isscalar(item) - try: - res1dc = unpacker(hypotest(*data, *args, axis=None, - nan_policy=nan_policy, **kwds)) - except (RuntimeWarning, ValueError, ZeroDivisionError) as ec: - ec_str = str(ec) - - if ea_str or eb_str or ec_str: - assert any([str(ea_str).startswith(message) - for message in too_small_messages]) - assert ea_str == eb_str == ec_str - else: - assert_equal(res1db, res1da) - assert_equal(res1dc, res1da) - for item in list(res1da) + list(res1db) + list(res1dc): - # Most functions naturally return NumPy numbers, which - # are drop-in replacements for the Python versions but with - # desirable attributes. Make sure this is consistent. - assert np.issubdtype(item.dtype, np.number) # Test keepdims for: # - single-output and multi-output functions (gmean and mannwhitneyu) @@ -482,6 +545,8 @@ def unpacker(res): # - 1D with no NaNs # - 1D with NaN propagation # - Zero-sized output +@pytest.mark.filterwarnings('ignore:All axis-slices of one...') +@pytest.mark.filterwarnings('ignore:After omitting NaNs...') @pytest.mark.parametrize("nan_policy", ("omit", "propagate")) @pytest.mark.parametrize( ("hypotest", "args", "kwds", "n_samples", "unpacker"), @@ -713,7 +778,7 @@ def small_sample_generator(n_dims): gens = [small_sample_generator(n_dims) for i in range(n_samples)] yield from product(*gens) - n_dims = [2, 3] + n_dims = [1, 2, 3] for samples in small_data_generator(n_samples, n_dims): # this test is only for arrays of zero size @@ -737,13 +802,21 @@ def small_sample_generator(n_dims): if hypotest in empty_special_case_funcs: empty_val = hypotest(*([[]]*len(samples)), *args, **kwds) + expected = np.asarray(expected) mask = np.isnan(expected) expected[mask] = empty_val + expected = expected[()] - with np.testing.suppress_warnings() as sup: - # generated by f_oneway for too_small inputs - sup.filter(stats.DegenerateDataWarning) - res = hypotest(*samples, *args, axis=axis, **kwds) + if expected.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 np.testing.suppress_warnings() as sup: + # f_oneway special case + sup.filter(SmallSampleWarning, "all input arrays have length 1") + res = hypotest(*samples, *args, axis=axis, **kwds) res = unpacker(res) for i in range(n_outputs): @@ -891,6 +964,8 @@ def test_masked_stat_1d(): 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"), range(-3, 3)) def test_masked_stat_3d(axis): @@ -915,6 +990,8 @@ def test_masked_stat_3d(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: @@ -963,6 +1040,8 @@ def test_mixed_mask_nan_1(): 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: @@ -1082,6 +1161,8 @@ def test_other_axis_tuples(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 @pytest.mark.parametrize( ("weighted_fun_name, unpacker"), diff --git a/scipy/stats/tests/test_hypotests.py b/scipy/stats/tests/test_hypotests.py index 516d5b080b25..7783c65d8d0a 100644 --- a/scipy/stats/tests/test_hypotests.py +++ b/scipy/stats/tests/test_hypotests.py @@ -17,6 +17,7 @@ from scipy.stats._mannwhitneyu import mannwhitneyu, _mwu_state from .common_tests import check_named_results from scipy._lib._testutils import _TestPythranFunc +from scipy.stats._axis_nan_policy import SmallSampleWarning, too_small_1d_not_omit class TestEppsSingleton: @@ -55,9 +56,12 @@ def test_epps_singleton_array_like(self): assert_(p1 == p2 == p3) def test_epps_singleton_size(self): - # raise error if less than 5 elements + # warns if sample contains fewer than 5 elements x, y = (1, 2, 3, 4), np.arange(10) - assert_raises(ValueError, epps_singleton_2samp, x, y) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = epps_singleton_2samp(x, y) + assert_equal(res.statistic, np.nan) + assert_equal(res.pvalue, np.nan) def test_epps_singleton_nonfinite(self): # raise error if there are non-finite values @@ -128,9 +132,12 @@ def test_low_p(self): assert_(_cdf_cvm(res.statistic, n) > 1.0) assert_equal(res.pvalue, 0) - def test_invalid_input(self): - assert_raises(ValueError, cramervonmises, [1.5], "norm") - assert_raises(ValueError, cramervonmises, (), "norm") + @pytest.mark.parametrize('x', [(), [1.5]]) + def test_invalid_input(self, x): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = cramervonmises(x, "norm") + assert_equal(res.statistic, np.nan) + assert_equal(res.pvalue, np.nan) def test_values_R(self): # compared against R package goftest, version 1.1.1 @@ -168,13 +175,21 @@ class TestMannWhitneyU: # --- Test Input Validation --- + @pytest.mark.parametrize('kwargs_update', [{'x': []}, {'y': []}, + {'x': [], 'y': []}]) + def test_empty(self, kwargs_update): + x = np.array([1, 2]) # generic, valid inputs + y = np.array([3, 4]) + kwargs = dict(x=x, y=y) + kwargs.update(kwargs_update) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = mannwhitneyu(**kwargs) + assert_equal(res.statistic, np.nan) + assert_equal(res.pvalue, np.nan) + def test_input_validation(self): x = np.array([1, 2]) # generic, valid inputs y = np.array([3, 4]) - with assert_raises(ValueError, match="`x` and `y` must be of nonzero"): - mannwhitneyu([], y) - with assert_raises(ValueError, match="`x` and `y` must be of nonzero"): - mannwhitneyu(x, []) 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"): @@ -572,11 +587,6 @@ def test_gh_9184(self, use_continuity, alternative, method, pvalue_exp): assert_equal(res.statistic, statistic_exp) assert_allclose(res.pvalue, pvalue_exp) - def test_gh_6897(self): - # Test for correct behavior with empty input - with assert_raises(ValueError, match="`x` and `y` must be of nonzero"): - mannwhitneyu([], []) - def test_gh_4067(self): # Test for correct behavior with all NaN input - default is propagate a = np.array([np.nan, np.nan, np.nan, np.nan, np.nan]) @@ -1314,13 +1324,16 @@ def test_against_fisher_exact(self, alternative): class TestCvm_2samp: + @pytest.mark.parametrize('args', [([], np.arange(5)), + (np.arange(5), [1])]) + def test_too_small_input(self, args): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = cramervonmises_2samp(*args) + assert_equal(res.statistic, np.nan) + assert_equal(res.pvalue, np.nan) + def test_invalid_input(self): y = np.arange(5) - msg = 'x and y must contain at least two observations.' - with pytest.raises(ValueError, match=msg): - cramervonmises_2samp([], y) - with pytest.raises(ValueError, match=msg): - cramervonmises_2samp(y, [1]) msg = 'method must be either auto, exact or asymptotic' with pytest.raises(ValueError, match=msg): cramervonmises_2samp(y, y, 'xyz') diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 24e6428cd71b..e4bd35bd8573 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -7,6 +7,7 @@ from functools import partial import numpy as np +import numpy.testing from numpy.random import RandomState from numpy.testing import (assert_array_equal, assert_almost_equal, assert_array_less, assert_array_almost_equal, @@ -21,10 +22,12 @@ from .._hypotests import _get_wilcoxon_distr, _get_wilcoxon_distr2 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) from scipy.conftest import array_api_compatible from scipy._lib._array_api import (array_namespace, xp_assert_close, xp_assert_less, - SCIPY_ARRAY_API, xp_assert_equal) + xp_assert_equal, is_numpy) skip_xp_backends = pytest.mark.skip_xp_backends @@ -191,19 +194,12 @@ def test_2d(self): assert_almost_equal(pw, 0.52460, decimal=3) assert_almost_equal(shapiro_test.pvalue, 0.52460, decimal=3) - def test_empty_input(self): - assert_raises(ValueError, stats.shapiro, []) - assert_raises(ValueError, stats.shapiro, [[], [], []]) - - def test_not_enough_values(self): - assert_raises(ValueError, stats.shapiro, [1, 2]) - error_type = TypeError if SCIPY_ARRAY_API else ValueError - assert_raises(error_type, stats.shapiro, np.array([[], [2]], dtype=object)) - - def test_bad_arg(self): - # Length of x is less than 3. - x = [1] - assert_raises(ValueError, stats.shapiro, x) + @pytest.mark.parametrize('x', ([], [1], [1, 2])) + def test_not_enough_values(self, x): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.shapiro(x) + assert_equal(res.statistic, np.nan) + assert_equal(res.pvalue, np.nan) def test_nan_input(self): x = np.arange(10.) @@ -634,9 +630,12 @@ def test_exact(self): assert_almost_equal(W, 10.0, 11) assert_almost_equal(pval, 0.533333333333333333, 7) - def test_bad_arg(self): - assert_raises(ValueError, stats.ansari, [], [1]) - assert_raises(ValueError, stats.ansari, [1], []) + @pytest.mark.parametrize('args', [([], [1]), ([1], [])]) + def test_bad_arg(self, args): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.ansari(*args) + assert_equal(res.statistic, np.nan) + assert_equal(res.pvalue, np.nan) def test_result_attributes(self): x = [1, 2, 3, 3, 4] @@ -757,10 +756,23 @@ def test_result_attributes(self, xp): attributes = ('statistic', 'pvalue') check_named_results(res, attributes) + @pytest.mark.skip_xp_backends( + "jax.numpy", cpu_only=True, + reasons=['`var` incorrect when `correction > n` (google/jax#21330)']) + @pytest.mark.usefixtures("skip_xp_backends") 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] - res = stats.bartlett(*args) + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.bartlett(*args) + else: + with np.testing.suppress_warnings() as sup: + # torch/array_api_strict + sup.filter(RuntimeWarning, "invalid value encountered") + sup.filter(UserWarning, r"var\(\): degrees of freedom is <= 0.") + sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice") + res = stats.bartlett(*args) NaN = xp.asarray(xp.nan) xp_assert_equal(res.statistic, NaN) xp_assert_equal(res.pvalue, NaN) @@ -1141,7 +1153,10 @@ def test_bad_num_args(self): def test_empty_arg(self): x = np.arange(5) - assert_equal((np.nan, np.nan), stats.fligner(x, x**2, [])) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.fligner(x, x**2, []) + assert_equal(res.statistic, np.nan) + assert_equal(res.pvalue, np.nan) def mood_cases_with_ties(): @@ -1309,9 +1324,11 @@ def test_mood_3d(self): stats.mood(slice1, slice2)) def test_mood_bad_arg(self): - # Raise ValueError when the sum of the lengths of the args is - # less than 3 - assert_raises(ValueError, stats.mood, [1], []) + # Warns when the sum of the lengths of the args is less than 3 + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.mood([1], []) + assert_equal(res.statistic, np.nan) + assert_equal(res.pvalue, np.nan) def test_mood_alternative(self): @@ -1722,9 +1739,13 @@ def test_moments_normal_distribution(self, xp): xp_assert_close(xp.asarray((m1, m2, m3)), expected[:-1], atol=0.02, rtol=1e-2) def test_empty_input(self, xp): - message = 'Data input must not be empty' - with pytest.raises(ValueError, match=message): - stats.kstat(xp.asarray([])) + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.kstat(xp.asarray([])) + else: + with np.errstate(invalid='ignore'): # for array_api_strict + res = stats.kstat(xp.asarray([])) + xp_assert_equal(res, xp.asarray(xp.nan)) def test_nan_input(self, xp): data = xp.arange(10.) @@ -1758,13 +1779,17 @@ def test_against_R(self, case, xp): xp_assert_close(res, xp.asarray(ref)) - @array_api_compatible class TestKstatVar: def test_empty_input(self, xp): - message = 'Data input must not be empty' - with pytest.raises(ValueError, match=message): - stats.kstatvar(xp.asarray([])) + x = xp.asarray([]) + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.kstatvar(x) + else: + with np.errstate(invalid='ignore'): # for array_api_strict + res = stats.kstatvar(x) + xp_assert_equal(res, xp.asarray(xp.nan)) def test_nan_input(self, xp): data = xp.arange(10.) @@ -1774,7 +1799,8 @@ def test_nan_input(self, xp): @skip_xp_backends(np_only=True, reasons=['input validation of `n` does not depend on backend']) - def test_bad_arg(self, xp): + @pytest.mark.usefixtures("skip_xp_backends") + def test_bad_arg(self): # Raise ValueError is n is not 1 or 2. data = [1] n = 10 @@ -2624,7 +2650,16 @@ def test_circfuncs_array_like(self, test_func, expected, xp): def test_empty(self, test_func, xp): dtype = xp.float64 x = xp.asarray([], dtype=dtype) - xp_assert_equal(test_func(x), xp.asarray(xp.nan, dtype=dtype)) + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = test_func(x) + else: + with np.testing.suppress_warnings() as sup: + # for array_api_strict + sup.filter(RuntimeWarning, "Mean of empty slice") + sup.filter(RuntimeWarning, "invalid value encountered") + 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]) @@ -2705,12 +2740,14 @@ def test_nan_omit_array(self, test_func, expected): [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(): - out = test_func(x, high=360, nan_policy='omit', axis=axis) if axis is None: + out = test_func(x, high=360, nan_policy='omit', axis=axis) assert_allclose(out, expected[axis], rtol=1e-7) else: - assert_allclose(out[:-1], expected[axis], rtol=1e-7) - assert_(np.isnan(out[-1])) + 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), @@ -2725,16 +2762,18 @@ def test_nan_omit(self, test_func, expected): stats.circstd]) def test_nan_omit_all(self, test_func): x = [np.nan, np.nan, np.nan, np.nan, np.nan] - assert_(np.isnan(test_func(x, nan_policy='omit'))) + 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): - 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) + 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], diff --git a/scipy/stats/tests/test_mstats_basic.py b/scipy/stats/tests/test_mstats_basic.py index c31c965e0e30..0fc2b7046737 100644 --- a/scipy/stats/tests/test_mstats_basic.py +++ b/scipy/stats/tests/test_mstats_basic.py @@ -21,6 +21,7 @@ from numpy.testing import suppress_warnings from scipy.stats import _mstats_basic 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): @@ -1101,7 +1102,10 @@ def test_vs_nonmasked(self): mfuncs = [mstats.normaltest, mstats.skewtest, mstats.kurtosistest] x = [1, 2, 3, 4] for func, mfunc in zip(funcs, mfuncs): - assert_raises(ValueError, func, x) + 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): diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 356a33915c7b..ef08fcb5bef2 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -34,7 +34,9 @@ from scipy.special import binom from scipy import optimize from .common_tests import check_named_results -from scipy.stats._axis_nan_policy import _broadcast_concatenate +from scipy.stats._axis_nan_policy import (_broadcast_concatenate, 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 _permutation_distribution_t, _chk_asarray, _moment from scipy._lib._util import AxisError from scipy.conftest import array_api_compatible, skip_xp_invalid_arg @@ -42,7 +44,6 @@ copy, is_numpy, is_torch, SCIPY_ARRAY_API, size as xp_size) - skip_xp_backends = pytest.mark.skip_xp_backends @@ -2438,11 +2439,11 @@ def test_empty(self): assert_equal(stats.scoreatpercentile([], [50, 99]), [np.nan, np.nan]) -@pytest.mark.filterwarnings('ignore::FutureWarning') class TestMode: def test_empty(self): - vals, counts = stats.mode([]) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + vals, counts = stats.mode([]) assert_equal(vals, np.array([])) assert_equal(counts, np.array([])) @@ -2491,7 +2492,8 @@ def test_mode_result_attributes(self): actual = stats.mode(data1) attributes = ('mode', 'count') check_named_results(actual, attributes) - actual2 = stats.mode(data2) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + actual2 = stats.mode(data2) check_named_results(actual2, attributes) def test_mode_nan(self): @@ -2617,15 +2619,18 @@ 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. - res = stats.mode([]) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.mode([]) ref = (np.nan, 0) assert_equal(res, ref) - res = stats.mode([np.nan], nan_policy='omit') + 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]] - res = stats.mode(a, axis=1, nan_policy='omit') + 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) @@ -2634,14 +2639,19 @@ def test_gh9955(self): assert_equal(res, ref) z = np.array([[], []]) - res = stats.mode(z, axis=1) + 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): - res = stats.mode(z, axis=None, keepdims=True) + 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 = np.mean(z, axis=None, keepdims=True) assert res[0].shape == res[1].shape == ref.shape == (1, 1, 1) @@ -2668,21 +2678,25 @@ class TestSEM: testcase = [1., 2., 3., 4.] scalar_testcase = 4. - def test_sem(self, xp): + 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)[()] - with suppress_warnings() as sup, np.errstate(invalid="ignore"): - # numpy - sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice") - # torch - sup.filter(UserWarning, "std*") - y = stats.sem(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 np.testing.suppress_warnings() as sup: + sup.filter(UserWarning) + sup.filter(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)) @@ -3042,9 +3056,10 @@ def test_api(self): stats.iqr(d, None, (50, 50), 'normal', 'raise', 'linear') stats.iqr(d, None, (25, 75), -0.4, 'omit', 'lower', True) - def test_empty(self): - assert_equal(stats.iqr([]), np.nan) - assert_equal(stats.iqr(np.arange(0)), np.nan) + @pytest.mark.parametrize('x', [[], np.arange(0)]) + def test_empty(self, x): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + assert_equal(stats.iqr(x), np.nan) def test_constant(self): # Constant array always gives 0 @@ -3335,10 +3350,7 @@ def test_moment(self, xp): y = stats.moment(testcase, [1.0, 2, 3, 4.0]) xp_assert_close(y, xp.asarray([0, 1.25, 0, 2.5625])) - # test empty input - message = r"Mean of empty slice\.|invalid value encountered.*" - with pytest.warns(RuntimeWarning, match=message): - np.mean([]) # lazy way of ignoring warnings + def test_cases(): y = stats.moment(xp.asarray([])) xp_assert_equal(y, xp.asarray(xp.nan)) y = stats.moment(xp.asarray([], dtype=xp.float32)) @@ -3350,6 +3362,16 @@ def test_moment(self, xp): y = stats.moment(xp.asarray([[]]), order=[0, 1], axis=0) xp_assert_equal(y, xp.empty((2, 0))) + # test empty input + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match="See documentation for..."): + test_cases() + else: + with np.testing.suppress_warnings() as sup: # needed by array_api_strict + sup.filter(RuntimeWarning, "Mean of empty slice.") + sup.filter(RuntimeWarning, "invalid value") + test_cases() + def test_nan_policy(self): x = np.arange(10.) x[9] = np.nan @@ -3435,13 +3457,20 @@ class SkewKurtosisTest: class TestSkew(SkewKurtosisTest): - def test_empty_1d(self): - # This is not essential behavior to maintain w/ array API - message = r"Mean of empty slice\.|invalid value encountered.*" - with pytest.warns(RuntimeWarning, match=message): - stats.skew([]) - with pytest.warns(RuntimeWarning, match=message): - stats.kurtosis([]) + @array_api_compatible + @pytest.mark.parametrize('stat_fun', [stats.skew, stats.kurtosis]) + def test_empty_1d(self, stat_fun, xp): + x = xp.asarray([]) + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stat_fun(x) + else: + with np.testing.suppress_warnings() as sup: + # array_api_strict produces these + sup.filter(RuntimeWarning, "Mean of empty slice") + sup.filter(RuntimeWarning, "invalid value encountered") + res = stat_fun(x) + xp_assert_equal(res, xp.asarray(xp.nan)) @skip_xp_backends('jax.numpy', reasons=["JAX arrays do not support item assignment"]) @@ -4760,11 +4789,13 @@ def test_some_code_paths(self): assert_raises(FloatingPointError, _count_paths_outside_method, 2000, 1000, 1, 1) - def test_argument_checking(self): - # Check that an empty array causes a ValueError - assert_raises(ValueError, stats.ks_2samp, [], [1]) - assert_raises(ValueError, stats.ks_2samp, [1], []) - assert_raises(ValueError, stats.ks_2samp, [], []) + @pytest.mark.parametrize('case', (([], [1]), ([1], []), ([], []))) + def test_argument_checking(self, case): + # Check that an empty array warns + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.ks_2samp(*case) + assert_equal(res.statistic, np.nan) + assert_equal(res.pvalue, np.nan) @pytest.mark.xslow def test_gh12218(self): @@ -4909,16 +4940,19 @@ def convert(t, p, alt): rvs1_2D[:, 20:30] = np.nan rvs2_2D[:, 15:25] = np.nan - tr, pr = stats.ttest_rel(rvs1_2D, rvs2_2D, 0, nan_policy='omit') + with pytest.warns(SmallSampleWarning, match=too_small_nd_omit): + tr, pr = stats.ttest_rel(rvs1_2D, rvs2_2D, 0, nan_policy='omit') - t, p = stats.ttest_rel(rvs1_2D, rvs2_2D, 0, nan_policy='omit', - alternative='less') + 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) - t, p = stats.ttest_rel(rvs1_2D, rvs2_2D, 0, nan_policy='omit', - alternative='greater') + 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) @@ -4948,7 +4982,8 @@ def test_ttest_rel_nan_2nd_arg(): def test_ttest_rel_empty_1d_returns_nan(): # Two empty inputs should return a TtestResult containing nan # for both values. - result = stats.ttest_rel([], []) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + result = stats.ttest_rel([], []) assert isinstance(result, stats._stats_py.TtestResult) assert_equal(result, (np.nan, np.nan)) @@ -4961,7 +4996,10 @@ def test_ttest_rel_axis_size_zero(b, expected_shape): # The results should be arrays containing nan with shape # given by the broadcast nonaxis dimensions. a = np.empty((3, 1, 0)) - result = stats.ttest_rel(a, b, axis=-1) + with np.testing.suppress_warnings() as sup: + # first case should warn, second shouldn't? + sup.filter(SmallSampleWarning, too_small_nd_not_omit) + result = stats.ttest_rel(a, b, axis=-1) assert isinstance(result, stats._stats_py.TtestResult) expected_value = np.full(expected_shape, fill_value=np.nan) assert_equal(result.statistic, expected_value) @@ -5823,7 +5861,8 @@ def test_ttest_ind_nan_2nd_arg(): def test_ttest_ind_empty_1d_returns_nan(): # Two empty inputs should return a TtestResult containing nan # for both values. - result = stats.ttest_ind([], []) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + result = stats.ttest_ind([], []) assert isinstance(result, stats._stats_py.TtestResult) assert_equal(result, (np.nan, np.nan)) @@ -5836,7 +5875,10 @@ def test_ttest_ind_axis_size_zero(b, expected_shape): # The results should be arrays containing nan with shape # given by the broadcast nonaxis dimensions. a = np.empty((3, 1, 0)) - result = stats.ttest_ind(a, b, axis=-1) + with np.testing.suppress_warnings() as sup: + # first case should warn, second shouldn't? + sup.filter(SmallSampleWarning, too_small_nd_not_omit) + result = stats.ttest_ind(a, b, axis=-1) assert isinstance(result, stats._stats_py.TtestResult) expected_value = np.full(expected_shape, fill_value=np.nan) assert_equal(result.statistic, expected_value) @@ -6189,11 +6231,19 @@ def test_describe_empty(self, xp): @array_api_compatible class NormalityTests: def test_too_small(self, xp): - # 1D sample has too few observations -> error + # 1D sample has too few observations -> warning/error test_fun = getattr(stats, self.test_name) - message = "...requires at least..." - with pytest.raises(ValueError, match=message): - test_fun(xp.asarray(4.)) + x = xp.asarray(4.) + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = test_fun(x) + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + else: + message = "...requires at least..." + with pytest.raises(ValueError, match=message): + test_fun(x) @pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater']) def test_against_R(self, alternative, xp): @@ -6251,9 +6301,19 @@ def test_skewtest_too_few_observations(self, xp): # Regression test for ticket #1492. # skewtest requires at least 8 observations; 7 should raise a ValueError. stats.skewtest(xp.arange(8.0)) - message = '`skewtest` requires at least 8 observations...' - with pytest.raises(ValueError, match=message): - stats.skewtest(xp.arange(7.0)) + + x = xp.arange(7.0) + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.skewtest(x) + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + else: + message = "`skewtest` requires at least 8 observations" + with pytest.raises(ValueError, match=message): + stats.skewtest(x) + class TestKurtosisTest(NormalityTests): test_name = 'kurtosistest' @@ -6286,9 +6346,17 @@ def test_kurtosistest_too_few_observations(self, xp): with pytest.warns(UserWarning, match=message): stats.kurtosistest(xp.arange(19.0)) - message = '`kurtosistest` requires at least 5 observations...' - with pytest.raises(ValueError, match=message): - stats.kurtosistest(xp.arange(4.0)) + x = xp.arange(4.0) + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.skewtest(x) + NaN = xp.asarray(xp.nan) + xp_assert_equal(res.statistic, NaN) + xp_assert_equal(res.pvalue, NaN) + else: + message = "`kurtosistest` requires at least 5 observations" + with pytest.raises(ValueError, match=message): + stats.kurtosistest(x) class TestNormalTest(NormalityTests): @@ -6350,9 +6418,16 @@ def test_jarque_bera_array_like(self): def test_jarque_bera_size(self, xp): x = xp.asarray([]) - message = "At least one observation is required." - with pytest.raises(ValueError, match=message): - stats.jarque_bera(x) + if is_numpy(xp): + 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) + else: + message = "At least one observation is required." + with pytest.raises(ValueError, match=message): + res = stats.jarque_bera(x) def test_axis(self, xp): rng = np.random.RandomState(seed=122398129) @@ -7265,19 +7340,15 @@ def test_compare_dtypes(self): assert (res_int16.statistic == res_int32.statistic == res_unit8.statistic == res_float64.statistic) + @pytest.mark.parametrize('case',[([1, 2], []), ([1, 2], 2), ([1, 2], [2])]) + def test_too_small_inputs(self, case): + # input array is of size zero or too small + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.alexandergovern(*case) + assert_equal(res.statistic, np.nan) + assert_equal(res.pvalue, np.nan) + def test_bad_inputs(self): - # input array is of size zero - with assert_raises(ValueError, match="Input sample size must be" - " greater than one."): - stats.alexandergovern([1, 2], []) - # input is a singular non list element - with assert_raises(ValueError, match="Input sample size must be" - " greater than one."): - stats.alexandergovern([1, 2], 2) - # input list is of size 1 - with assert_raises(ValueError, match="Input sample size must be" - " greater than one."): - stats.alexandergovern([1, 2], [2]) # inputs are not finite (infinity) with assert_raises(ValueError, match="Input samples must be finite."): stats.alexandergovern([1, 2], [np.inf, np.inf]) @@ -7628,14 +7699,12 @@ def test_3d_inputs(self): def test_length0_1d_error(self): # Require at least one value in each group. - msg = 'at least one input has length 0' - with pytest.warns(stats.DegenerateDataWarning, match=msg): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): result = stats.f_oneway([1, 2, 3], [], [4, 5, 6, 7]) assert_equal(result, (np.nan, np.nan)) def test_length0_2d_error(self): - msg = 'at least one input has length 0' - with pytest.warns(stats.DegenerateDataWarning, match=msg): + with pytest.warns(SmallSampleWarning, match=too_small_nd_not_omit): ncols = 3 a = np.ones((4, ncols)) b = np.ones((0, ncols)) @@ -7646,14 +7715,14 @@ def test_length0_2d_error(self): assert_equal(p, nans) def test_all_length_one(self): - msg = 'all input arrays have length 1.' - with pytest.warns(stats.DegenerateDataWarning, match=msg): + with pytest.warns(SmallSampleWarning): result = stats.f_oneway([10], [11], [12], [13]) assert_equal(result, (np.nan, np.nan)) @pytest.mark.parametrize('args', [(), ([1, 2, 3],)]) def test_too_few_inputs(self, args): - with assert_raises(TypeError): + message = "At least two samples are required..." + with assert_raises(TypeError, match=message): stats.f_oneway(*args) def test_axis_error(self): @@ -7727,7 +7796,8 @@ def test_empty(self): x = [1, 1, 1] y = [2, 2, 2] z = [] - assert_equal(stats.kruskal(x, y, z), (np.nan, np.nan)) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + assert_equal(stats.kruskal(x, y, z), (np.nan, np.nan)) def test_kruskal_result_attributes(self): x = [1, 3, 5, 7, 9] @@ -8301,17 +8371,15 @@ def test_brunnermunzel_distribution_error(self): distribution, nan_policy) - def test_brunnermunzel_empty_imput(self): - u1, p1 = stats.brunnermunzel(self.X, []) - u2, p2 = stats.brunnermunzel([], self.Y) - u3, p3 = stats.brunnermunzel([], []) - - assert_equal(u1, np.nan) - assert_equal(p1, np.nan) - assert_equal(u2, np.nan) - assert_equal(p2, np.nan) - assert_equal(u3, np.nan) - assert_equal(p3, np.nan) + @pytest.mark.parametrize("kwarg_update", [{'y': []}, {'x': []}, + {'x': [], 'y': []}]) + def test_brunnermunzel_empty_imput(self, kwarg_update): + kwargs = {'x': self.X, 'y': self.Y} + kwargs.update(kwarg_update) + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + statistic, pvalue = stats.brunnermunzel(**kwargs) + assert_equal(statistic, np.nan) + assert_equal(pvalue, np.nan) def test_brunnermunzel_nan_input_propagate(self): X = [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1, np.nan] diff --git a/scipy/stats/tests/test_variation.py b/scipy/stats/tests/test_variation.py index e13287fca916..229237090916 100644 --- a/scipy/stats/tests/test_variation.py +++ b/scipy/stats/tests/test_variation.py @@ -7,7 +7,9 @@ from scipy.stats import variation from scipy._lib._util import AxisError from scipy.conftest import array_api_compatible -from scipy._lib._array_api import xp_assert_equal, xp_assert_close +from scipy._lib._array_api import xp_assert_equal, xp_assert_close, is_numpy +from scipy.stats._axis_nan_policy import (too_small_nd_omit, too_small_nd_not_omit, + SmallSampleWarning) pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends")] skip_xp_backends = pytest.mark.skip_xp_backends @@ -72,7 +74,11 @@ def test_keepdims(self, xp): (1, np.full((5, 1), fill_value=np.nan))]) def test_keepdims_size0(self, axis, expected, xp): x = xp.zeros((5, 0)) - y = variation(x, axis=axis, keepdims=True) + 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, expected) @skip_xp_backends(np_only=True, @@ -117,14 +123,11 @@ def test_mean_zero(self, xp): y2 = variation(x2, axis=1) xp_assert_equal(y2, xp.asarray([xp.inf, xp.inf])) - @pytest.mark.parametrize('x', [[0.]*5, [], [1, 2, np.inf, 9]]) + @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. - with suppress_warnings() as sup: - # torch - sup.filter(UserWarning, "std*") - y = variation(x) + y = variation(x) xp_assert_equal(y, xp.asarray(xp.nan, dtype=x.dtype)) @pytest.mark.parametrize('axis, expected', @@ -134,7 +137,14 @@ def test_2d_size_zero_with_axis(self, axis, expected, xp): with suppress_warnings() as sup: # torch sup.filter(UserWarning, "std*") - y = variation(x, axis=axis) + if axis != 0: + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match="See documentation..."): + y = variation(x, axis=axis) + else: + y = variation(x, axis=axis) + else: + y = variation(x, axis=axis) xp_assert_equal(y, xp.asarray(expected)) def test_neg_inf(self, xp): @@ -158,7 +168,11 @@ def test_combined_edge_cases(self, nan_policy, xp): x = xp.array([[0, 10, xp.nan, 1], [0, -5, xp.nan, 2], [0, -5, xp.nan, 3]]) - y = variation(x, axis=0, nan_policy=nan_policy) + 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.nan, xp.inf, xp.nan, math.sqrt(2/3)/2]) @skip_xp_backends(np_only=True, @@ -173,7 +187,7 @@ 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. + # 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], @@ -182,7 +196,8 @@ def test_more_nan_policy_omit_tests(self, ddof, expected, xp): [nan, nan, nan, nan], [3.0, 3.0, 3.0, 3.0], [0.0, 0.0, 0.0, 0.0]]) - v = variation(x, axis=1, ddof=ddof, nan_policy='omit') + 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(np_only=True, From 5382a029cfca4ad1c2782fe804b5f322407a1287 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 25 May 2024 04:26:17 -0700 Subject: [PATCH 282/500] MAINT: stats.linregress: remove out of _stats_mstats_common --- scipy/stats/_mstats_basic.py | 15 +- scipy/stats/_stats_mstats_common.py | 195 +----------------------- scipy/stats/_stats_py.py | 198 ++++++++++++++++++++++++- scipy/stats/tests/test_mstats_basic.py | 4 +- scipy/stats/tests/test_stats.py | 9 +- 5 files changed, 211 insertions(+), 210 deletions(-) diff --git a/scipy/stats/_mstats_basic.py b/scipy/stats/_mstats_basic.py index 0ce0d9abb955..9d07c7f27428 100644 --- a/scipy/stats/_mstats_basic.py +++ b/scipy/stats/_mstats_basic.py @@ -45,11 +45,10 @@ 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 ._stats_mstats_common import ( _find_repeats, - linregress as stats_linregress, - LinregressResult as stats_LinregressResult, theilslopes as stats_theilslopes, siegelslopes as stats_siegelslopes ) @@ -1174,15 +1173,15 @@ def linregress(x, y=None): x = ma.array(x, mask=m) y = ma.array(y, mask=m) if np.any(~m): - result = stats_linregress(x.data[~m], y.data[~m]) + result = _stats_py.linregress(x.data[~m], y.data[~m]) else: # All data is masked - result = stats_LinregressResult(slope=None, intercept=None, - rvalue=None, pvalue=None, - stderr=None, - intercept_stderr=None) + result = _stats_py.LinregressResult(slope=None, intercept=None, + rvalue=None, pvalue=None, + stderr=None, + intercept_stderr=None) else: - result = stats_linregress(x.data, y.data) + result = _stats_py.linregress(x.data, y.data) return result diff --git a/scipy/stats/_stats_mstats_common.py b/scipy/stats/_stats_mstats_common.py index e4ffd9ca58b8..6900eba1fa61 100644 --- a/scipy/stats/_stats_mstats_common.py +++ b/scipy/stats/_stats_mstats_common.py @@ -3,15 +3,10 @@ from . import distributions from .._lib._bunch import _make_tuple_bunch from ._stats_pythran import siegelslopes as siegelslopes_pythran -from scipy.stats import _stats_py -__all__ = ['_find_repeats', 'linregress', 'theilslopes', 'siegelslopes'] +__all__ = ['_find_repeats', 'theilslopes', 'siegelslopes'] # This is not a namedtuple for backwards compatibility. See PR #12983 -LinregressResult = _make_tuple_bunch('LinregressResult', - ['slope', 'intercept', 'rvalue', - 'pvalue', 'stderr'], - extra_field_names=['intercept_stderr']) TheilslopesResult = _make_tuple_bunch('TheilslopesResult', ['slope', 'intercept', 'low_slope', 'high_slope']) @@ -19,194 +14,6 @@ ['slope', 'intercept']) -def linregress(x, y=None, alternative='two-sided'): - """ - 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])``. - 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 - - 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 - - """ - TINY = 1.0e-20 - if y is None: # x is a (2, N) or (N, 2) shaped array_like - x = np.asarray(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); provided shape " - f"was {x.shape}.") - else: - x = np.asarray(x) - y = np.asarray(y) - - if x.size == 0 or y.size == 0: - raise ValueError("Inputs must not be empty.") - - if np.amax(x) == np.amin(x) and len(x) > 1: - raise ValueError("Cannot calculate a linear regression " - "if all x values are identical") - - n = len(x) - xmean = np.mean(x, None) - ymean = np.mean(y, None) - - # Average sums of square differences from the mean - # ssxm = mean( (x-mean(x))^2 ) - # ssxym = mean( (x-mean(x)) * (y-mean(y)) ) - ssxm, ssxym, _, ssym = np.cov(x, y, bias=1).flat - - # R-value - # r = ssxym / sqrt( ssxm * ssym ) - if ssxm == 0.0 or ssym == 0.0: - # If the denominator was going to be 0 - r = 0.0 - else: - r = ssxym / np.sqrt(ssxm * ssym) - # Test for numerical error propagation (make sure -1 < r < 1) - if r > 1.0: - r = 1.0 - elif r < -1.0: - r = -1.0 - - slope = ssxym / ssxm - intercept = ymean - slope*xmean - if n == 2: - # handle case when only two points are passed in - if y[0] == y[1]: - prob = 1.0 - else: - prob = 0.0 - slope_stderr = 0.0 - intercept_stderr = 0.0 - else: - 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 * np.sqrt(df / ((1.0 - r + TINY)*(1.0 + r + TINY))) - prob = _stats_py._get_pvalue(t, distributions.t(df), alternative) - - slope_stderr = np.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 * np.sqrt(ssxm + xmean**2) - - return LinregressResult(slope=slope, intercept=intercept, rvalue=r, - pvalue=prob, stderr=slope_stderr, - intercept_stderr=intercept_stderr) - - def theilslopes(y, x=None, alpha=0.95, method='separate'): r""" Computes the Theil-Sen estimator for a set of points (x, y). diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 05feae9d57dd..5496a3240e9e 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -48,8 +48,8 @@ from scipy import linalg # noqa: F401 from . import distributions from . import _mstats_basic as mstats_basic -from ._stats_mstats_common import (_find_repeats, linregress, theilslopes, - siegelslopes) + +from ._stats_mstats_common import _find_repeats, theilslopes, siegelslopes from ._stats import _kendall_dis, _toint64, _weightedrankedtau from dataclasses import dataclass, field @@ -10675,6 +10675,200 @@ def first_order(t): return res.root +LinregressResult = _make_tuple_bunch('LinregressResult', + ['slope', 'intercept', 'rvalue', + 'pvalue', 'stderr'], + extra_field_names=['intercept_stderr']) + + +def linregress(x, y=None, alternative='two-sided'): + """ + 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])``. + 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 + + 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 + + """ + TINY = 1.0e-20 + if y is None: # x is a (2, N) or (N, 2) shaped array_like + x = np.asarray(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); provided shape " + f"was {x.shape}.") + else: + x = np.asarray(x) + y = np.asarray(y) + + if x.size == 0 or y.size == 0: + raise ValueError("Inputs must not be empty.") + + if np.amax(x) == np.amin(x) and len(x) > 1: + raise ValueError("Cannot calculate a linear regression " + "if all x values are identical") + + n = len(x) + xmean = np.mean(x, None) + ymean = np.mean(y, None) + + # Average sums of square differences from the mean + # ssxm = mean( (x-mean(x))^2 ) + # ssxym = mean( (x-mean(x)) * (y-mean(y)) ) + ssxm, ssxym, _, ssym = np.cov(x, y, bias=1).flat + + # R-value + # r = ssxym / sqrt( ssxm * ssym ) + if ssxm == 0.0 or ssym == 0.0: + # If the denominator was going to be 0 + r = 0.0 + else: + r = ssxym / np.sqrt(ssxm * ssym) + # Test for numerical error propagation (make sure -1 < r < 1) + if r > 1.0: + r = 1.0 + elif r < -1.0: + r = -1.0 + + slope = ssxym / ssxm + intercept = ymean - slope*xmean + if n == 2: + # handle case when only two points are passed in + if y[0] == y[1]: + prob = 1.0 + else: + prob = 0.0 + slope_stderr = 0.0 + intercept_stderr = 0.0 + else: + 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 * np.sqrt(df / ((1.0 - r + TINY)*(1.0 + r + TINY))) + prob = _get_pvalue(t, distributions.t(df), alternative) + + slope_stderr = np.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 * np.sqrt(ssxm + xmean**2) + + return LinregressResult(slope=slope, intercept=intercept, rvalue=r, + pvalue=prob, stderr=slope_stderr, + intercept_stderr=intercept_stderr) + + class _SimpleNormal: # A very simple, array-API compatible normal distribution for use in # hypothesis tests. May be replaced by new infrastructure Normal diff --git a/scipy/stats/tests/test_mstats_basic.py b/scipy/stats/tests/test_mstats_basic.py index 0fc2b7046737..74701cdb8b62 100644 --- a/scipy/stats/tests/test_mstats_basic.py +++ b/scipy/stats/tests/test_mstats_basic.py @@ -19,7 +19,7 @@ assert_array_almost_equal_nulp, assert_, assert_allclose, assert_array_equal) from numpy.testing import suppress_warnings -from scipy.stats import _mstats_basic +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 @@ -917,7 +917,7 @@ def test_regress_simple(): result = mstats.linregress(x, y) # Result is of a correct class and with correct fields - lr = stats._stats_mstats_common.LinregressResult + lr = _stats_py.LinregressResult assert_(isinstance(result, lr)) attributes = ('slope', 'intercept', 'rvalue', 'pvalue', 'stderr') check_named_results(result, attributes, ma=True) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index ef08fcb5bef2..818fc91c5820 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -37,7 +37,8 @@ from scipy.stats._axis_nan_policy import (_broadcast_concatenate, 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 _permutation_distribution_t, _chk_asarray, _moment +from scipy.stats._stats_py import (_permutation_distribution_t, _chk_asarray, _moment, + LinregressResult) from scipy._lib._util import AxisError from scipy.conftest import array_api_compatible, skip_xp_invalid_arg from scipy._lib._array_api import (xp_assert_close, xp_assert_equal, array_namespace, @@ -2048,7 +2049,7 @@ def test_regress_simple(self): y += np.sin(np.linspace(0, 20, 100)) result = stats.linregress(x, y) - lr = stats._stats_mstats_common.LinregressResult + lr = LinregressResult assert_(isinstance(result, lr)) assert_almost_equal(result.stderr, 2.3957814497838803e-3) @@ -2166,7 +2167,7 @@ def test_linregress_result_attributes(self): result = stats.linregress(x, y) # Result is of a correct class - lr = stats._stats_mstats_common.LinregressResult + lr = LinregressResult assert_(isinstance(result, lr)) # LinregressResult elements have correct names @@ -2244,7 +2245,7 @@ def test_nan_input(self): result = stats.linregress(x, x) # Make sure the result still comes back as `LinregressResult` - lr = stats._stats_mstats_common.LinregressResult + lr = LinregressResult assert_(isinstance(result, lr)) assert_array_equal(result, (np.nan,)*5) assert_equal(result.intercept_stderr, np.nan) From 37de595539ce9533f926306a36c9a650efa39552 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 25 May 2024 04:45:20 -0700 Subject: [PATCH 283/500] DEP: stats.linregress: deprecate one-arg use --- scipy/stats/_stats_py.py | 10 ++++++++++ scipy/stats/tests/test_stats.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 5496a3240e9e..6623431729fa 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -10694,6 +10694,11 @@ def linregress(x, y=None, alternative='two-sided'): 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])``. + + .. deprecated:: 1.14.0 + Inference of the two sets of measurements from a single argument `x` + is deprecated will result in an error in SciPy 1.16.0; the sets + must be specified separately as `x` and `y`. alternative : {'two-sided', 'less', 'greater'}, optional Defines the alternative hypothesis. Default is 'two-sided'. The following options are available: @@ -10796,6 +10801,11 @@ def linregress(x, y=None, alternative='two-sided'): """ TINY = 1.0e-20 if y is None: # x is a (2, N) or (N, 2) shaped array_like + message = ('Inference of the two sets of measurements from a single "' + 'argument `x` is deprecated will result in an error in "' + 'SciPy 1.16.0; the sets must be specified separately as "' + '`x` and `y`.') + warnings.warn(message, DeprecationWarning, stacklevel=2) x = np.asarray(x) if x.shape[0] == 2: x, y = x diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 818fc91c5820..6d91e108ad25 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -2002,6 +2002,13 @@ def test_empty_result(self): class TestRegression: + def test_one_arg_deprecation(self): + x = np.arange(20).reshape((2, 10)) + message = "Inference of the two sets..." + with pytest.deprecated_call(match=message): + stats.linregress(x) + stats.linregress(x[0], x[1]) + def test_linregressBIGX(self): # W.II.F. Regress BIG on X. result = stats.linregress(X, BIG) @@ -2093,6 +2100,9 @@ def test_regress_against_R(self): assert_allclose(res.stderr, 0.0519051424731) assert_allclose(res.intercept_stderr, 8.0490133029927) + # TODO: remove this test once single-arg support is dropped; + # deprecation warning tested in `test_one_arg_deprecation` + @pytest.mark.filterwarnings('ignore::DeprecationWarning') def test_regress_simple_onearg_rows(self): # Regress a line w sinusoidal noise, # with a single input of shape (2, N) @@ -2105,6 +2115,9 @@ def test_regress_simple_onearg_rows(self): assert_almost_equal(result.stderr, 2.3957814497838803e-3) assert_almost_equal(result.intercept_stderr, 1.3866936078570702e-1) + # TODO: remove this test once single-arg support is dropped; + # deprecation warning tested in `test_one_arg_deprecation` + @pytest.mark.filterwarnings('ignore::DeprecationWarning') def test_regress_simple_onearg_cols(self): x = np.linspace(0, 100, 100) y = 0.2 * np.linspace(0, 100, 100) + 10 @@ -2115,6 +2128,9 @@ def test_regress_simple_onearg_cols(self): assert_almost_equal(result.stderr, 2.3957814497838803e-3) assert_almost_equal(result.intercept_stderr, 1.3866936078570702e-1) + # TODO: remove this test once single-arg support is dropped; + # deprecation warning tested in `test_one_arg_deprecation` + @pytest.mark.filterwarnings('ignore::DeprecationWarning') def test_regress_shape_error(self): # Check that a single input argument to linregress with wrong shape # results in a ValueError. From ed65b675496cb38bf021858d56e32a7d740ca2ea Mon Sep 17 00:00:00 2001 From: Christian Lorentzen Date: Tue, 28 May 2024 11:17:20 +0200 Subject: [PATCH 284/500] MAINT: optimize.isotonic_regression: remove unnecessary copies (#20582) reviewed at https://github.com/scipy/scipy/pull/20582 --- scipy/optimize/_isotonic.py | 12 ++++++------ scipy/optimize/tests/test_isotonic_regression.py | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/scipy/optimize/_isotonic.py b/scipy/optimize/_isotonic.py index 929481e02261..bbbce625a7e5 100644 --- a/scipy/optimize/_isotonic.py +++ b/scipy/optimize/_isotonic.py @@ -121,11 +121,13 @@ class of strictly consistent scoring functions for the mean, see [2]_ input y of length 1000, the minimizer takes about 4 seconds, while ``isotonic_regression`` takes about 200 microseconds. """ - yarr = np.asarray(y) # Check yarr.ndim == 1 is implicit (pybind11) in pava. + yarr = np.atleast_1d(y) # Check yarr.ndim == 1 is implicit (pybind11) in pava. + order = slice(None) if increasing else slice(None, None, -1) + x = np.array(yarr[order], order="C", dtype=np.float64, copy=True) if weights is None: - warr = np.ones_like(yarr) + wx = np.ones_like(yarr, dtype=np.float64) else: - warr = np.asarray(weights) + warr = np.atleast_1d(weights) if not (yarr.ndim == warr.ndim == 1 and yarr.shape[0] == warr.shape[0]): raise ValueError( @@ -134,9 +136,7 @@ class of strictly consistent scoring functions for the mean, see [2]_ if np.any(warr <= 0): raise ValueError("Weights w must be strictly positive.") - order = slice(None) if increasing else slice(None, None, -1) - x = np.array(yarr[order], order="C", dtype=np.float64, copy=True) - wx = np.array(warr[order], order="C", dtype=np.float64, copy=True) + wx = np.array(warr[order], order="C", dtype=np.float64, copy=True) n = x.shape[0] r = np.full(shape=n + 1, fill_value=-1, dtype=np.intp) x, wx, r, b = pava(x, wx, r) diff --git a/scipy/optimize/tests/test_isotonic_regression.py b/scipy/optimize/tests/test_isotonic_regression.py index ac574b34b1a1..b49c56db5b44 100644 --- a/scipy/optimize/tests/test_isotonic_regression.py +++ b/scipy/optimize/tests/test_isotonic_regression.py @@ -16,7 +16,9 @@ class TestIsotonicRegression: "Input arrays y and w must have one dimension of equal length"), ([0, 1], [1], "Input arrays y and w must have one dimension of equal length"), - (1, 2, + (1, [1, 2], + "Input arrays y and w must have one dimension of equal length"), + ([1, 2], 1, "Input arrays y and w must have one dimension of equal length"), ([0, 1], [0, 1], "Weights w must be strictly positive"), From 03ae516fce839759f7c4fb2458fd8f8ea05f99ec Mon Sep 17 00:00:00 2001 From: fancidev Date: Tue, 28 May 2024 19:40:53 +0800 Subject: [PATCH 285/500] circstd: work around pytorch and array_api_strict limitations. --- scipy/stats/_morestats.py | 2 +- scipy/stats/tests/test_morestats.py | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index e351798c7b10..f7a8ec403133 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -4659,7 +4659,7 @@ def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, # hypotenuse can go slightly above 1 due to rounding errors R = xp_minimum(xp.asarray(1.), hypotenuse) # [1] (2.2.4) - res = (-2*xp.log(R))**0.5 # do not use sqrt to avoid -0.0 if R==1 + 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 diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index bbad4ee51d4d..c98576f2291a 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2,6 +2,7 @@ # # Further enhancements and tests added by numerous SciPy developers. # +import math import warnings import sys from functools import partial @@ -2674,14 +2675,10 @@ def test_circfuncs_uint8(self, xp): xp_assert_close(stats.circvar(x, high=180), xp.asarray(0.2339555554617)) xp_assert_close(stats.circstd(x, high=180), xp.asarray(20.91551378)) - @skip_xp_backends("array_api_strict", "torch", reasons=[ - "array_api_strict does not yet support xp.signbit", - "pytorch's pow is non-compliant and may return -0", - ]) def test_circstd_zero(self, xp): # circstd() of a single number should return positive zero. y = stats.circstd(xp.asarray([0])) - xp_assert_equal(xp.signbit(y), xp.asarray(False)) + assert math.copysign(1.0, y) == 1.0 class TestCircFuncsNanPolicy: From 69cd453280b156806611926a2f2eff0819cc05a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 28 May 2024 07:56:39 -0500 Subject: [PATCH 286/500] ENH: spatial: serialize concurrent calls to QHull (#20619) This PR introduces a Mutex mechanism to serialize QHull calls, thus preventing potential segfaults or undefined behaviour, since it is not thread-safe. See http://www.qhull.org/html/qh-code.htm#reentrant Depends on PR 20611 Closes issue 8856 --- scipy/interpolate/tests/test_interpnd.py | 48 ++ scipy/spatial/_qhull.pyx | 772 ++++++++++++----------- 2 files changed, 466 insertions(+), 354 deletions(-) diff --git a/scipy/interpolate/tests/test_interpnd.py b/scipy/interpolate/tests/test_interpnd.py index e77bd1119a1a..f1a2acc30eee 100644 --- a/scipy/interpolate/tests/test_interpnd.py +++ b/scipy/interpolate/tests/test_interpnd.py @@ -1,4 +1,5 @@ import os +import sys import numpy as np from numpy.testing import (assert_equal, assert_allclose, assert_almost_equal, @@ -6,10 +7,14 @@ from pytest import raises as assert_raises import pytest +from scipy._lib._testutils import check_free_memory import scipy.interpolate.interpnd as interpnd import scipy.spatial._qhull as qhull import pickle +import threading + +_IS_32BIT = (sys.maxsize < 2**32) def data_file(basename): @@ -166,6 +171,49 @@ def test_pickle(self): assert_almost_equal(ip(0.5, 0.5), ip2(0.5, 0.5)) + @pytest.mark.slow + @pytest.mark.skipif(_IS_32BIT, reason='it fails on 32-bit') + def test_threading(self): + # This test was taken from issue 8856 + # https://github.com/scipy/scipy/issues/8856 + check_free_memory(10000) + + r_ticks = np.arange(0, 4200, 10) + phi_ticks = np.arange(0, 4200, 10) + r_grid, phi_grid = np.meshgrid(r_ticks, phi_ticks) + + def do_interp(interpolator, slice_rows, slice_cols): + grid_x, grid_y = np.mgrid[slice_rows, slice_cols] + res = interpolator((grid_x, grid_y)) + return res + + points = np.vstack((r_grid.ravel(), phi_grid.ravel())).T + values = (r_grid * phi_grid).ravel() + interpolator = interpnd.LinearNDInterpolator(points, values) + + worker_thread_1 = threading.Thread( + target=do_interp, + args=(interpolator, slice(0, 2100), slice(0, 2100))) + worker_thread_2 = threading.Thread( + target=do_interp, + args=(interpolator, slice(2100, 4200), slice(0, 2100))) + worker_thread_3 = threading.Thread( + target=do_interp, + args=(interpolator, slice(0, 2100), slice(2100, 4200))) + worker_thread_4 = threading.Thread( + target=do_interp, + args=(interpolator, slice(2100, 4200), slice(2100, 4200))) + + worker_thread_1.start() + worker_thread_2.start() + worker_thread_3.start() + worker_thread_4.start() + + worker_thread_1.join() + worker_thread_2.join() + worker_thread_3.join() + worker_thread_4.join() + class TestEstimateGradients2DGlobal: def test_smoketest(self): diff --git a/scipy/spatial/_qhull.pyx b/scipy/spatial/_qhull.pyx index 143315aba74e..c51bc95ebef5 100644 --- a/scipy/spatial/_qhull.pyx +++ b/scipy/spatial/_qhull.pyx @@ -16,6 +16,10 @@ Wrappers for Qhull triangulation, plus some additional N-D geometry utilities import numpy as np cimport numpy as np cimport cython +from cpython.pythread cimport ( + PyThread_type_lock, PyThread_allocate_lock, PyThread_free_lock, + PyThread_acquire_lock, PyThread_release_lock) + from . cimport _qhull from . cimport setlist from libc cimport stdlib @@ -242,6 +246,7 @@ cdef class _Qhull: cdef int _nridges cdef np.ndarray _ridge_equations + cdef PyThread_type_lock _lock @cython.final def __init__(self, @@ -254,6 +259,10 @@ cdef class _Qhull: np.ndarray[np.double_t, ndim=1] interior_point=None): cdef int exitcode + self._lock = PyThread_allocate_lock() + if self._lock == NULL: + raise MemoryError("thread lock allocation failed") + self._qh = NULL self._messages = MessageStream() @@ -342,6 +351,13 @@ cdef class _Qhull: self.close() raise QhullError(msg) + cdef void acquire_lock(self): + if not PyThread_acquire_lock(self._lock, 0): + PyThread_acquire_lock(self._lock, 1) + + cdef void release_lock(self): + PyThread_release_lock(self._lock) + def check_active(self): if self._qh == NULL: raise RuntimeError("Qhull instance is closed") @@ -350,19 +366,25 @@ cdef class _Qhull: def __dealloc__(self): cdef int curlong, totlong - if self._qh != NULL: - qh_freeqhull(self._qh, qh_ALL) - qh_memfreeshort(self._qh, &curlong, &totlong) - stdlib.free(self._qh) - self._qh = NULL - - if curlong != 0 or totlong != 0: - raise QhullError( - "qhull: did not free %d bytes (%d pieces)" % - (totlong, curlong)) + self.acquire_lock() + try: + if self._qh != NULL: + qh_freeqhull(self._qh, qh_ALL) + qh_memfreeshort(self._qh, &curlong, &totlong) + stdlib.free(self._qh) + self._qh = NULL + + if curlong != 0 or totlong != 0: + raise QhullError( + "qhull: did not free %d bytes (%d pieces)" % + (totlong, curlong)) + + if self._messages is not None: + self._messages.close() + finally: + self.release_lock() - if self._messages is not None: - self._messages.close() + PyThread_free_lock(self._lock) @cython.final def close(self): @@ -377,19 +399,23 @@ cdef class _Qhull: cdef int curlong, totlong - if self._qh != NULL: - qh_freeqhull(self._qh, qh_ALL) - qh_memfreeshort(self._qh, &curlong, &totlong) - stdlib.free(self._qh) - self._qh = NULL - - if curlong != 0 or totlong != 0: - raise QhullError( - "qhull: did not free %d bytes (%d pieces)" % - (totlong, curlong)) - - if self._messages is not None: - self._messages.close() + self.acquire_lock() + try: + if self._qh != NULL: + qh_freeqhull(self._qh, qh_ALL) + qh_memfreeshort(self._qh, &curlong, &totlong) + stdlib.free(self._qh) + self._qh = NULL + + if curlong != 0 or totlong != 0: + raise QhullError( + "qhull: did not free %d bytes (%d pieces)" % + (totlong, curlong)) + + if self._messages is not None: + self._messages.close() + finally: + self.release_lock() @cython.final def get_points(self): @@ -409,107 +435,124 @@ cdef class _Qhull: cdef boolT isoutside cdef np.ndarray arr - self.check_active() - - points = np.asarray(points) - if points.ndim!=2 or points.shape[1] != self._point_arrays[0].shape[1]: - raise ValueError("invalid size for new points array") - if points.size == 0: - return - - if self._is_delaunay: - arr = np.empty((points.shape[0], self.ndim+1), dtype=np.double) - arr[:,:-1] = points - elif self._is_halfspaces: - #Store the halfspaces in _points and the dual points in _dual_points later - self._point_arrays.append(np.array(points, copy=True)) - dists = points[:, :-1].dot(interior_point)+points[:, -1] - arr = np.array(-points[:, :-1]/dists, dtype=np.double, order="C", copy=True) - else: - arr = np.array(points, dtype=np.double, order="C", copy=True) - - self._messages.clear() + self.acquire_lock() try: - # nonlocal error handling - exitcode = setjmp(self._qh[0].errexit) - if exitcode != 0: - raise QhullError(self._messages.get()) - self._qh[0].NOerrexit = 0 + self.check_active() + + points = np.asarray(points) + if points.ndim!=2 or points.shape[1] != self._point_arrays[0].shape[1]: + raise ValueError("invalid size for new points array") + if points.size == 0: + return - # add points to triangulation if self._is_delaunay: - # lift to paraboloid - qh_setdelaunay(self._qh, arr.shape[1], arr.shape[0], arr.data) + arr = np.empty((points.shape[0], self.ndim+1), dtype=np.double) + arr[:,:-1] = points + elif self._is_halfspaces: + #Store the halfspaces in _points and the dual points in _dual_points later + self._point_arrays.append(np.array(points, copy=True)) + dists = points[:, :-1].dot(interior_point)+points[:, -1] + arr = np.array(-points[:, :-1]/dists, dtype=np.double, order="C", copy=True) + else: + arr = np.array(points, dtype=np.double, order="C", copy=True) - p = arr.data + self._messages.clear() - for j in range(arr.shape[0]): - facet = qh_findbestfacet(self._qh, p, 0, &bestdist, &isoutside) - if isoutside: - if not qh_addpoint(self._qh, p, facet, 0): - break - else: - # append the point to the "other points" list, to - # maintain the point IDs - qh_setappend(self._qh, &self._qh[0].other_points, p) + try: + # nonlocal error handling + exitcode = setjmp(self._qh[0].errexit) + if exitcode != 0: + raise QhullError(self._messages.get()) + self._qh[0].NOerrexit = 0 + + # add points to triangulation + if self._is_delaunay: + # lift to paraboloid + qh_setdelaunay(self._qh, arr.shape[1], arr.shape[0], arr.data) + + p = arr.data + + for j in range(arr.shape[0]): + facet = qh_findbestfacet(self._qh, p, 0, &bestdist, &isoutside) + if isoutside: + if not qh_addpoint(self._qh, p, facet, 0): + break + else: + # append the point to the "other points" list, to + # maintain the point IDs + qh_setappend(self._qh, &self._qh[0].other_points, p) - p += arr.shape[1] + p += arr.shape[1] - qh_check_maxout(self._qh) - self._qh[0].hasTriangulation = 0 + qh_check_maxout(self._qh) + self._qh[0].hasTriangulation = 0 - if self._is_halfspaces: - self._dual_point_arrays.append(arr) - else: - self._point_arrays.append(arr) - self.numpoints += arr.shape[0] + if self._is_halfspaces: + self._dual_point_arrays.append(arr) + else: + self._point_arrays.append(arr) + self.numpoints += arr.shape[0] - # update facet visibility - with nogil: - qh_findgood_all(self._qh, self._qh[0].facet_list) + # update facet visibility + with nogil: + qh_findgood_all(self._qh, self._qh[0].facet_list) + finally: + self._qh[0].NOerrexit = 1 finally: - self._qh[0].NOerrexit = 1 + self.release_lock() @cython.final def get_paraboloid_shift_scale(self): cdef double paraboloid_scale cdef double paraboloid_shift - self.check_active() + self.acquire_lock() + try: + self.check_active() - if self._qh[0].SCALElast: - paraboloid_scale = self._qh[0].last_newhigh / ( - self._qh[0].last_high - self._qh[0].last_low) - paraboloid_shift = - self._qh[0].last_low * paraboloid_scale - else: - paraboloid_scale = 1.0 - paraboloid_shift = 0.0 + if self._qh[0].SCALElast: + paraboloid_scale = self._qh[0].last_newhigh / ( + self._qh[0].last_high - self._qh[0].last_low) + paraboloid_shift = - self._qh[0].last_low * paraboloid_scale + else: + paraboloid_scale = 1.0 + paraboloid_shift = 0.0 - return paraboloid_scale, paraboloid_shift + return paraboloid_scale, paraboloid_shift + finally: + self.release_lock() @cython.final def volume_area(self): cdef double volume cdef double area - self.check_active() + self.acquire_lock() + try: + self.check_active() - self._qh.hasAreaVolume = 0 - with nogil: - qh_getarea(self._qh, self._qh[0].facet_list) + self._qh.hasAreaVolume = 0 + with nogil: + qh_getarea(self._qh, self._qh[0].facet_list) - volume = self._qh[0].totvol - area = self._qh[0].totarea + volume = self._qh[0].totvol + area = self._qh[0].totarea - return volume, area + return volume, area + finally: + self.release_lock() @cython.final def triangulate(self): - self.check_active() + self.acquire_lock() + try: + self.check_active() - with nogil: - qh_triangulate(self._qh) # get rid of non-simplical facets + with nogil: + qh_triangulate(self._qh) # get rid of non-simplical facets + finally: + self.release_lock() @cython.final @cython.boundscheck(False) @@ -548,120 +591,124 @@ cdef class _Qhull: cdef unsigned int lower_bound cdef unsigned int swapped_index - self.check_active() + self.acquire_lock() + try: + self.check_active() - facet_ndim = self.ndim + facet_ndim = self.ndim - if self._is_halfspaces: - facet_ndim = self.ndim - 1 + if self._is_halfspaces: + facet_ndim = self.ndim - 1 - if self._is_delaunay: - facet_ndim += 1 + if self._is_delaunay: + facet_ndim += 1 - id_map = np.empty(self._qh[0].facet_id, dtype=np.intc) + id_map = np.empty(self._qh[0].facet_id, dtype=np.intc) - # Compute facet indices - with nogil: - for i in range(self._qh[0].facet_id): - id_map[i] = -1 + # Compute facet indices + with nogil: + for i in range(self._qh[0].facet_id): + id_map[i] = -1 + + facet = self._qh[0].facet_list + j = 0 + while facet and facet.next: + if not self._is_delaunay or facet.upperdelaunay == self._qh[0].UPPERdelaunay: + if not facet.simplicial and ( \ + qh_setsize(self._qh, facet.vertices) != facet_ndim or \ + qh_setsize(self._qh, facet.neighbors) != facet_ndim): + with gil: + raise QhullError( + "non-simplical facet encountered: %r vertices" + % (qh_setsize(self._qh, facet.vertices),)) - facet = self._qh[0].facet_list - j = 0 - while facet and facet.next: - if not self._is_delaunay or facet.upperdelaunay == self._qh[0].UPPERdelaunay: - if not facet.simplicial and ( \ - qh_setsize(self._qh, facet.vertices) != facet_ndim or \ - qh_setsize(self._qh, facet.neighbors) != facet_ndim): - with gil: - raise QhullError( - "non-simplical facet encountered: %r vertices" - % (qh_setsize(self._qh, facet.vertices),)) - - id_map[facet.id] = j - j += 1 + id_map[facet.id] = j + j += 1 - facet = facet.next + facet = facet.next - # Allocate output - facets = np.zeros((j, facet_ndim), dtype=np.intc) - good = np.zeros(j, dtype=np.intc) - neighbors = np.zeros((j, facet_ndim), dtype=np.intc) - equations = np.zeros((j, facet_ndim+1), dtype=np.double) + # Allocate output + facets = np.zeros((j, facet_ndim), dtype=np.intc) + good = np.zeros(j, dtype=np.intc) + neighbors = np.zeros((j, facet_ndim), dtype=np.intc) + equations = np.zeros((j, facet_ndim+1), dtype=np.double) - ncoplanar = 0 - coplanar = np.zeros((10, 3), dtype=np.intc) - coplanar_shape = coplanar.shape + ncoplanar = 0 + coplanar = np.zeros((10, 3), dtype=np.intc) + coplanar_shape = coplanar.shape - # Retrieve facet information - with nogil: - facet = self._qh[0].facet_list - j = 0 - while facet and facet.next: - if self._is_delaunay and facet.upperdelaunay != self._qh[0].UPPERdelaunay: - facet = facet.next - continue + # Retrieve facet information + with nogil: + facet = self._qh[0].facet_list + j = 0 + while facet and facet.next: + if self._is_delaunay and facet.upperdelaunay != self._qh[0].UPPERdelaunay: + facet = facet.next + continue - # Use a lower bound so that the tight loop in high dimensions - # is not affected by the conditional below - lower_bound = 0 - if (self._is_delaunay and - facet.toporient == qh_ORIENTclock and facet_ndim == 3): - # Swap the first and second indices to maintain a - # counter-clockwise orientation. - for i in range(2): + # Use a lower bound so that the tight loop in high dimensions + # is not affected by the conditional below + lower_bound = 0 + if (self._is_delaunay and + facet.toporient == qh_ORIENTclock and facet_ndim == 3): + # Swap the first and second indices to maintain a + # counter-clockwise orientation. + for i in range(2): + # Save the vertex info + swapped_index = 1 ^ i + vertex = facet.vertices.e[i].p + ipoint = qh_pointid(self._qh, vertex.point) + facets[j, swapped_index] = ipoint + + # Save the neighbor info + neighbor = facet.neighbors.e[i].p + neighbors[j, swapped_index] = id_map[neighbor.id] + + lower_bound = 2 + + for i in range(lower_bound, facet_ndim): # Save the vertex info - swapped_index = 1 ^ i vertex = facet.vertices.e[i].p ipoint = qh_pointid(self._qh, vertex.point) - facets[j, swapped_index] = ipoint + facets[j, i] = ipoint # Save the neighbor info neighbor = facet.neighbors.e[i].p - neighbors[j, swapped_index] = id_map[neighbor.id] - - lower_bound = 2 - - for i in range(lower_bound, facet_ndim): - # Save the vertex info - vertex = facet.vertices.e[i].p - ipoint = qh_pointid(self._qh, vertex.point) - facets[j, i] = ipoint - - # Save the neighbor info - neighbor = facet.neighbors.e[i].p - neighbors[j, i] = id_map[neighbor.id] - - # Save simplex equation info - for i in range(facet_ndim): - equations[j, i] = facet.normal[i] - equations[j, facet_ndim] = facet.offset + neighbors[j, i] = id_map[neighbor.id] + + # Save simplex equation info + for i in range(facet_ndim): + equations[j, i] = facet.normal[i] + equations[j, facet_ndim] = facet.offset + + # Save coplanar info + if facet.coplanarset: + for i in range(qh_setsize(self._qh, facet.coplanarset)): + point = facet.coplanarset.e[i].p + vertex = qh_nearvertex(self._qh, facet, point, &dist) + + if ncoplanar >= coplanar_shape[0]: + with gil: + tmp = coplanar + coplanar = None + # The array is always safe to resize + tmp.resize(2 * ncoplanar + 1, 3, refcheck=False) + coplanar = tmp + + coplanar[ncoplanar, 0] = qh_pointid(self._qh, point) + coplanar[ncoplanar, 1] = id_map[facet.id] + coplanar[ncoplanar, 2] = qh_pointid(self._qh, vertex.point) + ncoplanar += 1 + + # Save good info + good[j] = facet.good - # Save coplanar info - if facet.coplanarset: - for i in range(qh_setsize(self._qh, facet.coplanarset)): - point = facet.coplanarset.e[i].p - vertex = qh_nearvertex(self._qh, facet, point, &dist) - - if ncoplanar >= coplanar_shape[0]: - with gil: - tmp = coplanar - coplanar = None - # The array is always safe to resize - tmp.resize(2 * ncoplanar + 1, 3, refcheck=False) - coplanar = tmp - - coplanar[ncoplanar, 0] = qh_pointid(self._qh, point) - coplanar[ncoplanar, 1] = id_map[facet.id] - coplanar[ncoplanar, 2] = qh_pointid(self._qh, vertex.point) - ncoplanar += 1 - - # Save good info - good[j] = facet.good - - j += 1 - facet = facet.next + j += 1 + facet = facet.next - return facets, neighbors, equations, coplanar[:ncoplanar], good + return facets, neighbors, equations, coplanar[:ncoplanar], good + finally: + self.release_lock() @cython.final @cython.boundscheck(False) @@ -682,27 +729,32 @@ cdef class _Qhull: cdef int i, j, numpoints, point_ndim cdef np.ndarray[np.npy_double, ndim=2] points - self.check_active() + self.acquire_lock() - point_ndim = self.ndim + try: + self.check_active() - if self._is_halfspaces: - point_ndim -= 1 + point_ndim = self.ndim - if self._is_delaunay: - point_ndim += 1 + if self._is_halfspaces: + point_ndim -= 1 - numpoints = self._qh.num_points - points = np.empty((numpoints, point_ndim)) + if self._is_delaunay: + point_ndim += 1 - with nogil: - point = self._qh.first_point - for i in range(numpoints): - for j in range(point_ndim): - points[i,j] = point[j] - point += self._qh.hull_dim + numpoints = self._qh.num_points + points = np.empty((numpoints, point_ndim)) + + with nogil: + point = self._qh.first_point + for i in range(numpoints): + for j in range(point_ndim): + points[i,j] = point[j] + point += self._qh.hull_dim - return points + return points + finally: + self.release_lock() @cython.final @cython.boundscheck(False) @@ -724,45 +776,49 @@ cdef class _Qhull: cdef np.ndarray[np.double_t, ndim=2] equations cdef list facets, facetsi - self.check_active() - - facet_ndim = self.ndim + self.acquire_lock() + try: + self.check_active() - if self._is_halfspaces: - facet_ndim -= 1 + facet_ndim = self.ndim - if self._is_delaunay: - facet_ndim += 1 + if self._is_halfspaces: + facet_ndim -= 1 - numfacets = self._qh.num_facets - self._qh.num_visible + if self._is_delaunay: + facet_ndim += 1 - facet = self._qh.facet_list - equations = np.empty((numfacets, facet_ndim+1)) + numfacets = self._qh.num_facets - self._qh.num_visible - facets = [] + facet = self._qh.facet_list + equations = np.empty((numfacets, facet_ndim+1)) - i = 0 - while facet and facet.next: - facetsi = [] - j = 0 - for j in range(facet_ndim): - equations[i, j] = facet.normal[j] - equations[i, facet_ndim] = facet.offset + facets = [] - j = 0 - vertex = facet.vertices.e[0].p - while vertex: - # Save the vertex info - ipoint = qh_pointid(self._qh, vertex.point) - facetsi.append(ipoint) - j += 1 - vertex = facet.vertices.e[j].p + i = 0 + while facet and facet.next: + facetsi = [] + j = 0 + for j in range(facet_ndim): + equations[i, j] = facet.normal[j] + equations[i, facet_ndim] = facet.offset + + j = 0 + vertex = facet.vertices.e[0].p + while vertex: + # Save the vertex info + ipoint = qh_pointid(self._qh, vertex.point) + facetsi.append(ipoint) + j += 1 + vertex = facet.vertices.e[j].p - i += 1 - facets.append(facetsi) - facet = facet.next + i += 1 + facets.append(facetsi) + facet = facet.next - return facets, equations + return facets, equations + finally: + self.release_lock() @cython.final @cython.boundscheck(False) @@ -808,101 +864,105 @@ cdef class _Qhull: cdef list regions cdef list cur_region - self.check_active() + self.acquire_lock() + try: + self.check_active() - # -- Grab Voronoi ridges - self._nridges = 0 - self._ridge_error = None - self._ridge_points = np.empty((10, 2), np.intc) - self._ridge_vertices = [] + # -- Grab Voronoi ridges + self._nridges = 0 + self._ridge_error = None + self._ridge_points = np.empty((10, 2), np.intc) + self._ridge_vertices = [] - qh_eachvoronoi_all(self._qh, self, &_visit_voronoi, self._qh[0].UPPERdelaunay, - qh_RIDGEall, 1) + qh_eachvoronoi_all(self._qh, self, &_visit_voronoi, self._qh[0].UPPERdelaunay, + qh_RIDGEall, 1) - self._ridge_points = self._ridge_points[:self._nridges] + self._ridge_points = self._ridge_points[:self._nridges] - if self._ridge_error is not None: - raise self._ridge_error + if self._ridge_error is not None: + raise self._ridge_error - # Now, qh_eachvoronoi_all has initialized the visitids of facets - # to correspond do the Voronoi vertex indices. + # Now, qh_eachvoronoi_all has initialized the visitids of facets + # to correspond do the Voronoi vertex indices. - # -- Grab Voronoi regions - regions = [] + # -- Grab Voronoi regions + regions = [] - point_region = np.empty(self.numpoints, np.intp) - for i in range(self.numpoints): - point_region[i] = -1 + point_region = np.empty(self.numpoints, np.intp) + for i in range(self.numpoints): + point_region[i] = -1 - vertex = self._qh[0].vertex_list - while vertex and vertex.next: - qh_order_vertexneighbors_nd(self._qh, self.ndim+1, vertex) + vertex = self._qh[0].vertex_list + while vertex and vertex.next: + qh_order_vertexneighbors_nd(self._qh, self.ndim+1, vertex) - i = qh_pointid(self._qh, vertex.point) - if i < self.numpoints: - # Qz results to one extra point - point_region[i] = len(regions) + i = qh_pointid(self._qh, vertex.point) + if i < self.numpoints: + # Qz results to one extra point + point_region[i] = len(regions) - inf_seen = 0 - cur_region = [] - for k in range(qh_setsize(self._qh, vertex.neighbors)): - neighbor = vertex.neighbors.e[k].p - i = neighbor.visitid - 1 - if i == -1: - if not inf_seen: - inf_seen = 1 - else: - continue - cur_region.append(int(i)) - if len(cur_region) == 1 and cur_region[0] == -1: - # report similarly as qvoronoi o + inf_seen = 0 cur_region = [] - regions.append(cur_region) - - vertex = vertex.next + for k in range(qh_setsize(self._qh, vertex.neighbors)): + neighbor = vertex.neighbors.e[k].p + i = neighbor.visitid - 1 + if i == -1: + if not inf_seen: + inf_seen = 1 + else: + continue + cur_region.append(int(i)) + if len(cur_region) == 1 and cur_region[0] == -1: + # report similarly as qvoronoi o + cur_region = [] + regions.append(cur_region) + + vertex = vertex.next + + # -- Grab Voronoi vertices and point-to-region map + nvoronoi_vertices = 0 + voronoi_vertices = np.empty((10, self.ndim), np.double) - # -- Grab Voronoi vertices and point-to-region map - nvoronoi_vertices = 0 - voronoi_vertices = np.empty((10, self.ndim), np.double) - - facet = self._qh[0].facet_list - while facet and facet.next: - if facet.visitid > 0: - # finite Voronoi vertex + facet = self._qh[0].facet_list + while facet and facet.next: + if facet.visitid > 0: + # finite Voronoi vertex - center = qh_facetcenter(self._qh, facet.vertices) + center = qh_facetcenter(self._qh, facet.vertices) - nvoronoi_vertices = max(facet.visitid, nvoronoi_vertices) - if nvoronoi_vertices >= voronoi_vertices.shape[0]: - tmp = voronoi_vertices - voronoi_vertices = None - # Array is safe to resize - tmp.resize(2*nvoronoi_vertices + 1, self.ndim, refcheck=False) - voronoi_vertices = tmp + nvoronoi_vertices = max(facet.visitid, nvoronoi_vertices) + if nvoronoi_vertices >= voronoi_vertices.shape[0]: + tmp = voronoi_vertices + voronoi_vertices = None + # Array is safe to resize + tmp.resize(2*nvoronoi_vertices + 1, self.ndim, refcheck=False) + voronoi_vertices = tmp - for k in range(self.ndim): - voronoi_vertices[facet.visitid-1, k] = center[k] + for k in range(self.ndim): + voronoi_vertices[facet.visitid-1, k] = center[k] - qh_memfree(self._qh, center, self._qh[0].center_size) + qh_memfree(self._qh, center, self._qh[0].center_size) - if facet.coplanarset: - for k in range(qh_setsize(self._qh, facet.coplanarset)): - point = facet.coplanarset.e[k].p - vertex = qh_nearvertex(self._qh, facet, point, &dist) + if facet.coplanarset: + for k in range(qh_setsize(self._qh, facet.coplanarset)): + point = facet.coplanarset.e[k].p + vertex = qh_nearvertex(self._qh, facet, point, &dist) - i = qh_pointid(self._qh, point) - j = qh_pointid(self._qh, vertex.point) + i = qh_pointid(self._qh, point) + j = qh_pointid(self._qh, vertex.point) - if i < self.numpoints: - # Qz can result to one extra point - point_region[i] = point_region[j] + if i < self.numpoints: + # Qz can result to one extra point + point_region[i] = point_region[j] - facet = facet.next + facet = facet.next - voronoi_vertices = voronoi_vertices[:nvoronoi_vertices] + voronoi_vertices = voronoi_vertices[:nvoronoi_vertices] - return voronoi_vertices, self._ridge_points, self._ridge_vertices, \ - regions, point_region + return voronoi_vertices, self._ridge_points, self._ridge_vertices, \ + regions, point_region + finally: + self.release_lock() @cython.final @cython.boundscheck(False) @@ -923,59 +983,63 @@ cdef class _Qhull: cdef int[:] extremes cdef int nextremes - self.check_active() + self.acquire_lock() + try: + self.check_active() - if self._is_delaunay: - raise ValueError("Cannot compute for Delaunay") + if self._is_delaunay: + raise ValueError("Cannot compute for Delaunay") - nextremes = 0 - extremes_arr = np.zeros(100, dtype=np.intc) - extremes = extremes_arr + nextremes = 0 + extremes_arr = np.zeros(100, dtype=np.intc) + extremes = extremes_arr - self._qh[0].visit_id += 1 - self._qh[0].vertex_visit += 1 + self._qh[0].visit_id += 1 + self._qh[0].vertex_visit += 1 - facet = self._qh[0].facet_list - startfacet = facet - while facet: - if facet.visitid == self._qh[0].visit_id: - raise QhullError("Qhull internal error: loop in facet list") + facet = self._qh[0].facet_list + startfacet = facet + while facet: + if facet.visitid == self._qh[0].visit_id: + raise QhullError("Qhull internal error: loop in facet list") + + if facet.toporient: + vertexA = facet.vertices.e[0].p + vertexB = facet.vertices.e[1].p + nextfacet = facet.neighbors.e[0].p + else: + vertexB = facet.vertices.e[0].p + vertexA = facet.vertices.e[1].p + nextfacet = facet.neighbors.e[1].p - if facet.toporient: - vertexA = facet.vertices.e[0].p - vertexB = facet.vertices.e[1].p - nextfacet = facet.neighbors.e[0].p - else: - vertexB = facet.vertices.e[0].p - vertexA = facet.vertices.e[1].p - nextfacet = facet.neighbors.e[1].p - - if nextremes + 2 >= extremes.shape[0]: - extremes = None - # Array is safe to resize - extremes_arr.resize(2*extremes_arr.shape[0]+1, refcheck=False) - extremes = extremes_arr - - if vertexA.visitid != self._qh[0].vertex_visit: - vertexA.visitid = self._qh[0].vertex_visit - extremes[nextremes] = qh_pointid(self._qh, vertexA.point) - nextremes += 1 - - if vertexB.visitid != self._qh[0].vertex_visit: - vertexB.visitid = self._qh[0].vertex_visit - extremes[nextremes] = qh_pointid(self._qh, vertexB.point) - nextremes += 1 - - facet.visitid = self._qh[0].visit_id - facet = nextfacet - - if facet == startfacet: - break + if nextremes + 2 >= extremes.shape[0]: + extremes = None + # Array is safe to resize + extremes_arr.resize(2*extremes_arr.shape[0]+1, refcheck=False) + extremes = extremes_arr + + if vertexA.visitid != self._qh[0].vertex_visit: + vertexA.visitid = self._qh[0].vertex_visit + extremes[nextremes] = qh_pointid(self._qh, vertexA.point) + nextremes += 1 + + if vertexB.visitid != self._qh[0].vertex_visit: + vertexB.visitid = self._qh[0].vertex_visit + extremes[nextremes] = qh_pointid(self._qh, vertexB.point) + nextremes += 1 - extremes = None - # This array is always safe to resize - extremes_arr.resize(nextremes, refcheck=False) - return extremes_arr + facet.visitid = self._qh[0].visit_id + facet = nextfacet + + if facet == startfacet: + break + + extremes = None + # This array is always safe to resize + extremes_arr.resize(nextremes, refcheck=False) + return extremes_arr + finally: + self.release_lock() cdef void _visit_voronoi(qhT *_qh, FILE *ptr, vertexT *vertex, vertexT *vertexA, From d47402617f28d121008f11e3de1f416e9cf66cb3 Mon Sep 17 00:00:00 2001 From: Matti Picus Date: Tue, 28 May 2024 16:10:52 +0300 Subject: [PATCH 287/500] CI/BLD: use scipy-openblas wheel when building (#20585) --- .github/workflows/linux.yml | 27 +- .github/workflows/macos.yml | 9 +- .github/workflows/musllinux.yml | 2 + .github/workflows/wheels.yml | 52 ++- .github/workflows/windows.yml | 24 +- dev.py | 10 +- pyproject.toml | 25 +- requirements/openblas.txt | 1 + scipy/meson.build | 5 +- tools/openblas_support.py | 410 ------------------------ tools/wheels/cibw_before_build_linux.sh | 7 +- tools/wheels/cibw_before_build_macos.sh | 35 +- tools/wheels/cibw_before_build_win.sh | 29 +- tools/wheels/cibw_test_command.sh | 9 - tools/wheels/repair_windows.sh | 6 +- 15 files changed, 95 insertions(+), 556 deletions(-) create mode 100644 requirements/openblas.txt delete mode 100644 tools/openblas_support.py diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 2c9789aba166..0836cdf1ecd0 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -321,10 +321,8 @@ jobs: # (it can be put back after matplotlib has made a 2.0-compatible # release on PyPI. python -m pip install --pre --upgrade pytest pytest-cov pytest-xdist mpmath gmpy2 threadpoolctl pooch hypothesis - # TODO: once the scipy_ symbol prefix issue is fixed, install - # scipy-openblas32 from the pre-releases bucket again (see gh-19640) - python -m pip install "scipy-openblas32<=0.3.23.293.2" - # Install numpy last, to ensure we get 2.0.0-dev (avoid possible <2.0 constraints). + python -m pip install -r requirements/openblas.txt + # Install numpy last, to ensure we get nightly (avoid possible <2.0 constraints). python -m pip install --pre --upgrade --timeout=60 -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - name: Prepare compiler cache @@ -382,20 +380,27 @@ jobs: - name: build + test run: | - set -euo pipefail + set -exuo pipefail docker pull quay.io/pypa/manylinux2014_i686 docker run -v $(pwd):/scipy --platform=linux/i386 quay.io/pypa/manylinux2014_i686 /bin/bash -c "cd /scipy && \ uname -a && \ - basedir=\$(python3.10 tools/openblas_support.py) && \ - cp -r \$basedir/lib/* /usr/local/lib && \ - cp \$basedir/include/* /usr/local/include && \ - export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig && \ python3.10 -m venv test && \ source test/bin/activate && \ python -m pip install doit click rich_click pydevtool meson ninja && \ + python -m pip install -r requirements/openblas.txt && \ + # Ensure that scipy-openblas is picked up by the numpy<1.26 build + cat > \$HOME/.numpy-site.cfg <> $env:GITHUB_PATH if: ${{ runner.os == 'Windows' && env.IS_32_BIT == 'false' }} + - name: windows - set PKG_CONFIG_PATH + run: | + $env:CIBW = "${{ github.workspace }}" + # It seems somewhere in the env passing, `\` is not + # passed through, so convert it to '/' + $env:CIBW=$env:CIBW.replace("\","/") + echo "CIBW_ENVIRONMENT_WINDOWS=PKG_CONFIG_PATH=$env:CIBW" >> $env:GITHUB_ENV + if: ${{ runner.os == 'Windows' }} + - name: Setup macOS if: startsWith( matrix.buildplat[0], 'macos-' ) run: | @@ -126,12 +135,6 @@ jobs: echo "PATH=$PATH" >> "$GITHUB_ENV" LIB_PATH=$(dirname $(gfortran --print-file-name libgfortran.dylib)) fi - # Add libraries installed by cibw_before_build_macos.sh to path - if [[ ${{ matrix.buildplat[2] }} == 'arm64' ]]; then - LIB_PATH=$LIB_PATH:/opt/arm64-builds/lib - else - LIB_PATH=$LIB_PATH:/usr/local/lib - fi if [[ ${{ matrix.buildplat[4] }} == '10.9' ]]; then # Newest version of Xcode that supports macOS 10.9 XCODE_VER='13.4.1' @@ -144,18 +147,21 @@ jobs: # installed in cibw_before_build_macos.sh sudo xcode-select -s /Applications/Xcode_${XCODE_VER}.app CIBW="MACOSX_DEPLOYMENT_TARGET=${{ matrix.buildplat[4] }}\ - LD_LIBRARY_PATH=$LIB_PATH:$LD_LIBRARY_PATH\ SDKROOT=$(xcrun --sdk macosx --show-sdk-path)\ - PIP_PRE=1\ - PIP_NO_BUILD_ISOLATION=false\ - PKG_CONFIG_PATH=$LIB_PATH/pkgconfig\ - PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" + PKG_CONFIG_PATH=${{ github.workspace }}" echo "CIBW_ENVIRONMENT_MACOS=$CIBW" >> "$GITHUB_ENV" echo "REPAIR_PATH=$LIB_PATH" >> "$GITHUB_ENV" - GFORTRAN_LIB="\$(dirname \$(gfortran --print-file-name libgfortran.dylib))" - CIBW="DYLD_LIBRARY_PATH=$GFORTRAN_LIB:$LIB_PATH delocate-listdeps {wheel} &&\ - DYLD_LIBRARY_PATH=$GFORTRAN_LIB:$LIB_PATH delocate-wheel --require-archs \ + if [[ ${{ matrix.buildplat[3] }} == 'accelerate' ]]; then + PREFIX=DYLD_LIBRARY_PATH=$(dirname $(gfortran --print-file-name libgfortran.dylib)) + else + # Use libgfortran and friends from the scipy-openblas wheel, + # which should be exactly the same as the ones from gfortran + # This will exclude the duplicates from gfortran in /opt/gfortran* + EXCLUDE="-e /gfortran" + fi + CIBW="$PREFIX delocate-listdeps {wheel} &&\ + $PREFIX delocate-wheel $EXCLUDE --require-archs \ {delocate_archs} -w {dest_dir} {wheel}" # Rename x86 Accelerate wheel to test on macOS 13 runner if [[ ${{ matrix.buildplat[0] }} == 'macos-13' && ${{ matrix.buildplat[4] }} == '14.0' ]]; then @@ -169,26 +175,8 @@ jobs: env: CIBW_BUILD: ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }}* CIBW_ARCHS: ${{ matrix.buildplat[2] }} - CIBW_ENVIRONMENT_PASS_LINUX: RUNNER_OS CIBW_PRERELEASE_PYTHONS: True - # TODO remove the CIBW_BEFORE_BUILD_* lines once there are - # numpy2.0 wheels available on PyPI. Also remove/comment out the - # PIP_NO_BUILD_ISOLATION and PIP_EXTRA_INDEX_URL from CIBW_ENVIRONMENT - # (also for _MACOS and _WINDOWS below) - CIBW_BEFORE_BUILD_LINUX: "pip install numpy>=2.0.0.dev0 meson-python cython pythran pybind11 ninja; bash {project}/tools/wheels/cibw_before_build_linux.sh {project}" - CIBW_BEFORE_BUILD_WINDOWS: "pip install numpy>=2.0.0.dev0 meson-python cython pythran pybind11 ninja && bash {project}/tools/wheels/cibw_before_build_win.sh {project}" - CIBW_BEFORE_BUILD_MACOS: "pip install numpy>=2.0.0.dev0 meson-python cython pythran pybind11 ninja; bash {project}/tools/wheels/cibw_before_build_macos.sh {project}" - # Allow pip to find install nightly wheels if necessary - # Setting PIP_NO_BUILD_ISOLATION=false makes pip use build-isolation. - CIBW_ENVIRONMENT: "PIP_NO_BUILD_ISOLATION=false PIP_PRE=1 PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" - - CIBW_ENVIRONMENT_WINDOWS: > - PKG_CONFIG_PATH=c:/opt/64/lib/pkgconfig - PIP_PRE=1 - PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple - PIP_NO_BUILD_ISOLATION=false - - name: Rename after test (macOS x86 Accelerate only) # Rename x86 Accelerate wheel back so it targets macOS >= 14 if: matrix.buildplat[0] == 'macos-13' && matrix.buildplat[4] == '14.0' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index f055d4861273..bd27819346bc 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -49,7 +49,8 @@ jobs: - name: pip-packages run: | - pip install numpy cython pybind11 pythran meson ninja pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich_click click doit pydevtool hypothesis "scipy-openblas32<=0.3.23.293.2" + pip install numpy cython pybind11 pythran meson ninja pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich_click click doit pydevtool hypothesis + python -m pip install -r requirements/openblas.txt - name: Build run: | @@ -57,6 +58,8 @@ jobs: - name: Test run: | + # test runner parallel clashes with OpenBLAS multithreading + $env:OPENBLAS_NUM_THREADS=1 python dev.py test -j2 -- --durations=0 --durations-min=0.25 --fail-slow=1.0 @@ -89,7 +92,8 @@ jobs: run: | # 1.23.5 is currently the oldest numpy usable on cp3.10 according # to pyproject.toml - python -m pip install numpy==1.23.5 cython pybind11 pythran meson-python meson ninja pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich_click click doit pydevtool hypothesis "scipy-openblas32<=0.3.23.293.2" + python -m pip install numpy==1.23.5 cython pybind11 pythran meson-python meson ninja pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich_click click doit pydevtool hypothesis + python -m pip install -r requirements/openblas.txt - name: Build run: | @@ -97,6 +101,8 @@ jobs: - name: Test run: | + # test runner parallel clashes with OpenBLAS multithreading + $env:OPENBLAS_NUM_THREADS=1 python dev.py test -j2 --mode full -- --durations=0 --durations-min=1.0 --timeout=60 --fail-slow=5.0 @@ -130,10 +136,10 @@ jobs: - name: Install OpenBLAS shell: bash run: | - # Keep this using the OpenBLAS tarballs for now, as long as we use those for wheel builds set -xe + python -m pip install -r requirements/openblas.txt bash tools/wheels/cibw_before_build_win.sh . - echo "PKG_CONFIG_PATH=c:\opt\64\lib\pkgconfig;" >> $GITHUB_ENV + echo "PKG_CONFIG_PATH=${{ github.workspace }}" >> $GITHUB_ENV - name: pip-packages run: | @@ -141,19 +147,23 @@ jobs: python -m pip install --pre --upgrade --timeout=60 -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - name: Build + shell: bash run: | python -m build --no-isolation -x -Csetup-args="-Duse-pythran=false" # Vendor openblas.dll and the DLL's it depends on into the wheel # Ignore `libsf_error_state.dll` for special function error handling; # it will be loaded using ctypes in scipy/special/__init__.py. - $env:wheel_name=Get-ChildItem -Path dist/* -Include *.whl - delvewheel repair --add-path c:\opt\openblas\openblas_dll --no-dll libsf_error_state.dll -w dist $env:wheel_name + wheel_name=$(ls dist/*.whl) + openblas_dir=$(python -c"import scipy_openblas32 as sop; print(sop.get_lib_dir())") + delvewheel repair --add-path $openblas_dir --no-dll libsf_error_state.dll -w wheelhouse $wheel_name - python -m pip install $env:wheel_name + python -m pip install wheelhouse/* - name: Test run: | cd $RUNNER_TEMP # run full test suite + # test runner parallel clashes with OpenBLAS multithreading + $env:OPENBLAS_NUM_THREADS=1 pytest --pyargs scipy diff --git a/dev.py b/dev.py index c4d957e96cf4..3cd5b7afb7e5 100644 --- a/dev.py +++ b/dev.py @@ -498,7 +498,7 @@ def setup_build(cls, dirs, args): elif args.with_scipy_openblas: cls.configure_scipy_openblas() env['PKG_CONFIG_PATH'] = os.pathsep.join([ - os.path.join(os.getcwd(), '.openblas'), + os.getcwd(), env.get('PKG_CONFIG_PATH', '') ]) @@ -607,13 +607,12 @@ def install_project(cls, dirs, args): @classmethod def configure_scipy_openblas(self, blas_variant='32'): - """Create .openblas/scipy-openblas.pc and scipy/_distributor_init_local.py + """Create scipy-openblas.pc and scipy/_distributor_init_local.py Requires a pre-installed scipy-openblas32 wheel from PyPI. """ basedir = os.getcwd() - openblas_dir = os.path.join(basedir, ".openblas") - pkg_config_fname = os.path.join(openblas_dir, "scipy-openblas.pc") + pkg_config_fname = os.path.join(basedir, "scipy-openblas.pc") if os.path.exists(pkg_config_fname): return None @@ -631,9 +630,8 @@ def configure_scipy_openblas(self, blas_variant='32'): with open(local, "w", encoding="utf8") as fid: fid.write(f"import {module_name}\n") - os.makedirs(openblas_dir, exist_ok=True) with open(pkg_config_fname, "w", encoding="utf8") as fid: - fid.write(openblas.get_pkg_config().replace("\\", "/")) + fid.write(openblas.get_pkg_config()) @classmethod def run(cls, add_path=False, **kwargs): diff --git a/pyproject.toml b/pyproject.toml index 5dacd58974ea..62b9e0550a05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,7 +122,6 @@ dodoFile = "dev.py" [tool.cibuildwheel] skip = "cp36-* cp37-* cp38-* pp* *_ppc64le *_i686 *_s390x" -build-verbosity = "3" # gmpy2 and scikit-umfpack are usually added for testing. However, there are # currently wheels missing that make the test script fail. test-requires = [ @@ -141,22 +140,22 @@ manylinux-x86_64-image = "manylinux2014" manylinux-aarch64-image = "manylinux2014" before-build = "bash {project}/tools/wheels/cibw_before_build_linux.sh {project}" +[tool.cibuildwheel.linux.environment] +# /project will be the $PWD equivalent inside the docker used to build the wheel +PKG_CONFIG_PATH="/project/" + [tool.cibuildwheel.macos] before-build = "bash {project}/tools/wheels/cibw_before_build_macos.sh {project}" +[tool.cibuildwheel.macos.environment] +PKG_CONFIG_PATH="{project}" + [tool.cibuildwheel.windows] before-build = "bash {project}/tools/wheels/cibw_before_build_win.sh {project}" repair-wheel-command = "bash ./tools/wheels/repair_windows.sh {wheel} {dest_dir}" -[[tool.cibuildwheel.overrides]] -select = "*-win32" - -[[tool.cibuildwheel.overrides]] -select = "*-win_amd64" -# can use pkg-config detection for win_amd64 because the installed rtools -# provide a working pkg-config. -# An alternative is to set CMAKE_PREFIX_PATH="c:/opt/openblas/if_32/32" -# Don't use double backslash for path separators, they don't get passed -# to the build correctly -# environment = { CMAKE_PREFIX_PATH="c:/opt/64" } -environment = { PKG_CONFIG_PATH = "c:/opt/64/lib/pkgconfig" } +[tool.cibuildwheel.windows.environment] +# This does not work because pkg-config does not like backslashes, +PKG_CONFIG_PATH="{project}" +# do this instead (which will override this setting) +# set CIBW_ENVIRONMENT_WINDOWS=PKG_CONFIG_PATH=PWD.replace('\\', '/') diff --git a/requirements/openblas.txt b/requirements/openblas.txt new file mode 100644 index 000000000000..db99a76438b7 --- /dev/null +++ b/requirements/openblas.txt @@ -0,0 +1 @@ +scipy-openblas32==0.3.27.63.1 diff --git a/scipy/meson.build b/scipy/meson.build index 988458d21a3b..79a16cd5b8ea 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -206,6 +206,7 @@ if blas_name == 'openblas' or blas_name == 'auto' blas = dependency('scipy-openblas', method: 'pkg-config', required: false) if blas.found() blas_name = 'scipy-openblas' + generate_blas_wrappers = true endif endif @@ -298,7 +299,8 @@ python_sources = [ 'special.pxd', ] -if blas_name == 'scipy-openblas' +fs = import('fs') +if fs.exists('_distributor_init_local.py') python_sources += ['_distributor_init_local.py'] endif @@ -309,7 +311,6 @@ py3.install_sources( # Copy the main __init__.py and pxd files to the build dir. # Needed to trick Cython, it won't do a relative import outside a package -fs = import('fs') #_cython_tree = declare_dependency(sources: [ _cython_tree = [ fs.copyfile('__init__.py'), diff --git a/tools/openblas_support.py b/tools/openblas_support.py deleted file mode 100644 index 37b60dd69e55..000000000000 --- a/tools/openblas_support.py +++ /dev/null @@ -1,410 +0,0 @@ -import glob -import os -import platform -import sysconfig -import sys -import shutil -import tarfile -import textwrap -import time -import zipfile - -from tempfile import mkstemp, gettempdir -from urllib.request import urlopen, Request -from urllib.error import HTTPError - -OPENBLAS_V = '0.3.27' -OPENBLAS_LONG = 'v0.3.27' -BASE_LOC = 'https://anaconda.org/multibuild-wheels-staging/openblas-libs' -NIGHTLY_BASE_LOC = ( - 'https://anaconda.org/scientific-python-nightly-wheels/openblas-libs' -) - -SUPPORTED_PLATFORMS = [ - 'linux-aarch64', - 'linux-x86_64', - 'musllinux-x86_64', - 'linux-i686', - 'linux-ppc64le', - 'linux-s390x', - 'win-amd64', - 'win-32', - 'macosx-x86_64', - 'macosx-arm64', -] -IS_32BIT = sys.maxsize < 2**32 - - -def get_plat(): - plat = sysconfig.get_platform() - plat_split = plat.split("-") - - arch = plat_split[-1] - if arch == "win32": - plat = "win-32" - elif arch in ["universal2", "intel"]: - plat = f"macosx-{platform.uname().machine}" - elif len(plat_split) > 2: - plat = f"{plat_split[0]}-{arch}" - assert plat in SUPPORTED_PLATFORMS, f'invalid platform {plat}' - return plat - - -def get_ilp64(): - if os.environ.get("NPY_USE_BLAS_ILP64", "0") == "0": - return None - if IS_32BIT: - raise RuntimeError("NPY_USE_BLAS_ILP64 set on 32-bit arch") - return "64_" - - -def get_manylinux(arch): - default = '2014' - ml_ver = os.environ.get("MB_ML_VER", default) - # XXX For PEP 600 this can be a glibc version - assert ml_ver in ('2010', '2014', '_2_24'), f'invalid MB_ML_VER {ml_ver}' - suffix = f'manylinux{ml_ver}_{arch}.tar.gz' - return suffix - - -def get_musllinux(arch): - musl_ver = "1_1" - suffix = f'musllinux_{musl_ver}_{arch}.tar.gz' - return suffix - - -def get_linux(arch): - # best way of figuring out whether manylinux or musllinux is to look - # at the packaging tags. If packaging isn't installed (it's not by default) - # fallback to sysconfig (which may be flakier) - try: - from packaging.tags import sys_tags - tags = list(sys_tags()) - plat = tags[0].platform - except ImportError: - # fallback to sysconfig for figuring out if you're using musl - plat = 'manylinux' - # value could be None - v = sysconfig.get_config_var('HOST_GNU_TYPE') or '' - if 'musl' in v: - plat = 'musllinux' - - if 'manylinux' in plat: - return get_manylinux(arch) - elif 'musllinux' in plat: - return get_musllinux(arch) - - -def download_openblas( - target, plat, ilp64, *, openblas_version=OPENBLAS_LONG, base_loc=BASE_LOC -): - osname, arch = plat.split("-") - fnsuffix = {None: "", "64_": "64_"}[ilp64] - filename = '' - headers = {'User-Agent': - ('Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 ; ' - '(KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.3')} - suffix = None - if osname == "linux": - suffix = get_linux(arch) - typ = 'tar.gz' - elif plat == 'macosx-x86_64': - suffix = 'macosx_10_9_x86_64-gf_c469a42.tar.gz' - typ = 'tar.gz' - elif plat == 'macosx-arm64': - suffix = 'macosx_11_0_arm64-gf_5272328.tar.gz' - typ = 'tar.gz' - elif osname == 'win': - if plat == "win-32": - suffix = 'win32-gcc_8_3_0.zip' - else: - suffix = 'win_amd64-gcc_10_3_0.zip' - typ = 'zip' - - if not suffix: - return None - BASEURL = f'{base_loc}/{openblas_version}/download' - filename = f'{BASEURL}/openblas{fnsuffix}-{openblas_version}-{suffix}' - req = Request(url=filename, headers=headers) - - for _ in range(3): - try: - time.sleep(1) - response = urlopen(req) - break - except HTTPError: - print(f'Could not download "{filename}"', file=sys.stderr) - raise - - length = response.getheader('content-length') - if response.status != 200: - print(f'Could not download "{filename}"', file=sys.stderr) - return None - print(f"Downloading {length} from {filename}", file=sys.stderr) - data = response.read() - print("Saving to file", file=sys.stderr) - with open(target, 'wb') as fid: - fid.write(data) - return typ - - -def setup_openblas(plat=get_plat(), ilp64=get_ilp64(), nightly=False): - ''' - Download and setup an openblas library for building. If successful, - the configuration script will find it automatically. - - Returns - ------- - msg : str - path to extracted files on success, otherwise indicates what went wrong - To determine success, do ``os.path.exists(msg)`` - ''' - fd, tmp = mkstemp() - os.close(fd) - if not plat: - raise ValueError('unknown platform') - openblas_version = "HEAD" if nightly else OPENBLAS_LONG - base_loc = NIGHTLY_BASE_LOC if nightly else BASE_LOC - typ = download_openblas( - tmp, plat, ilp64, openblas_version=openblas_version, base_loc=base_loc - ) - if not typ: - return '' - osname, arch = plat.split("-") - if osname == 'win': - if not typ == 'zip': - return f'expecting to download zipfile on windows, not {typ}' - return unpack_windows_zip(tmp) - else: - if not typ == 'tar.gz': - return 'expecting to download tar.gz, not %s' % str(typ) - return unpack_targz(tmp) - - -def unpack_windows_zip(fname): - with zipfile.ZipFile(fname, 'r') as zf: - # Get the openblas.a file, but not openblas.dll.a nor openblas.dev.a - lib = [x for x in zf.namelist() if OPENBLAS_LONG in x and - x.endswith('a') and not x.endswith('dll.a') and - not x.endswith('dev.a')] - if not lib: - return 'could not find libopenblas_%s*.a ' \ - 'in downloaded zipfile' % OPENBLAS_LONG - if get_ilp64() is None: - target = os.path.join(gettempdir(), 'openblas.a') - else: - target = os.path.join(gettempdir(), 'openblas64_.a') - with open(target, 'wb') as fid: - fid.write(zf.read(lib[0])) - return target - - -def unpack_targz(fname): - target = os.path.join(gettempdir(), 'openblas') - if not os.path.exists(target): - os.mkdir(target) - with tarfile.open(fname, 'r') as zf: - # Strip common prefix from paths when unpacking - prefix = os.path.commonpath(zf.getnames()) - extract_tarfile_to(zf, target, prefix) - return target - - -def extract_tarfile_to(tarfileobj, target_path, archive_path): - """Extract TarFile contents under archive_path/ to target_path/""" - - target_path = os.path.abspath(target_path) - - def get_members(): - for member in tarfileobj.getmembers(): - if archive_path: - norm_path = os.path.normpath(member.name) - if norm_path.startswith(archive_path + os.path.sep): - member.name = norm_path[len(archive_path)+1:] - else: - continue - - dst_path = os.path.abspath(os.path.join(target_path, member.name)) - if os.path.commonpath([target_path, dst_path]) != target_path: - # Path not under target_path, probably contains ../ - continue - - yield member - - tarfileobj.extractall(target_path, members=get_members()) - reformat_pkg_file(target_path=target_path) - - -def reformat_pkg_file(target_path): - # attempt to deal with: - # https://github.com/scipy/scipy/pull/20362#issuecomment-2028517797 - for root, dirs, files in os.walk(target_path): - for name in files: - if name.endswith(".pc") and "openblas" in name: - pkg_path = os.path.join(root, name) - new_pkg_lines = [] - with open(pkg_path) as pkg_orig: - for line in pkg_orig: - if line.startswith("Libs:"): - new_line = line.replace("$(libprefix}", "${libprefix}") - new_pkg_lines.append(new_line) - else: - new_pkg_lines.append(line) - with open(pkg_path, "w") as new_pkg: - new_pkg.writelines(new_pkg_lines) - - -def make_init(dirname): - ''' - Create a _distributor_init.py file for OpenBlas - ''' - with open(os.path.join(dirname, '_distributor_init.py'), 'w') as fid: - fid.write(textwrap.dedent(""" - ''' - Helper to preload windows dlls to prevent dll not found errors. - Once a DLL is preloaded, its namespace is made available to any - subsequent DLL. This file originated in the numpy-wheels repo, - and is created as part of the scripts that build the wheel. - ''' - import os - import glob - if os.name == 'nt': - # convention for storing / loading the DLL from - # numpy/.libs/, if present - try: - from ctypes import WinDLL - basedir = os.path.dirname(__file__) - except: - pass - else: - libs_dir = os.path.abspath(os.path.join(basedir, '.libs')) - DLL_filenames = [] - if os.path.isdir(libs_dir): - for filename in glob.glob(os.path.join(libs_dir, - '*openblas*dll')): - # NOTE: would it change behavior to load ALL - # DLLs at this path vs. the name restriction? - WinDLL(os.path.abspath(filename)) - DLL_filenames.append(filename) - if len(DLL_filenames) > 1: - import warnings - warnings.warn("loaded more than 1 DLL from .libs:" - "\\n%s" % "\\n".join(DLL_filenames), - stacklevel=1) - """)) - - -def test_setup(plats): - ''' - Make sure all the downloadable files exist and can be opened - ''' - def items(): - """ yields all combinations of arch, ilp64 - """ - for plat in plats: - yield plat, None - osname, arch = plat.split("-") - if arch not in ('i686', 'arm64', '32'): - yield plat, '64_' - if osname == "linux" and arch in ('i686', 'x86_64'): - oldval = os.environ.get('MB_ML_VER', None) - os.environ['MB_ML_VER'] = '1' - yield plat, None - # Once we create x86_64 and i686 manylinux2014 wheels... - # os.environ['MB_ML_VER'] = '2014' - # yield arch, None, False - if oldval: - os.environ['MB_ML_VER'] = oldval - else: - os.environ.pop('MB_ML_VER') - - errs = [] - for plat, ilp64 in items(): - osname, _ = plat.split("-") - if plat not in plats: - continue - target = None - try: - try: - target = setup_openblas(plat, ilp64) - except Exception as e: - print(f'Could not setup {plat} with ilp64 {ilp64}, ') - print(e) - errs.append(e) - continue - if not target: - raise RuntimeError(f'Could not setup {plat}') - print(target) - if osname == 'win': - if not target.endswith('.a'): - raise RuntimeError("Not .a extracted!") - else: - files = glob.glob(os.path.join(target, "lib", "*.a")) - if not files: - raise RuntimeError("No lib/*.a unpacked!") - finally: - if target is not None: - if os.path.isfile(target): - os.unlink(target) - else: - shutil.rmtree(target) - if errs: - raise errs[0] - - -def test_version(expected_version, ilp64=get_ilp64()): - """ - Assert that expected OpenBLAS version is - actually available via SciPy - """ - import scipy - import scipy.linalg - import ctypes - - dll = ctypes.CDLL(scipy.linalg.cython_blas.__file__) - if ilp64 == "64_": - get_config = dll.openblas_get_config64_ - else: - get_config = dll.openblas_get_config - get_config.restype = ctypes.c_char_p - res = get_config() - print('OpenBLAS get_config returned', str(res)) - if not expected_version: - expected_version = OPENBLAS_V - check_str = b'OpenBLAS %s' % expected_version.encode() - print(check_str) - assert check_str in res, f'{expected_version} not found in {res}' - if ilp64: - assert b"USE64BITINT" in res - else: - assert b"USE64BITINT" not in res - - -if __name__ == '__main__': - import argparse - parser = argparse.ArgumentParser( - description='Download and expand an OpenBLAS archive for this ' - 'architecture') - parser.add_argument('--test', nargs='*', default=None, - help='Test different architectures. "all", or any of ' - f'{SUPPORTED_PLATFORMS}') - parser.add_argument('--write-init', nargs=1, - metavar='OUT_SCIPY_DIR', - help='Write distribution init to named dir') - parser.add_argument('--check_version', nargs='?', default='', - help='Check provided OpenBLAS version string ' - 'against available OpenBLAS') - parser.add_argument('--nightly', action='store_true', - help='If set, use nightly OpenBLAS build.') - args = parser.parse_args() - if args.check_version != '': - test_version(args.check_version) - elif args.write_init: - make_init(args.write_init[0]) - elif args.test is None: - print(setup_openblas(nightly=args.nightly)) - else: - if len(args.test) == 0 or 'all' in args.test: - test_setup(SUPPORTED_PLATFORMS) - else: - test_setup(args.test) diff --git a/tools/wheels/cibw_before_build_linux.sh b/tools/wheels/cibw_before_build_linux.sh index 2bae0f5e75b1..1b98aeb2d858 100755 --- a/tools/wheels/cibw_before_build_linux.sh +++ b/tools/wheels/cibw_before_build_linux.sh @@ -13,13 +13,10 @@ else exit 1 fi -PLATFORM=$(PYTHONPATH=tools python -c "import openblas_support; print(openblas_support.get_plat())") - printenv # Update license cat $PROJECT_DIR/tools/wheels/LICENSE_linux.txt >> $PROJECT_DIR/LICENSE.txt # Install Openblas -basedir=$(python tools/openblas_support.py $NIGHTLY_FLAG) -cp -r $basedir/lib/* /usr/local/lib -cp $basedir/include/* /usr/local/include +python -m pip install -r requirements/openblas.txt +python -c "import scipy_openblas32; print(scipy_openblas32.get_pkg_config())" > $PROJECT_DIR/scipy-openblas.pc diff --git a/tools/wheels/cibw_before_build_macos.sh b/tools/wheels/cibw_before_build_macos.sh index 1a664a309083..ec8df95cb53f 100644 --- a/tools/wheels/cibw_before_build_macos.sh +++ b/tools/wheels/cibw_before_build_macos.sh @@ -1,7 +1,7 @@ set -xe PROJECT_DIR="$1" -PLATFORM=$(PYTHONPATH=tools python -c "import openblas_support; print(openblas_support.get_plat())") +PLATFORM=$(uname -m) echo $PLATFORM # Update license @@ -10,17 +10,11 @@ cat $PROJECT_DIR/tools/wheels/LICENSE_osx.txt >> $PROJECT_DIR/LICENSE.txt ######################################################################################### # Install GFortran + OpenBLAS -if [[ $PLATFORM == "macosx-x86_64" ]]; then - # Openblas - basedir=$(python tools/openblas_support.py) - - # copy over the OpenBLAS library stuff first - cp -r $basedir/lib/* /usr/local/lib - cp $basedir/include/* /usr/local/include - +if [[ $PLATFORM == "x86_64" ]]; then #GFORTRAN=$(type -p gfortran-9) #sudo ln -s $GFORTRAN /usr/local/bin/gfortran - # same version of gfortran as the openblas-libs and scipy-wheel builds + # same version of gfortran as the openblas-libs + # https://github.com/MacPython/gfortran-install.git curl -L https://github.com/isuruf/gcc/releases/download/gcc-11.3.0-2/gfortran-darwin-x86_64-native.tar.gz -o gfortran.tar.gz GFORTRAN_SHA256=$(shasum -a 256 gfortran.tar.gz) @@ -49,21 +43,7 @@ if [[ $PLATFORM == "macosx-x86_64" ]]; then export SDKROOT=${SDKROOT:-$(xcrun --show-sdk-path)} fi -if [[ $PLATFORM == "macosx-arm64" ]]; then - # OpenBLAS - # need a version of OpenBLAS that is suited for gcc >= 11 - basedir=$(python tools/openblas_support.py) - - # use /opt/arm64-builds as a prefix, because that's what the multibuild - # OpenBLAS pkgconfig files state - sudo mkdir -p /opt/arm64-builds/lib - sudo mkdir -p /opt/arm64-builds/include - sudo cp -r $basedir/lib/* /opt/arm64-builds/lib - sudo cp $basedir/include/* /opt/arm64-builds/include - - # we want to force a dynamic linking - sudo rm /opt/arm64-builds/lib/*.a - +if [[ $PLATFORM == "arm64" ]]; then curl -L https://github.com/fxcoudert/gfortran-for-macOS/releases/download/12.1-monterey/gfortran-ARM-12.1-Monterey.dmg -o gfortran.dmg GFORTRAN_SHA256=$(shasum -a 256 gfortran.dmg) KNOWN_SHA256="e2e32f491303a00092921baebac7ffb7ae98de4ca82ebbe9e6a866dd8501acdf gfortran.dmg" @@ -77,3 +57,8 @@ if [[ $PLATFORM == "macosx-arm64" ]]; then sudo installer -pkg /Volumes/gfortran/gfortran.pkg -target / type -p gfortran fi + + +# Install Openblas +python -m pip install -r requirements/openblas.txt +python -c "import scipy_openblas32; print(scipy_openblas32.get_pkg_config())" > $PROJECT_DIR/scipy-openblas.pc diff --git a/tools/wheels/cibw_before_build_win.sh b/tools/wheels/cibw_before_build_win.sh index 63c186f0a989..69b4e7984906 100644 --- a/tools/wheels/cibw_before_build_win.sh +++ b/tools/wheels/cibw_before_build_win.sh @@ -1,39 +1,14 @@ set -xe PROJECT_DIR="$1" -PLATFORM=$(PYTHONPATH=tools python -c "import openblas_support; print(openblas_support.get_plat())") printenv # Update license cat $PROJECT_DIR/tools/wheels/LICENSE_win32.txt >> $PROJECT_DIR/LICENSE.txt # Install Openblas -PYTHONPATH=tools python -c "import openblas_support; openblas_support.make_init('scipy')" -mkdir -p /c/opt/32/lib/pkgconfig -mkdir -p /c/opt/64/lib/pkgconfig +python -m pip install -r requirements/openblas.txt +python -c "import scipy_openblas32; print(scipy_openblas32.get_pkg_config())" > $PROJECT_DIR/scipy-openblas.pc # delvewheel is the equivalent of delocate/auditwheel for windows. python -m pip install delvewheel - -# make the DLL available for tools/wheels/repair_windows.sh. If you change -# this location you need to alter that script. -mkdir -p /c/opt/openblas/openblas_dll -which strip - -target=$(python -c "import tools.openblas_support as obs; plat=obs.get_plat(); ilp64=obs.get_ilp64(); target=f'openblas_{plat}.zip'; obs.download_openblas(target, plat, ilp64);print(target)") - -# The 32/64 bit Fortran wheels are currently coming from different locations. -if [[ $PLATFORM == 'win-32' ]]; then - # 32-bit openBLAS - # Download 32 bit openBLAS and put it into c/opt/32/lib - unzip $target -d /c/opt/ - cp /c/opt/32/bin/*.dll /c/opt/openblas/openblas_dll - # rm /c/opt/openblas/if_32/32/lib/*.dll.a -else - # 64-bit openBLAS - unzip $target -d /c/opt/ - cp /c/opt/64/bin/*.dll /c/opt/openblas/openblas_dll -fi -# attempt to deal with: -# https://github.com/scipy/scipy/pull/20362#issuecomment-2028517797 -python -c "import tools.openblas_support as obs; obs.reformat_pkg_file('C:/opt/')" diff --git a/tools/wheels/cibw_test_command.sh b/tools/wheels/cibw_test_command.sh index 2eb214b3582b..5216423ed658 100644 --- a/tools/wheels/cibw_test_command.sh +++ b/tools/wheels/cibw_test_command.sh @@ -1,12 +1,3 @@ set -xe -PROJECT_DIR="$1" - -# python $PROJECT_DIR/tools/wheels/check_license.py -if [[ $(uname) == "Linux" ]] ; then - python $PROJECT_DIR/tools/openblas_support.py --check_version -fi -echo $? - python -c "import sys; import scipy; sys.exit(not scipy.test())" -echo $? diff --git a/tools/wheels/repair_windows.sh b/tools/wheels/repair_windows.sh index 9e966ec1f8c4..ac20eae6ac47 100644 --- a/tools/wheels/repair_windows.sh +++ b/tools/wheels/repair_windows.sh @@ -2,6 +2,7 @@ set -xe WHEEL="$1" DEST_DIR="$2" +OPENBLAS_DIR=$(python -c"import scipy_openblas32 as sop; print(sop.get_lib_dir())") # create a temporary directory in the destination folder and unpack the wheel # into there @@ -19,7 +20,6 @@ pushd scipy* for f in $(find ./scipy* -name '*.pyd'); do strip $f; done - # now repack the wheel and overwrite the original wheel pack . mv -fv *.whl $WHEEL @@ -27,6 +27,4 @@ mv -fv *.whl $WHEEL cd $DEST_DIR rm -rf tmp -# the libopenblas.dll is placed into this directory in the cibw_before_build -# script. -delvewheel repair --add-path /c/opt/openblas/openblas_dll --no-dll libsf_error_state.dll -w $DEST_DIR $WHEEL +delvewheel repair --add-path $OPENBLAS_DIR --no-dll libsf_error_state.dll -w $DEST_DIR $WHEEL From 6e393bcf9759d7608e4a78ca227c92744ee0db71 Mon Sep 17 00:00:00 2001 From: Max Aehle Date: Tue, 28 May 2024 15:17:26 +0200 Subject: [PATCH 288/500] ENH: sparse.linalg: speed up `spsolve_triangular` (#17924) * ENH: SuperLU supernode matrices in _superluobject. Add a function to generate SuperLU supernode matrices in the C wrapper for SuperLU. It is required by the gstrs wrapper to be added next. * ENH: Wrap the SuperLU gstrs to Python. The function gstrs takes factors L, U of a LU decomposition and a right hand side b, and solves the two triangular systems L@y=b, U@x=y. We will use gstrs to implement a faster version of scipy.sparse.linalg.spsolve_triangular. * ENH: Faster scipy.sparse.linalg.spsolve_triangular. The previous implementation was entirely written in Python. The new implementation uses the SuperLU function gstrs (general sparse triangular solve). --- .../sparse_linalg_spsolve_triangular.py | 47 +++++ scipy/_lib/cobyqa | 2 +- scipy/_lib/tests/test_warnings.py | 1 + scipy/sparse/linalg/_dsolve/_superlumodule.c | 185 ++++++++++++++++++ scipy/sparse/linalg/_dsolve/_superluobject.c | 116 +++++------ scipy/sparse/linalg/_dsolve/_superluobject.h | 14 ++ scipy/sparse/linalg/_dsolve/linsolve.py | 122 ++++++------ .../linalg/_dsolve/tests/test_linsolve.py | 119 ++++++++--- scipy/sparse/tests/test_array_api.py | 11 +- 9 files changed, 469 insertions(+), 148 deletions(-) create mode 100644 benchmarks/benchmarks/sparse_linalg_spsolve_triangular.py diff --git a/benchmarks/benchmarks/sparse_linalg_spsolve_triangular.py b/benchmarks/benchmarks/sparse_linalg_spsolve_triangular.py new file mode 100644 index 000000000000..be5feb66388c --- /dev/null +++ b/benchmarks/benchmarks/sparse_linalg_spsolve_triangular.py @@ -0,0 +1,47 @@ +""" +Check the speed of the sparse triangular solve function. +""" +import numpy as np +from numpy.testing import assert_equal + +from .common import Benchmark, safe_import + +with safe_import(): + from scipy import sparse + from scipy.sparse.linalg import spsolve, spsolve_triangular + +def _create_sparse_poisson1d(n): + # Make Gilbert Strang's favorite matrix + # http://www-math.mit.edu/~gs/PIX/cupcakematrix.jpg + # and take the lower triangular half + P1d = sparse.diags([[-1]*(n-1), [2]*n, [-1]*(n-1)], [-1, 0, 1]) + assert_equal(P1d.shape, (n, n)) + return P1d + + +def _create_sparse_poisson2d_half(n): + P1d = _create_sparse_poisson1d(n) + P2d = sparse.kronsum(P1d, P1d) + assert_equal(P2d.shape, (n*n, n*n)) + return sparse.tril(P2d).tocsr() + + +class Bench(Benchmark): + params = [ + [100,1000], + ["spsolve", "spsolve_triangular"], + ] + param_names = ['(n,n)',"method"] + + def setup(self, n, method): + self.b = np.ones(n*n) + self.P_sparse = _create_sparse_poisson2d_half(n) + + def time_solve(self, n, method): + if method == "spsolve": + spsolve(self.P_sparse, self.b) + elif method == "spsolve_triangular": + spsolve_triangular(self.P_sparse, self.b) + else: + raise NotImplementedError() + diff --git a/scipy/_lib/cobyqa b/scipy/_lib/cobyqa index c516987f947c..dee1a92b5f3f 160000 --- a/scipy/_lib/cobyqa +++ b/scipy/_lib/cobyqa @@ -1 +1 @@ -Subproject commit c516987f947ccef9a16869b9633c20f9b8e0f4fe +Subproject commit dee1a92b5f3fa50c6fce42a38a52ad2f4ebddc4c diff --git a/scipy/_lib/tests/test_warnings.py b/scipy/_lib/tests/test_warnings.py index 24245d9dd6e8..570ea017a8f1 100644 --- a/scipy/_lib/tests/test_warnings.py +++ b/scipy/_lib/tests/test_warnings.py @@ -120,6 +120,7 @@ def test_warning_calls_filters(warning_calls): os.path.join('stats', '_binned_statistic.py'), # gh-19345 os.path.join('stats', 'tests', 'test_axis_nan_policy.py'), # gh-20694 os.path.join('_lib', '_util.py'), # gh-19341 + os.path.join('sparse', 'linalg', '_dsolve', 'linsolve.py'), # gh-17924 "conftest.py", ) bad_filters = [item for item in bad_filters if item.split(':')[0] not in diff --git a/scipy/sparse/linalg/_dsolve/_superlumodule.c b/scipy/sparse/linalg/_dsolve/_superlumodule.c index 1bcbd7544b39..65359a93f32c 100644 --- a/scipy/sparse/linalg/_dsolve/_superlumodule.c +++ b/scipy/sparse/linalg/_dsolve/_superlumodule.c @@ -17,6 +17,7 @@ #include #include "_superluobject.h" +#include "SuperLU/SRC/superlu_enum_consts.h" /* @@ -263,6 +264,179 @@ static PyObject *Py_gstrf(PyObject * self, PyObject * args, return NULL; } +static PyObject *Py_gstrs(PyObject * self, PyObject * args, + PyObject * keywds) +{ + /* compressed sparse column matrix L */ + int L_N = 0, L_nnz = 0; + PyArrayObject *L_nzvals = NULL, *L_rowind = NULL, *L_colptr = NULL; + /* compressed sparse column matrix U */ + int U_N = 0, U_nnz = 0; + PyArrayObject *U_nzvals = NULL, *U_rowind = NULL, *U_colptr = NULL; + /* right hand side / solution */ + PyObject *X_py = NULL; + /* whether the matrix is transposed ('T'), conjugate transposed ('H') or normal ('N') */ + volatile int itrans = 'N'; + volatile jmp_buf* jmpbuf_ptr; + volatile trans_t trans; + SLU_BEGIN_THREADS_DEF; + + static char* kwlist[] = { + "trans", + "L_N", "L_nnz", "L_nzvals", "L_rowind", "L_colptr", + "U_N", "U_nnz", "U_nzvals", "U_rowind", "U_colptr", + "B", NULL + }; + + /* Parse and check input arguments. */ + int res = PyArg_ParseTupleAndKeywords(args, keywds, "CiiO!O!O!iiO!O!O!O", kwlist, + &itrans, + &L_N, &L_nnz, &PyArray_Type, &L_nzvals, &PyArray_Type, &L_rowind, &PyArray_Type, &L_colptr, + &U_N, &U_nnz, &PyArray_Type, &U_nzvals, &PyArray_Type, &U_rowind, &PyArray_Type, &U_colptr, + &X_py ); + if (!res) + return NULL; + + if (itrans == 'n' || itrans == 'N') { + trans = NOTRANS; + } else if (itrans == 't' || itrans == 'T') { + trans = TRANS; + } else if (itrans == 'h' || itrans == 'H') { + trans = CONJ; + } else { + PyErr_SetString(PyExc_ValueError, "trans must be N, T, or H"); + return NULL; + } + + if (L_N!=U_N) { + PyErr_SetString(PyExc_ValueError, "L and U must have the same dimension"); + return NULL; + } + + if (!_CHECK_INTEGER(L_rowind) || !_CHECK_INTEGER(L_colptr) || + !_CHECK_INTEGER(U_rowind) || !_CHECK_INTEGER(U_colptr) ) { + PyErr_SetString(PyExc_TypeError, "row indices and column pointers must be of type cint"); + return NULL; + } + + int L_type = PyArray_TYPE((PyArrayObject*)L_nzvals); + int U_type = PyArray_TYPE((PyArrayObject*)U_nzvals); + if (L_type != U_type) { + PyErr_SetString(PyExc_TypeError, + "nzvals types of L and U differ"); + return NULL; + } + if (!CHECK_SLU_TYPE(L_type)) { + PyErr_SetString(PyExc_TypeError, + "nzvals is not of a type supported by SuperLU"); + return NULL; + } + + /* Create SuperLU matrices out of L and U. */ + int* L_col_to_sup = intMalloc(L_N+1); + int* L_sup_to_col = intMalloc(L_N+1); + for(int i=0; i<=L_N; i++){ + L_col_to_sup[i] = i; + L_sup_to_col[i] = i; + } + L_col_to_sup[L_N] = L_N - 1; + SuperMatrix L_super = {0}; + SuperMatrix U_super = {0}; + int L_conv_err = SparseFormat_from_spMatrix( + &L_super, L_N, L_N, L_nnz, -1, + (PyArrayObject*)L_nzvals, (PyArrayObject*)L_rowind, (PyArrayObject*)L_colptr, + L_type, SLU_SC, SLU_TRLU, L_col_to_sup, L_sup_to_col); + if (L_conv_err) { + return NULL; + } + int U_conv_err = SparseFormat_from_spMatrix( + &U_super, U_N, U_N, U_nnz, 0, + (PyArrayObject*)U_nzvals, (PyArrayObject*)U_rowind, (PyArrayObject*)U_colptr, + U_type, SLU_NC, SLU_TRU, NULL, NULL); + if (U_conv_err) { + Destroy_SuperMatrix_Store((SuperMatrix*)&L_super); + return NULL; + } + + /* Read right-hand-side (i.e., solution) vector. */ + PyArrayObject* X_arr = (PyArrayObject*)PyArray_FROMANY( + (PyObject*)X_py, L_type, 1, 2, + NPY_ARRAY_F_CONTIGUOUS | NPY_ARRAY_ENSURECOPY); + if (X_arr == NULL) { + SUPERLU_FREE((void*)L_col_to_sup); + SUPERLU_FREE((void*)L_sup_to_col); + Destroy_SuperMatrix_Store((SuperMatrix*)&L_super); + Destroy_SuperMatrix_Store((SuperMatrix*)&U_super); + return NULL; + } + if (PyArray_DIM((PyArrayObject*)X_arr, 0) != L_N) { + PyErr_SetString(PyExc_ValueError, + "right hand side array has invalid shape"); + SUPERLU_FREE((void*)L_col_to_sup); + SUPERLU_FREE((void*)L_sup_to_col); + Destroy_SuperMatrix_Store((SuperMatrix*)&L_super); + Destroy_SuperMatrix_Store((SuperMatrix*)&U_super); + Py_DECREF(X_arr); + return NULL; + } + + SuperMatrix X; + if (DenseSuper_from_Numeric((SuperMatrix*)&X, (PyObject*)X_arr)) { + SUPERLU_FREE((void*)L_col_to_sup); + SUPERLU_FREE((void*)L_sup_to_col); + Destroy_SuperMatrix_Store((SuperMatrix*)&L_super); + Destroy_SuperMatrix_Store((SuperMatrix*)&U_super); + Py_DECREF(X_arr); + return NULL; + } /* X and X_arr share the same data but X_arr "owns" it. */ + + /* Call SuperLU functions. */ + int info=0; + SuperLUStat_t stat = { 0 }; + StatInit((SuperLUStat_t *)&stat); + int* perm_c = intMalloc(L_N); + for (int i=0; i>> import numpy as np - >>> from scipy.sparse import csr_matrix + >>> from scipy.sparse import csc_array >>> from scipy.sparse.linalg import spsolve_triangular - >>> A = csr_matrix([[3, 0, 0], [1, -1, 0], [2, 0, 1]], dtype=float) + >>> 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) @@ -662,20 +660,42 @@ def spsolve_triangular(A, b, lower=True, overwrite_A=False, overwrite_b=False, """ if is_pydata_spmatrix(A): - A = A.to_scipy_sparse().tocsr() - - # Check the input for correct type and format. - if not (issparse(A) and A.format == "csr"): - warn('CSR matrix format is required. Converting to CSR matrix.', + 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 = csr_matrix(A) + A = csc_matrix(A) elif not overwrite_A: A = A.copy() - if A.shape[0] != A.shape[1]: + + 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(invdiag) + else: + A = (A.T @ diags(invdiag)).T + # sum duplicates for non-canonical format A.sum_duplicates() @@ -684,63 +704,39 @@ def spsolve_triangular(A, b, lower=True, overwrite_A=False, overwrite_b=False, if b.ndim not in [1, 2]: raise ValueError( f'b must have 1 or 2 dims but its shape is {b.shape}.') - if A.shape[0] != b.shape[0]: + 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}.' ) - # Init x as (a copy of) b. - x_dtype = np.result_type(A.data, b, np.float64) - if overwrite_b: - if np.can_cast(b.dtype, x_dtype, casting='same_kind'): - x = b - else: - raise ValueError( - f'Cannot overwrite b (dtype {b.dtype}) with result ' - f'of type {x_dtype}.' - ) - else: - x = b.astype(x_dtype, copy=True) + 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() - # Choose forward or backward order. if lower: - row_indices = range(len(b)) + L = A + U = csc_matrix((N, N), dtype=result_dtype) else: - row_indices = range(len(b) - 1, -1, -1) - - # Fill x iteratively. - for i in row_indices: - - # Get indices for i-th row. - indptr_start = A.indptr[i] - indptr_stop = A.indptr[i + 1] + L = eye(N, dtype=result_dtype, format='csc') + U = A + U.setdiag(0) - if lower: - A_diagonal_index_row_i = indptr_stop - 1 - A_off_diagonal_indices_row_i = slice(indptr_start, indptr_stop - 1) - else: - A_diagonal_index_row_i = indptr_start - A_off_diagonal_indices_row_i = slice(indptr_start + 1, indptr_stop) - - # Check regularity and triangularity of A. - if not unit_diagonal and (indptr_stop <= indptr_start - or A.indices[A_diagonal_index_row_i] < i): - raise LinAlgError( - f'A is singular: diagonal {i} is zero.') - if not unit_diagonal and A.indices[A_diagonal_index_row_i] > i: - raise LinAlgError( - 'A is not triangular: A[{}, {}] is nonzero.' - ''.format(i, A.indices[A_diagonal_index_row_i])) - - # Incorporate off-diagonal entries. - A_column_indices_in_row_i = A.indices[A_off_diagonal_indices_row_i] - A_values_in_row_i = A.data[A_off_diagonal_indices_row_i] - x[i] -= np.dot(x[A_column_indices_in_row_i].T, A_values_in_row_i) + 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.') - # Compute i-th entry of x. - if not unit_diagonal: - x[i] /= A.data[A_diagonal_index_row_i] + if not unit_diagonal: + invdiag = invdiag.reshape(-1, *([1] * (len(x.shape) - 1))) + x = x * invdiag return x + diff --git a/scipy/sparse/linalg/_dsolve/tests/test_linsolve.py b/scipy/sparse/linalg/_dsolve/tests/test_linsolve.py index fe900d8bf9a0..836b0127ff66 100644 --- a/scipy/sparse/linalg/_dsolve/tests/test_linsolve.py +++ b/scipy/sparse/linalg/_dsolve/tests/test_linsolve.py @@ -722,17 +722,59 @@ def worker(): assert_equal(len(oks), 20) +class TestGstrsErrors: + def setup_method(self): + self.A = 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 = scipy.sparse.tril(self.A, format='csc') + U = scipy.sparse.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 = scipy.sparse.tril(self.A[0:2,0:2], format='csc') + U = scipy.sparse.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 = scipy.sparse.tril(self.A, format='csc') + U = scipy.sparse.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 = scipy.sparse.tril(self.A.astype(np.float32), format='csc') + U = scipy.sparse.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 = scipy.sparse.tril(self.A.astype(np.uint8), format='csc') + U = scipy.sparse.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) - def test_zero_diagonal(self): + @pytest.mark.parametrize("fmt",["csr","csc"]) + def test_zero_diagonal(self,fmt): n = 5 rng = np.random.default_rng(43876432987) A = rng.standard_normal((n, n)) b = np.arange(n) - A = scipy.sparse.tril(A, k=0, format='csr') + A = scipy.sparse.tril(A, k=0, format=fmt) x = spsolve_triangular(A, b, unit_diagonal=True, lower=True) @@ -743,12 +785,16 @@ def test_zero_diagonal(self): A = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0]], dtype=np.float64) b = np.array([1., 2., 3.]) with suppress_warnings() as sup: - sup.filter(SparseEfficiencyWarning, "CSR matrix format is") + sup.filter(SparseEfficiencyWarning, "CSC or CSR matrix format is") spsolve_triangular(A, b, unit_diagonal=True) - def test_singular(self): + @pytest.mark.parametrize("fmt",["csr","csc"]) + def test_singular(self,fmt): n = 5 - A = csr_matrix((n, n)) + if fmt == "csr": + A = csr_matrix((n, n)) + else: + A = csc_matrix((n, n)) b = np.arange(n) for lower in (True, False): assert_raises(scipy.linalg.LinAlgError, @@ -774,32 +820,53 @@ def test_input_types(self): assert_array_almost_equal(A.dot(x), b) @pytest.mark.slow - @pytest.mark.timeout(120) # prerelease_deps_coverage_64bit_blas job @sup_sparse_efficiency - def test_random(self): - def random_triangle_matrix(n, lower=True): - A = scipy.sparse.random(n, n, density=0.1, format='coo') + @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): + 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 = scipy.sparse.random(n, n, density=0.1, format='lil', dtype=dtype, + random_state=rng, data_rvs=rvs) if lower: - A = scipy.sparse.tril(A) + A = scipy.sparse.tril(A, format="lil") else: - A = scipy.sparse.triu(A) - A = A.tocsr(copy=False) + A = scipy.sparse.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 np.random.seed(1234) - for lower in (True, False): - for n in (10, 10**2, 10**3): - A = random_triangle_matrix(n, lower=lower) - for m in (1, 10): - for b in (np.random.rand(n, m), - np.random.randint(-9, 9, (n, m)), - np.random.randint(-9, 9, (n, m)) + - np.random.randint(-9, 9, (n, m)) * 1j): - x = spsolve_triangular(A, b, lower=lower) - assert_array_almost_equal(A.dot(x), b) - x = spsolve_triangular(A, b, lower=lower, - unit_diagonal=True) - A.setdiag(1) - assert_array_almost_equal(A.dot(x), b) + A = random_triangle_matrix(n, lower=lower) + if choice_of_b == "floats": + b = np.random.rand(n, m) + elif choice_of_b == "ints": + b = np.random.randint(-9, 9, (n, m)) + elif choice_of_b == "complexints": + b = np.random.randint(-9, 9, (n, m)) + np.random.randint(-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) + + + diff --git a/scipy/sparse/tests/test_array_api.py b/scipy/sparse/tests/test_array_api.py index 73fb46da5913..a3be7b868b1a 100644 --- a/scipy/sparse/tests/test_array_api.py +++ b/scipy/sparse/tests/test_array_api.py @@ -256,13 +256,18 @@ def test_spsolve(B): ) -def test_spsolve_triangular(): - X = scipy.sparse.csr_array([ +@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]) From b05493aa6df5e80072134102b37bcb71089a89dd Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Tue, 28 May 2024 15:54:00 +0100 Subject: [PATCH 289/500] REV: revert `cobyqa` submodule update in gh-17924 (#20819) --- scipy/_lib/cobyqa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/_lib/cobyqa b/scipy/_lib/cobyqa index dee1a92b5f3f..7f40b6dd5452 160000 --- a/scipy/_lib/cobyqa +++ b/scipy/_lib/cobyqa @@ -1 +1 @@ -Subproject commit dee1a92b5f3fa50c6fce42a38a52ad2f4ebddc4c +Subproject commit 7f40b6dd54525d7aa722f9558c186d39d163af94 From 12b5a19147385bc2f2f307ad54486684754f11c3 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 28 May 2024 09:19:08 -0700 Subject: [PATCH 290/500] DOC: optimize.elementwise: change function names [skip ci] --- scipy/optimize/__init__.py | 2 +- scipy/optimize/_elementwise.py | 44 +++++++++++++++++----------------- scipy/optimize/elementwise.py | 8 +++---- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/scipy/optimize/__init__.py b/scipy/optimize/__init__.py index 029ad97ea150..bcbc670393a2 100644 --- a/scipy/optimize/__init__.py +++ b/scipy/optimize/__init__.py @@ -116,7 +116,7 @@ shgo - Simplicial homology global optimizer. dual_annealing - Dual annealing stochastic optimizer. direct - DIRECT (Dividing Rectangles) optimizer. - + Least-squares and curve fitting =============================== diff --git a/scipy/optimize/_elementwise.py b/scipy/optimize/_elementwise.py index 105fe61da729..f7ef41718bed 100644 --- a/scipy/optimize/_elementwise.py +++ b/scipy/optimize/_elementwise.py @@ -3,16 +3,16 @@ from scipy._lib._util import _RichResult -def rootfind(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=None): +def find_root(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=None): """Find the root of a monotonic, real-valued function of a real variable. - For each element of the output of `f`, `rootfind` seeks the scalar + For each element of the output of `f`, `find_root` seeks the scalar root that makes the element 0. This function currently uses Chandrupatla's bracketing algorithm [1]_ and therefore requires argument ``init`` to provide a bracket around the root: the function values at the two endpoints must have opposite signs. - Provided a valid bracket, `rootfind` is guaranteed to converge to a solution that + Provided a valid bracket, `find_root` is guaranteed to converge to a solution that satisfies the provided `tolerances` under mild requirements (e.g. the function is defined and the root exists within the bracket). @@ -30,7 +30,7 @@ def rootfind(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=Non with `x`. ``f`` must be an elementwise function: each element ``f(x)[i]`` - must equal ``f(x[i])`` for all indices ``i``. `rootfind` seeks an + must equal ``f(x[i])`` for all indices ``i``. `find_root` seeks an array ``x`` such that ``f(x)`` is an array of zeros. init : 2-tuple of real number arrays The lower and upper endpoints of a bracket surrounding the desired root. @@ -61,10 +61,10 @@ def rootfind(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=Non An optional user-supplied function to be called before the first iteration and after each iteration. Called as ``callback(res)``, where ``res`` is a ``_RichResult`` - similar to that returned by `rootfind` (but containing the current + similar to that returned by `find_root` (but containing the current iterate's values of all variables). If `callback` raises a ``StopIteration``, the algorithm will terminate immediately and - `rootfind` will return a result. + `find_root` will return a result. Returns ------- @@ -158,7 +158,7 @@ def rootfind(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=Non >>> res_bracket = elementwise.bracket_root(f, 0) >>> res_bracket.bracket (2.0, 4.0) - >>> res_root = elementwise.rootfind(f, res_bracket.bracket) + >>> res_root = elementwise.find_root(f, res_bracket.bracket) >>> res_root.x 2.0945514815423265 @@ -166,7 +166,7 @@ def rootfind(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=Non >>> res_bracket = elementwise.bracket_root(f, 0, args=(c,)) >>> res_bracket.bracket (array([1., 1., 2.]), array([2., 2., 4.])) - >>> res_root = elementwise.rootfind(f, res_bracket.bracket, args=(c,)) + >>> res_root = elementwise.find_root(f, res_bracket.bracket, args=(c,)) >>> res_root.x array([1.8932892 , 2. , 2.09455148]) @@ -202,15 +202,15 @@ def _callback(res): return reformat_result(res) -def minimize(f, init, /, *, args=(), tolerances=None, maxiter=100, callback=None): +def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback=None): """Find the minimizer of an unimodal, real-valued function of a real variable. - For each element of the output of `f`, `minimize` seeks the scalar minimizer + For each element of the output of `f`, `find_minimum` seeks the scalar minimizer that minimizes the element. This function currently uses Chandrupatla's bracketing minimization algorithm [1]_ and therefore requires argument ``init`` to provide a three-point minimization bracket: - Provided a valid bracket, `minimize` is guaranteed to converge to a local + Provided a valid bracket, `find_minimum` is guaranteed to converge to a local minimizer that satisfies the provided `tolerances` under mild requirements (e.g. the function is defined and minimum exists within the bracket). @@ -228,7 +228,7 @@ def minimize(f, init, /, *, args=(), tolerances=None, maxiter=100, callback=None with `x`. ``f`` must be an elementwise function: each element ``f(x)[i]`` - must equal ``f(x[i])`` for all indices ``i``. `minimize` seeks an + must equal ``f(x[i])`` for all indices ``i``. `find_minimum` seeks an array ``x`` such that ``f(x)`` is an array of local minima. init : 3-tuple of real number arrays The abscissae of a standard scalar minimization bracket. A bracket is @@ -257,10 +257,10 @@ def minimize(f, init, /, *, args=(), tolerances=None, maxiter=100, callback=None An optional user-supplied function to be called before the first iteration and after each iteration. Called as ``callback(res)``, where ``res`` is a ``_RichResult`` - similar to that returned by `minimize` (but containing the current + similar to that returned by `find_minimum` (but containing the current iterate's values of all variables). If `callback` raises a ``StopIteration``, the algorithm will terminate immediately and - `rootfind` will return a result. + `find_root` will return a result. Returns ------- @@ -337,7 +337,7 @@ def minimize(f, init, /, *, args=(), tolerances=None, maxiter=100, callback=None >>> res_bracket = elementwise.bracket_minimum(f, 0) >>> res_bracket.bracket (0.0, 0.5, 1.5) - >>> res_minimize = elementwise.minimize(f, res_bracket.bracket) + >>> res_minimize = elementwise.find_minimum(f, res_bracket.bracket) >>> res_minimize.x 1.0 @@ -345,7 +345,7 @@ def minimize(f, init, /, *, args=(), tolerances=None, maxiter=100, callback=None >>> res_bracket = elementwise.bracket_minimum(f, 0, args=(c,)) >>> res_bracket.bracket (array([0. , 0.5, 0.5]), array([0.5, 1.5, 1.5]), array([1.5, 2.5, 2.5])) - >>> res_minimize = elementwise.minimize(f, res_bracket.bracket, args=(c,)) + >>> res_minimize = elementwise.find_minimum(f, res_bracket.bracket, args=(c,)) >>> res_minimize.x array([1. , 1.5, 2. ]) """ @@ -483,7 +483,7 @@ def bracket_root(f, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, args=() See Also -------- - rootfind + find_root Examples -------- @@ -493,7 +493,7 @@ def bracket_root(f, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, args=() >>> res_bracket = elementwise.bracket_root(f, 0) >>> res_bracket.bracket (2.0, 4.0) - >>> res_root = elementwise.rootfind(f, res_bracket.bracket) + >>> res_root = elementwise.find_root(f, res_bracket.bracket) >>> res_root.x 2.0945514815423265 @@ -501,7 +501,7 @@ def bracket_root(f, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, args=() >>> res_bracket = elementwise.bracket_root(f, 0, args=(c,)) >>> res_bracket.bracket (array([1., 1., 2.]), array([2., 2., 4.])) - >>> res_root = elementwise.rootfind(f, res_bracket.bracket, args=(c,)) + >>> res_root = elementwise.find_root(f, res_bracket.bracket, args=(c,)) >>> res_root.x array([1.8932892 , 2. , 2.09455148]) @@ -625,7 +625,7 @@ def bracket_minimum(f, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, See Also -------- scipy.optimize.bracket - scipy.optimize.elementwise.minimize + scipy.optimize.elementwise.find_minimum Examples -------- @@ -635,7 +635,7 @@ def bracket_minimum(f, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, >>> res_bracket = elementwise.bracket_minimum(f, 0) >>> res_bracket.bracket (0.0, 0.5, 1.5) - >>> res_minimize = elementwise.minimize(f, res_bracket.bracket) + >>> res_minimize = elementwise.find_minimum(f, res_bracket.bracket) >>> res_minimize.x 1.0 @@ -643,7 +643,7 @@ def bracket_minimum(f, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, >>> res_bracket = elementwise.bracket_minimum(f, 0, args=(c,)) >>> res_bracket.bracket (array([0. , 0.5, 0.5]), array([0.5, 1.5, 1.5]), array([1.5, 2.5, 2.5])) - >>> res_minimize = elementwise.minimize(f, res_bracket.bracket, args=(c,)) + >>> res_minimize = elementwise.find_minimum(f, res_bracket.bracket, args=(c,)) >>> res_minimize.x array([1. , 1.5, 2. ]) diff --git a/scipy/optimize/elementwise.py b/scipy/optimize/elementwise.py index 4c8399f273be..87a8841f4aa6 100644 --- a/scipy/optimize/elementwise.py +++ b/scipy/optimize/elementwise.py @@ -20,7 +20,7 @@ .. autosummary:: :toctree: generated/ - rootfind + find_root bracket_root Rootfinding @@ -29,10 +29,10 @@ .. autosummary:: :toctree: generated/ - minimize + find_minimum bracket_minimum """ -from ._elementwise import rootfind, bracket_root, minimize, bracket_minimum # noqa: F401, E501 +from ._elementwise import find_root, find_minimum, bracket_root, bracket_minimum # noqa: F401, E501 -__all__ = ["rootfind", "bracket_root", "minimize", "bracket_minimum"] +__all__ = ["find_root", "find_minimum", "bracket_root", "bracket_minimum"] From 6565ce27a24b2d4fbc67f1e9e9fcedb9bab15775 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 28 May 2024 10:04:28 -0700 Subject: [PATCH 291/500] TST: optimize.elementwise.find_root: (effectively) add all tests [skip ci] --- scipy/optimize/_elementwise.py | 8 +++-- scipy/optimize/tests/test_chandrupatla.py | 44 +++++++++++++++++++++-- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/scipy/optimize/_elementwise.py b/scipy/optimize/_elementwise.py index f7ef41718bed..4fa943b00593 100644 --- a/scipy/optimize/_elementwise.py +++ b/scipy/optimize/_elementwise.py @@ -181,7 +181,7 @@ def reformat_result(res_in): res_out.nfev = res_in.nfev res_out.nit = res_in.nit res_out.bracket = (res_in.xl, res_in.xr) - res_out.f_bracket = (res_in.fl, res_in.fl) + res_out.f_bracket = (res_in.fl, res_in.fr) res_out._order_keys = ['success', 'status', 'x', 'f_x', 'nfev', 'nit', 'bracket', 'f_bracket'] return res_out @@ -189,7 +189,8 @@ def reformat_result(res_in): xl, xr = init default_tolerances = dict(xatol=None, xrtol=None, fatol=None, frtol=0) tolerances = {} if tolerances is None else tolerances - tolerances.update(default_tolerances) + default_tolerances.update(tolerances) + tolerances = default_tolerances if callable(callback): def _callback(res): @@ -367,7 +368,8 @@ def reformat_result(res_in): xl, xm, xr = init default_tolerances = dict(xatol=None, xrtol=None, fatol=None, frtol=None) tolerances = {} if tolerances is None else tolerances - tolerances.update(default_tolerances) + default_tolerances.update(tolerances) + tolerances = default_tolerances if callable(callback): def _callback(res): diff --git a/scipy/optimize/tests/test_chandrupatla.py b/scipy/optimize/tests/test_chandrupatla.py index 1300c08784b5..a52207191239 100644 --- a/scipy/optimize/tests/test_chandrupatla.py +++ b/scipy/optimize/tests/test_chandrupatla.py @@ -8,13 +8,53 @@ from scipy._lib._array_api import (array_namespace, xp_assert_close, xp_assert_equal, xp_assert_less, xp_minimum, is_numpy, is_cupy) -from scipy.optimize._chandrupatla import (_chandrupatla_minimize, - _chandrupatla as _chandrupatla_root) +from scipy.optimize.elementwise import find_minimum, find_root +from scipy.optimize._chandrupatla import _chandrupatla_minimize from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS from itertools import permutations from .test_zeros import TestScalarRootFinders + +# These tests were originally written for the private `optimize._chandrupatla` +# interfaces, but now we want the tests to check the behavior of the public +# `optimize.elementwise`. Therefore, rather than importing +# `_chandrupatla`/`_chandrupatla_minimize` from `_chandrupatla.py`, we import +# `find_root`/`find_minimum` from `optimize.elementwise` and wrap those +# functions to conform to the private interface. This is a little strange, since +# it effectly just inverts the interface transformation done within the +# `find_root`/`find_minimum` functions, but it allows us to run the original, +# unmodified tests on the public interfaces, and it simplifies the PR that adds +# the public interfaces. We'll refactor this when we want to @parametrize the +# tests over multiple `method`s. +def _chandrupatla_root(f, a, b, **kwargs): + # avoid passing arguments to `find_minimum` to this function + tol_keys = {'xatol', 'xrtol', 'fatol', 'frtol'} + tolerances = {key: kwargs.pop(key) for key in tol_keys if key in kwargs} + bracket = (a, b) + _callback = kwargs.pop('callback', None) + if callable(_callback): + def callback(res): + res.xl, res.xr = res.bracket + res.fl, res.fr = res.f_bracket + res.fun = res.f_x + del res.bracket + del res.f_bracket + del res.f_x + return _callback(res) + else: + callback = _callback + + res = find_root(f, bracket, tolerances=tolerances, callback=callback, **kwargs) + res.xl, res.xr = res.bracket + res.fl, res.fr = res.f_bracket + res.fun = res.f_x + del res.bracket + del res.f_bracket + del res.f_x + return res + + def f1(x): return 100*(1 - x**3.)**2 + (1-x**2.) + 2*(1-x)**2. From ab78f1884014664a1585d379ec98b89291eafe9e Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 28 May 2024 10:45:24 -0700 Subject: [PATCH 292/500] TST: optimize.dual_annealing: add fail_slow exception missing from original PR [skip ci] --- scipy/optimize/tests/test__dual_annealing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/optimize/tests/test__dual_annealing.py b/scipy/optimize/tests/test__dual_annealing.py index 6819d7f2be52..041dffc5b509 100644 --- a/scipy/optimize/tests/test__dual_annealing.py +++ b/scipy/optimize/tests/test__dual_annealing.py @@ -367,6 +367,7 @@ def func(x): assert_allclose(ret_bounds_list.fun, ret_bounds_class.fun, atol=1e-9) assert ret_bounds_list.nfev == ret_bounds_class.nfev + @pytest.mark.fail_slow(5) def test_callable_jac_hess_with_args_gh11052(self): # dual_annealing used to fail when `jac` was callable and `args` were # used; check that this is resolved. Example is from gh-11052. From c715cd1638bc5c53790155e52fb7eeeebe04673c Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 28 May 2024 11:33:07 -0700 Subject: [PATCH 293/500] TST: optimize.elementwise: (effectively) add all remaining tests --- scipy/optimize/tests/test_bracket.py | 37 +++++++++++- scipy/optimize/tests/test_chandrupatla.py | 70 +++++++++++++---------- 2 files changed, 75 insertions(+), 32 deletions(-) diff --git a/scipy/optimize/tests/test_bracket.py b/scipy/optimize/tests/test_bracket.py index dc39b5fe5286..27688c3e7feb 100644 --- a/scipy/optimize/tests/test_bracket.py +++ b/scipy/optimize/tests/test_bracket.py @@ -3,10 +3,41 @@ import numpy as np from numpy.testing import assert_array_less, assert_allclose, assert_equal -from scipy.optimize._bracket import _bracket_root, _bracket_minimum, _ELIMITS +from scipy.optimize._bracket import _ELIMITS +from scipy.optimize.elementwise import bracket_root, bracket_minimum import scipy._lib._elementwise_iterative_method as eim from scipy import stats + +# These tests were originally written for the private `optimize._bracket` +# interfaces, but now we want the tests to check the behavior of the public +# `optimize.elementwise` interfaces. Therefore, rather than importing +# `_bracket_root`/`_bracket_minimum` from `_bracket.py`, we import +# `bracket_root`/`bracket_minimum` from `optimize.elementwise` and wrap those +# functions to conform to the private interface. This may look a little strange, +# since it effectly just inverts the interface transformation done within the +# `bracket_root`/`bracket_minimum` functions, but it allows us to run the original, +# unmodified tests on the public interfaces, simplifying the PR that adds +# the public interfaces. We'll refactor this when we want to @parametrize the +# tests over multiple `method`s. +def _bracket_root(*args, **kwargs): + res = bracket_root(*args, **kwargs) + res.xl, res.xr = res.bracket + res.fl, res.fr = res.f_bracket + del res.bracket + del res.f_bracket + return res + + +def _bracket_minimum(*args, **kwargs): + res = bracket_minimum(*args, **kwargs) + res.xl, res.xm, res.xr = res.bracket + res.fl, res.fm, res.fr = res.f_bracket + del res.bracket + del res.f_bracket + return res + + class TestBracketRoot: @pytest.mark.parametrize("seed", (615655101, 3141866013, 238075752)) @pytest.mark.parametrize("use_xmin", (False, True)) @@ -581,12 +612,12 @@ def test_scalar_with_limit_right(self, xl0, xm0, xr0, xmax, args): ( ( # Case 1: # Initial bracket. - 0.2, + 0.2, 0.3, 0.4, # Function slopes down to the right from the bracket to a minimum # at 1.0. xmax is also at 1.0 - None, + None, 1.0, (1.0, 0.0) ), diff --git a/scipy/optimize/tests/test_chandrupatla.py b/scipy/optimize/tests/test_chandrupatla.py index a52207191239..d341fc96261e 100644 --- a/scipy/optimize/tests/test_chandrupatla.py +++ b/scipy/optimize/tests/test_chandrupatla.py @@ -9,7 +9,6 @@ xp_assert_less, xp_minimum, is_numpy, is_cupy) from scipy.optimize.elementwise import find_minimum, find_root -from scipy.optimize._chandrupatla import _chandrupatla_minimize from scipy.optimize._tstutils import _CHANDRUPATLA_TESTS from itertools import permutations @@ -18,41 +17,54 @@ # These tests were originally written for the private `optimize._chandrupatla` # interfaces, but now we want the tests to check the behavior of the public -# `optimize.elementwise`. Therefore, rather than importing +# `optimize.elementwise` interfaces. Therefore, rather than importing # `_chandrupatla`/`_chandrupatla_minimize` from `_chandrupatla.py`, we import # `find_root`/`find_minimum` from `optimize.elementwise` and wrap those -# functions to conform to the private interface. This is a little strange, since -# it effectly just inverts the interface transformation done within the +# functions to conform to the private interface. This may look a little strange, +# since it effectly just inverts the interface transformation done within the # `find_root`/`find_minimum` functions, but it allows us to run the original, -# unmodified tests on the public interfaces, and it simplifies the PR that adds +# unmodified tests on the public interfaces, simplifying the PR that adds # the public interfaces. We'll refactor this when we want to @parametrize the # tests over multiple `method`s. -def _chandrupatla_root(f, a, b, **kwargs): - # avoid passing arguments to `find_minimum` to this function - tol_keys = {'xatol', 'xrtol', 'fatol', 'frtol'} - tolerances = {key: kwargs.pop(key) for key in tol_keys if key in kwargs} - bracket = (a, b) - _callback = kwargs.pop('callback', None) - if callable(_callback): - def callback(res): +def _wrap_chandrupatla(func): + def _chandrupatla_wrapper(f, *bracket, **kwargs): + # avoid passing arguments to `find_minimum` to this function + tol_keys = {'xatol', 'xrtol', 'fatol', 'frtol'} + tolerances = {key: kwargs.pop(key) for key in tol_keys if key in kwargs} + _callback = kwargs.pop('callback', None) + if callable(_callback): + def callback(res): + if func == find_root: + res.xl, res.xr = res.bracket + res.fl, res.fr = res.f_bracket + else: + res.xl, res.xm, res.xr = res.bracket + res.fl, res.fm, res.fr = res.f_bracket + res.fun = res.f_x + del res.bracket + del res.f_bracket + del res.f_x + return _callback(res) + else: + callback = _callback + + res = func(f, bracket, tolerances=tolerances, callback=callback, **kwargs) + if func == find_root: res.xl, res.xr = res.bracket res.fl, res.fr = res.f_bracket - res.fun = res.f_x - del res.bracket - del res.f_bracket - del res.f_x - return _callback(res) - else: - callback = _callback - - res = find_root(f, bracket, tolerances=tolerances, callback=callback, **kwargs) - res.xl, res.xr = res.bracket - res.fl, res.fr = res.f_bracket - res.fun = res.f_x - del res.bracket - del res.f_bracket - del res.f_x - return res + else: + res.xl, res.xm, res.xr = res.bracket + res.fl, res.fm, res.fr = res.f_bracket + res.fun = res.f_x + del res.bracket + del res.f_bracket + del res.f_x + return res + return _chandrupatla_wrapper + + +_chandrupatla_root = _wrap_chandrupatla(find_root) +_chandrupatla_minimize = _wrap_chandrupatla(find_minimum) def f1(x): From 0e01dac06eb8106f1696f9782eedf80ba47d7918 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 28 May 2024 11:39:42 -0700 Subject: [PATCH 294/500] TST: optimize.elementwise: improve bracket function test coverage [skip ci] --- scipy/optimize/tests/test_bracket.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scipy/optimize/tests/test_bracket.py b/scipy/optimize/tests/test_bracket.py index 27688c3e7feb..ac13c68de4a7 100644 --- a/scipy/optimize/tests/test_bracket.py +++ b/scipy/optimize/tests/test_bracket.py @@ -262,6 +262,9 @@ def test_input_validation(self): _bracket_root(lambda x: x, -4, 4, maxiter=1.5) with pytest.raises(ValueError, match=message): _bracket_root(lambda x: x, -4, 4, maxiter=-1) + with pytest.raises(ValueError, match=message): + _bracket_root(lambda x: x, -4, 4, maxiter="shrubbery") + def test_special_cases(self): # Test edge cases and other special cases @@ -492,6 +495,8 @@ def test_input_validation(self): _bracket_minimum(lambda x: x**2, 4+1j) with pytest.raises(ValueError, match=message): _bracket_minimum(lambda x: x**2, -4, xl0='hello') + with pytest.raises(ValueError, match=message): + _bracket_minimum(lambda x: x**2, -4, xr0='farcical aquatic ceremony') with pytest.raises(ValueError, match=message): _bracket_minimum(lambda x: x**2, -4, xmin=np) with pytest.raises(ValueError, match=message): @@ -513,6 +518,8 @@ def test_input_validation(self): _bracket_minimum(lambda x: x**2, -4, xr0=4, maxiter=1.5) with pytest.raises(ValueError, match=message): _bracket_minimum(lambda x: x**2, -4, xr0=4, maxiter=-1) + with pytest.raises(ValueError, match=message): + _bracket_minimum(lambda x: x**2, -4, xr0=4, maxiter="ekki") @pytest.mark.parametrize("xl0", [0.0, None]) @pytest.mark.parametrize("xm0", (0.05, 0.1, 0.15)) From 7d738e67e1cfc08066c21d82847dc341e30f52c4 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 28 May 2024 11:50:45 -0700 Subject: [PATCH 295/500] DOC: optimize.elementwise: add TOC to docs, hopefully --- doc/source/reference/optimize.elementwise.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 doc/source/reference/optimize.elementwise.rst diff --git a/doc/source/reference/optimize.elementwise.rst b/doc/source/reference/optimize.elementwise.rst new file mode 100644 index 000000000000..a2c8383e4cef --- /dev/null +++ b/doc/source/reference/optimize.elementwise.rst @@ -0,0 +1,4 @@ +.. automodule:: scipy.optimize.elementwise + :no-members: + :no-inherited-members: + :no-special-members: From 9f10349b9d0bd2553c15b9ee23fe85394b219ab2 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 28 May 2024 12:50:16 -0700 Subject: [PATCH 296/500] TST/DOC: optimize.elementwise: fixup test failure, TOC depth --- scipy/_lib/tests/test_public_api.py | 3 ++- scipy/optimize/__init__.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scipy/_lib/tests/test_public_api.py b/scipy/_lib/tests/test_public_api.py index 0e789f9ccb33..2a5a5fcdad8b 100644 --- a/scipy/_lib/tests/test_public_api.py +++ b/scipy/_lib/tests/test_public_api.py @@ -53,6 +53,7 @@ def test_dir_testing(): "ndimage", "odr", "optimize", + "optimize.elementwise", "signal", "signal.windows", "sparse", @@ -489,7 +490,7 @@ def test_misc_doccer_deprecation(): getattr(module, attr_name) # Attributes that were not in `scipy.misc.doccer` get an error - # notifying the user that the attribute is not in `scipy.misc.doccer` + # notifying the user that the attribute is not in `scipy.misc.doccer` # and that `scipy.misc.doccer` is deprecated. message = "`scipy.misc.doccer` is deprecated..." with pytest.raises(AttributeError, match=message): diff --git a/scipy/optimize/__init__.py b/scipy/optimize/__init__.py index bcbc670393a2..0f9ad7880b58 100644 --- a/scipy/optimize/__init__.py +++ b/scipy/optimize/__init__.py @@ -245,7 +245,7 @@ ======================================== .. toctree:: - :maxdepth: 4 + :maxdepth: 3 optimize.elementwise From 98df70e89e0258f373bf1af3989116b413aa9413 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 28 May 2024 13:02:28 -0700 Subject: [PATCH 297/500] DOC: optimize.elementwise: improve rendering --- scipy/optimize/_elementwise.py | 81 +++++++++++++++------------------- 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/scipy/optimize/_elementwise.py b/scipy/optimize/_elementwise.py index 4fa943b00593..a59d0dc1cb4c 100644 --- a/scipy/optimize/_elementwise.py +++ b/scipy/optimize/_elementwise.py @@ -25,13 +25,13 @@ def find_root(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=No f(x: array, *args) -> array - where each element of ``x`` is a finite real and ``args`` is a tuple, - which may contain an arbitrary number of arrays that are broadcastable - with `x`. + where each element of ``x`` is a finite real and ``args`` is a tuple, + which may contain an arbitrary number of arrays that are broadcastable + with `x`. - ``f`` must be an elementwise function: each element ``f(x)[i]`` - must equal ``f(x[i])`` for all indices ``i``. `find_root` seeks an - array ``x`` such that ``f(x)`` is an array of zeros. + ``f`` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. `find_root` seeks an + array ``x`` such that ``f(x)`` is an array of zeros. init : 2-tuple of real number arrays The lower and upper endpoints of a bracket surrounding the desired root. A bracket is valid if arrays ``xl, xr = init`` satisfy ``xl < xr`` and @@ -69,9 +69,9 @@ def find_root(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=No Returns ------- res : _RichResult - An instance of `scipy._lib._util._RichResult` 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 + 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 @@ -122,18 +122,9 @@ def find_root(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=No - ``fun(xmin) <= fatol + abs(fmin0) * frtol``. This is equivalent to the termination condition described in [1]_ with - - - ``xrtol = 4e-10``, - - ``xatol = 1e-5``, and - - ``fatol = frtol = 0``. - + ``xrtol = 4e-10``, ``xatol = 1e-5``, and ``fatol = frtol = 0``. However, the default values of the `tolerances` dictionary are - - - ``xatol = 4*tiny``, - - ``xrtol = 4*eps``, - - ``frtol = 0``, and - - ``fatol = tiny``, - + ``xatol = 4*tiny``, ``xrtol = 4*eps``, ``frtol = 0``, and ``fatol = tiny``, where ``eps`` and ``tiny`` are the precision and smallest normal number of the result ``dtype`` of function inputs and outputs. @@ -224,13 +215,13 @@ def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback= f(x: array, *args) -> array - where each element of ``x`` is a finite real and ``args`` is a tuple, - which may contain an arbitrary number of arrays that are broadcastable - with `x`. + where each element of ``x`` is a finite real and ``args`` is a tuple, + which may contain an arbitrary number of arrays that are broadcastable + with `x`. - ``f`` must be an elementwise function: each element ``f(x)[i]`` - must equal ``f(x[i])`` for all indices ``i``. `find_minimum` seeks an - array ``x`` such that ``f(x)`` is an array of local minima. + ``f`` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. `find_minimum` seeks an + array ``x`` such that ``f(x)`` is an array of local minima. init : 3-tuple of real number arrays The abscissae of a standard scalar minimization bracket. A bracket is valid if arrays ``x1, x2, x3 = init`` satisfy ``x1 < x2 < x3`` and @@ -266,9 +257,9 @@ def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback= Returns ------- res : _RichResult - An instance of `scipy._lib._util._RichResult` 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 + 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 @@ -400,12 +391,12 @@ def bracket_root(f, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, args=() f(x: array, *args) -> array - where each element of ``x`` is a finite real and ``args`` is a tuple, - which may contain an arbitrary number of arrays that are broadcastable - with `x`. + where each element of ``x`` is a finite real and ``args`` is a tuple, + which may contain an arbitrary number of arrays that are broadcastable + with `x`. - ``f`` must be an elementwise function: each element ``f(x)[i]`` - must equal ``f(x[i])`` for all indices ``i``. + ``f`` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. xl0, xr0: float array Starting guess of bracket, which need not contain a root. If `xr0` is not provided, ``xr0 = xl0 + 1``. Must be broadcastable with all other @@ -426,9 +417,9 @@ def bracket_root(f, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, args=() Returns ------- res : _RichResult - An instance of `scipy._lib._util._RichResult` 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 + 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 @@ -537,12 +528,12 @@ def bracket_minimum(f, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, f(x: array, *args) -> array - where each element of ``x`` is a finite real and ``args`` is a tuple, - which may contain an arbitrary number of arrays that are broadcastable - with `x`. + where each element of ``x`` is a finite real and ``args`` is a tuple, + which may contain an arbitrary number of arrays that are broadcastable + with `x`. - ``f`` must be an elementwise function: each element ``f(x)[i]`` - must equal ``f(x[i])`` for all indices ``i``. + ``f`` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. xm0: float array_like Starting guess for middle point of bracket. xl0, xr0: float array_like, optional @@ -564,9 +555,9 @@ def bracket_minimum(f, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, Returns ------- res : _RichResult - An instance of `scipy._lib._util._RichResult` 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 + 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 From 1326c6a51e523d6388124341aa1d86cdaeefbc11 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Tue, 28 May 2024 15:10:42 -0600 Subject: [PATCH 298/500] REL: set version to 1.15.0.dev0 * Don't merge until the `1.14.0` release notes updates have been merged and the `maintenance/1.14.x` branch has been pushed (I will probably self merge when the time is right). * The versions of dependencies mentioned in the new release notes are only drafts and subject to change/update of course. [skip ci] [ci skip] [skip circle] --- doc/source/release.rst | 1 + doc/source/release/1.15.0-notes.rst | 112 ++++++++++++++++++++++++++++ meson.build | 2 +- pyproject.toml | 2 +- tools/version_utils.py | 2 +- 5 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 doc/source/release/1.15.0-notes.rst diff --git a/doc/source/release.rst b/doc/source/release.rst index 1bde64f973b2..a14c7a9eba96 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -8,6 +8,7 @@ see the `commit logs `_. .. toctree:: :maxdepth: 1 + release/1.15.0-notes release/1.14.0-notes release/1.13.1-notes release/1.13.0-notes diff --git a/doc/source/release/1.15.0-notes.rst b/doc/source/release/1.15.0-notes.rst new file mode 100644 index 000000000000..5bd01eb81508 --- /dev/null +++ b/doc/source/release/1.15.0-notes.rst @@ -0,0 +1,112 @@ +========================== +SciPy 1.15.0 Release Notes +========================== + +.. note:: SciPy 1.15.0 is not released yet! + +.. contents:: + +SciPy 1.15.0 is the culmination of X months of hard work. It contains +many new features, numerous bug-fixes, improved test coverage and better +documentation. There have been a number of deprecations and API changes +in this release, which are documented below. All users are encouraged to +upgrade to this release, as there are a large number of bug-fixes and +optimizations. Before upgrading, we recommend that users check that +their own code does not use deprecated SciPy functionality (to do so, +run your code with ``python -Wd`` and check for ``DeprecationWarning`` s). +Our development attention will now shift to bug-fix releases on the +1.15.x branch, and on adding new features on the main branch. + +This release requires Python 3.10+ and NumPy 1.23.5 or greater. + + +************************** +Highlights of this release +************************** + + +************ +New features +************ + +`scipy.cluster` improvements +============================ + + +`scipy.interpolate` improvements +================================ + + +`scipy.linalg` improvements +=========================== + + +`scipy.ndimage` improvements +============================ + + +`scipy.optimize` improvements +============================= + + +`scipy.signal` improvements +=========================== + + +`scipy.sparse` improvements +=========================== + + + +`scipy.spatial` improvements +============================ + + +`scipy.special` improvements +============================ + + +`scipy.stats` improvements +========================== + + + +******************* +Deprecated features +******************* + +`scipy.linalg` deprecations +=========================== + + +`scipy.spatial` deprecations +============================ + + + +****************************** +Backwards incompatible changes +****************************** + +************* +Other changes +************* + + + +******* +Authors +******* + + + +************************ +Issues closed for 1.15.0 +************************ + + +************************ +Pull requests for 1.15.0 +************************ + + diff --git a/meson.build b/meson.build index ed92bdde6d36..1d57ef8bb709 100644 --- a/meson.build +++ b/meson.build @@ -4,7 +4,7 @@ project( # Note that the git commit hash cannot be added dynamically here (it is added # in the dynamically generated and installed `scipy/version.py` though - see # tools/version_utils.py - version: '1.14.0.dev0', + version: '1.15.0.dev0', license: 'BSD-3', meson_version: '>= 1.1.0', default_options: [ diff --git a/pyproject.toml b/pyproject.toml index 62b9e0550a05..48a0abed65d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ requires = [ [project] name = "scipy" -version = "1.14.0.dev0" +version = "1.15.0.dev0" # TODO: add `license-files` once PEP 639 is accepted (see meson-python#88) # at that point, no longer include them in `py3.install_sources()` license = { file = "LICENSE.txt" } diff --git a/tools/version_utils.py b/tools/version_utils.py index cea0e0af4b63..fbf490bcdf37 100644 --- a/tools/version_utils.py +++ b/tools/version_utils.py @@ -5,7 +5,7 @@ MAJOR = 1 -MINOR = 14 +MINOR = 15 MICRO = 0 ISRELEASED = False IS_RELEASE_BRANCH = False From 05207a875da4548e6adc55a079dc0b639243e1ad Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Tue, 28 May 2024 18:42:46 -0400 Subject: [PATCH 299/500] BUG: sparse: Fix argmin/max shape diff between axis 0/1. And min/max can now return 1D shapes when axis set. (#20792) * minmax for array returns 1D * add tests, fix argmin with axis=1 for sparray * adjust lists of array types to apease mypy * reduce tests lines using loops --- scipy/sparse/_data.py | 10 +++++- scipy/sparse/tests/test_minmax1d.py | 56 ++++++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/scipy/sparse/_data.py b/scipy/sparse/_data.py index b619eeb90bbb..139888ee43e6 100644 --- a/scipy/sparse/_data.py +++ b/scipy/sparse/_data.py @@ -9,7 +9,7 @@ import math import numpy as np -from ._base import _spbase, _ufuncs_with_fixed_point_at_zero +from ._base import _spbase, sparray, _ufuncs_with_fixed_point_at_zero from ._sputils import isscalarlike, validateaxis __all__ = [] @@ -195,6 +195,11 @@ def _min_or_max_axis(self, axis, min_or_max): major_index = np.compress(mask, major_index) 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)), @@ -267,6 +272,9 @@ def _arg_min_or_max_axis(self, axis, argmin_or_argmax, compare): else: ret[i] = zero_ind + if isinstance(self, sparray): + return ret + if axis == 1: ret = ret.reshape(-1, 1) diff --git a/scipy/sparse/tests/test_minmax1d.py b/scipy/sparse/tests/test_minmax1d.py index 53e961931492..dca3f44fa485 100644 --- a/scipy/sparse/tests/test_minmax1d.py +++ b/scipy/sparse/tests/test_minmax1d.py @@ -6,7 +6,8 @@ from numpy.testing import assert_equal, assert_array_equal -from scipy.sparse import coo_array +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 @@ -16,10 +17,11 @@ def toarray(a): return a.toarray() -formats_for_minmax = [coo_array] +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) +@pytest.mark.parametrize("spcreator", formats_for_minmax_supporting_1d) class Test_MinMaxMixin1D: def test_minmax(self, spcreator): D = np.arange(5) @@ -30,7 +32,6 @@ def test_minmax(self, spcreator): assert_equal((-X).min(), -4) assert_equal((-X).max(), 0) - def test_minmax_axis(self, spcreator): D = np.arange(50) X = spcreator(D) @@ -48,7 +49,6 @@ def test_minmax_axis(self, spcreator): 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) @@ -80,3 +80,49 @@ def test_argmax(self, spcreator): 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) From 3669cef38900e9648b962cb44258b30ec117a55a Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Fri, 24 May 2024 14:01:20 -0600 Subject: [PATCH 300/500] DOC: SciPy 1.14.0 relnotes * Draft of SciPy `1.14.0` release notes. [docs only] Co-authored-by: Matt Haberland Co-authored-by: h-vetinari Co-authored-by: Jake Bowhay Co-authored-by: Lucas Colley Co-authored-by: Ralf Gommers Co-authored-by: Ilhan Polat Co-authored-by: Daniel Schmitz <40656107+dschmitz89@users.noreply.github.com> --- .mailmap | 5 + doc/source/release/1.14.0-notes.rst | 641 ++++++++++++++++++++++++++-- 2 files changed, 605 insertions(+), 41 deletions(-) diff --git a/.mailmap b/.mailmap index cfd90864871a..095139b4620e 100644 --- a/.mailmap +++ b/.mailmap @@ -80,6 +80,7 @@ Arno Onken Arno Onken Arthur Volant Arthur <37664438+V0lantis@users.noreply.github.com> Ashwin Pathak ashwinpathak20 Ashwin Pathak ashwinpathak20nov1996 +Ataf Fazledin Ahamed fazledyn Atsushi Sakai Atsushi Sakai Aviv Yaish Aviv Balint Pato balopat @@ -235,6 +236,7 @@ Ilhan Polat ilayn Irvin Probst I--P Irwin Zaid izaid Jacob Carey Jacob Carey +Jacob Ogle jacobogle Jacob Vanderplas Jake VanderPlas Jacob Vanderplas Jake Vanderplas Jacob Vanderplas Jake Vanderplas @@ -310,6 +312,7 @@ Kai Striega kai Kai Striega kai-striega Kai Striega Kai Striega Kat Huang kat +Kenji S Emerson Sparrow Kentaro Yamamoto <38549987+yamaken1343@users.noreply.github.com> yamaken <38549987+yamaken1343@users.noreply.github.com> Kevin Richard Green kevinrichardgreen Klaus Sembritzki klaus @@ -375,6 +378,7 @@ Michael Hirsch michael Michael Hirsch Michael James Bedford Michael Michael Marien michaelmarien +Miguel A. Batalla mabatalla Mikhail Pak mp4096 Milad Sadeghi DM ELNS <57490926+EverLookNeverSee@users.noreply.github.com> Muhammad Firmansyah Kasim mfkasim91 @@ -479,6 +483,7 @@ Stefan Peterson Stefan Peterson Stefan van der Walt Stefan van der Walt Stefan van der Walt Steve Richardson arichar6 +Steven Adams <166521727+hugehope@users.noreply.github.com> hugehope <166521727+hugehope@users.noreply.github.com> Sturla Molden sturlamolden Sturla Molden Sturla Molden Sturla Molden unknown diff --git a/doc/source/release/1.14.0-notes.rst b/doc/source/release/1.14.0-notes.rst index e46b4b172162..0d6b0c661fe9 100644 --- a/doc/source/release/1.14.0-notes.rst +++ b/doc/source/release/1.14.0-notes.rst @@ -6,7 +6,7 @@ SciPy 1.14.0 Release Notes .. contents:: -SciPy 1.14.0 is the culmination of X months of hard work. It contains +SciPy 1.14.0 is the culmination of 3 months of hard work. It contains many new features, numerous bug-fixes, improved test coverage and better documentation. There have been a number of deprecations and API changes in this release, which are documented below. All users are encouraged to @@ -17,7 +17,7 @@ run your code with ``python -Wd`` and check for ``DeprecationWarning`` s). Our development attention will now shift to bug-fix releases on the 1.14.x branch, and on adding new features on the main branch. -This release requires Python 3.10+ and NumPy 1.22.4 or greater. +This release requires Python 3.10+ and NumPy 1.23.5 or greater. For running on PyPy, PyPy3 6.0+ is required. @@ -25,96 +25,342 @@ For running on PyPy, PyPy3 6.0+ is required. ************************** Highlights of this release ************************** - +- SciPy now supports the new Accelerate library introduced in macOS 13.3, and + has wheels built against Accelerate for macOS >=14 resulting in significant + performance improvements for many linear algebra operations. +- A new method, ``cobyqa``, has been added to `scipy.optimize.minimize` - this + is an interface for COBYQA (Constrained Optimization BY Quadratic + Approximations), a derivative-free optimization solver, designed to + supersede COBYLA, developed by the Department of Applied Mathematics, The + Hong Kong Polytechnic University. +- `scipy.sparse.linalg.spsolve_triangular` is now more than an order of + magnitude faster in many cases. ************ New features ************ -`scipy.cluster` improvements -============================ - - -`scipy.interpolate` improvements -================================ +`scipy.fft` improvements +======================== +- A new function, `scipy.fft.prev_fast_len`, has been added. This function + finds the largest composite of FFT radices that is less than the target + length. It is useful for discarding a minimal number of samples before FFT. +`scipy.io` improvements +======================= +- ``wavfile`` now supports reading and writing of ``wav`` files in the RF64 + format, allowing files greater than 4 GB in size to be handled. -`scipy.linalg` improvements -=========================== - +`scipy.constants` improvements +============================== +- Experimental support for the array API standard has been added. -`scipy.ndimage` improvements -============================ +`scipy.interpolate` improvements +================================ +- `scipy.interpolate.Akima1DInterpolator` now supports extrapolation via the + ``extrapolate`` argument. `scipy.optimize` improvements ============================= +- `scipy.optimize.HessianUpdateStrategy` now also accepts square arrays for + ``init_scale``. +- A new method, ``cobyqa``, has been added to `scipy.optimize.minimize` - this + is an interface for COBYQA (Constrained Optimization BY Quadratic + Approximations), a derivative-free optimization solver, designed to + supersede COBYLA, developed by the Department of Applied Mathematics, The + Hong Kong Polytechnic University. +- There are some performance improvements in + `scipy.optimize.differential_evolution`. +- `scipy.optimize.approx_fprime` now has linear space complexity. `scipy.signal` improvements =========================== +- `scipy.signal.minimum_phase` has a new argument ``half``, allowing the + provision of a filter of the same length as the linear-phase FIR filter + coefficients and with the same magnitude spectrum. `scipy.sparse` improvements =========================== - +- A special case has been added to handle multiplying a ``dia_array`` by a + scalar, which avoids a potentially costly conversion to CSR format. +- `scipy.sparse.csgraph.yen` has been added, allowing usage of Yen's K-Shortest + Paths algorithm on a directed on undirected graph. +- Addition between DIA-format sparse arrays and matrices is now faster. +- `scipy.sparse.linalg.spsolve_triangular` is now more than an order of + magnitude faster in many cases. `scipy.spatial` improvements ============================ - +- ``Rotation`` supports an alternative "scalar-first" convention of quaternion + component ordering. It is available via the keyword argument ``scalar_first`` + of ``from_quat`` and ``as_quat`` methods. +- Some minor performance improvements for inverting of ``Rotation`` objects. `scipy.special` improvements ============================ +- Added `scipy.special.log_wright_bessel`, for calculation of the logarithm of + Wright's Bessel function. +- The relative error in `scipy.special.hyp2f1` calculations has improved + substantially. +- Improved behavior of ``boxcox``, ``inv_boxcox``, ``boxcox1p``, and + ``inv_boxcox1p`` by preventing premature overflow. `scipy.stats` improvements ========================== - -Hypothesis Tests ----------------- - - -Sample statistics ------------------ - - -Statistical Distributions -------------------------- - - -Other ------ +- A new function `scipy.stats.power` can be used for simulating the power + of a hypothesis test with respect to a specified alternative. +- The Irwin-Hall (AKA Uniform Sum) distribution has been added as + `scipy.stats.irwinhall`. +- Exact p-value calculations of `scipy.stats.mannwhitneyu` are much faster + and use less memory. +- `scipy.stats.pearsonr` now accepts n-D arrays and computes the statistic + along a specified ``axis``. +- `scipy.stats.kstat`, `scipy.stats.kstatvar`, and `scipy.stats.bartlett` + are faster at performing calculations along an axis of a large n-D array. +************************** +Array API Standard Support +************************** +*Experimental* support for array libraries other than NumPy has been added to +existing sub-packages in recent versions of SciPy. Please consider testing +these features by setting an environment variable ``SCIPY_ARRAY_API=1`` and +providing PyTorch, JAX, or CuPy arrays as array arguments. + +As of 1.14.0, there is support for + +- `scipy.cluster` +- `scipy.fft` +- `scipy.constants` +- `scipy.special`: (select functions) + + - `scipy.special.log_ndtr` + - `scipy.special.ndtr` + - `scipy.special.ndtri` + - `scipy.special.erf` + - `scipy.special.erfc` + - `scipy.special.i0` + - `scipy.special.i0e` + - `scipy.special.i1` + - `scipy.special.i1e` + - `scipy.special.gammaln` + - `scipy.special.gammainc` + - `scipy.special.gammaincc` + - `scipy.special.logit` + - `scipy.special.expit` + - `scipy.special.entr` + - `scipy.special.rel_entr` + - `scipy.special.xlogy` + - `scipy.special.chdtrc` + +- `scipy.stats`: (select functions) + + - `scipy.stats.moment` + - `scipy.stats.skew` + - `scipy.stats.kurtosis` + - `scipy.stats.kstat` + - `scipy.stats.kstatvar` + - `scipy.stats.circmean` + - `scipy.stats.circvar` + - `scipy.stats.circstd` + - `scipy.stats.entropy` + - `scipy.stats.variation` + - `scipy.stats.sem` + - `scipy.stats.ttest_1samp` + - `scipy.stats.pearsonr` + - `scipy.stats.chisquare` + - `scipy.stats.skewtest` + - `scipy.stats.kurtosistest` + - `scipy.stats.normaltest` + - `scipy.stats.jarque_bera` + - `scipy.stats.bartlett` + - `scipy.stats.power_divergence` + - `scipy.stats.monte_carlo_test` ******************* Deprecated features ******************* - -`scipy.linalg` deprecations -=========================== - - -`scipy.spatial` deprecations -============================ - +- `scipy.stats.gstd`, `scipy.stats.chisquare`, and + `scipy.stats.power_divergence` have deprecated support for masked array + input. +- `scipy.stats.linregress` has deprecated support for specifying both samples + in one argument; ``x`` and ``y`` are to be provided as separate arguments. +- The ``conjtransp`` method for `scipy.sparse.dok_array` and + `scipy.sparse.dok_matrix` has been deprecated and will be removed in SciPy + 1.16.0. +- The option ``quadrature="trapz"`` in `scipy.integrate.quad_vec` has been + deprecated in favour of ``quadrature="trapezoid"`` and will be removed in + SciPy 1.16.0. +- `scipy.special.comb` has deprecated support for use of ``exact=True`` in + conjunction with non-integral ``N`` and/or ``k``. ****************************** Backwards incompatible changes ****************************** +- Many `scipy.stats` functions now produce a standardized warning message when + an input sample is too small (e.g. zero size). Previously, these functions + may have raised an error, emitted one or more less informative warnings, or + emitted no warnings. In most cases, returned results are unchanged; in almost + all cases the correct result is ``NaN``. + +Expired deprecations +==================== +There is an ongoing effort to follow through on long-standing deprecations. +The following previously deprecated features are affected: + +- Several previously deprecated methods for sparse arrays were removed: + ``asfptype``, ``getrow``, ``getcol``, ``get_shape``, ``getmaxprint``, + ``set_shape``, ``getnnz``, and ``getformat``. Additionally, the ``.A`` and + ``.H`` attributes were removed. +- ``scipy.integrate.{simps,trapz,cumtrapz}`` have been removed in favour of + ``simpson``, ``trapezoid``, and ``cumulative_trapezoid``. +- The ``tol`` argument of ``scipy.sparse.linalg.{bcg,bicstab,cg,cgs,gcrotmk, + mres,lgmres,minres,qmr,tfqmr}`` has been removed in favour of ``rtol``. + Furthermore, the default value of ``atol`` for these functions has changed + to ``0.0``. +- The ``restrt`` argument of `scipy.sparse.linalg.gmres` has been removed in + favour of ``restart``. +- The ``initial_lexsort`` argument of `scipy.stats.kendalltau` has been + removed. +- The ``cond`` and ``rcond`` arguments of `scipy.linalg.pinv` have been + removed. +- The ``even`` argument of `scipy.integrate.simpson` has been removed. +- The ``turbo`` and ``eigvals`` arguments from ``scipy.linalg.{eigh,eigvalsh}`` + have been removed. +- The ``legacy`` argument of `scipy.special.comb` has been removed. +- The ``hz``/``nyq`` argument of ``signal.{firls, firwin, firwin2, remez}`` has + been removed. +- Objects that weren't part of the public interface but were accessible through + deprecated submodules have been removed. +- ``float128``, ``float96``, and object arrays now raise an error in + `scipy.signal.medfilt` and `scipy.signal.order_filter`. +- ``scipy.interpolate.interp2d`` has been replaced by an empty stub (to be + removed completely in the future). +- Coinciding with changes to function signatures (e.g. removal of a deprecated + keyword), we had deprecated positional use of keyword arguments for the + affected functions, which will now raise an error. Affected functions are: + + - ``sparse.linalg.{bicg, bicgstab, cg, cgs, gcrotmk, gmres, lgmres, minres, + qmr, tfqmr}`` + - ``stats.kendalltau`` + - ``linalg.pinv`` + - ``integrate.simpson`` + - ``linalg.{eigh,eigvalsh}`` + - ``special.comb`` + - ``signal.{firls, firwin, firwin2, remez}`` + + ************* Other changes ************* - +- SciPy now uses C17 as the C standard to build with, instead of C99. The C++ + standard remains C++17. +- macOS Accelerate, which got a major upgrade in macOS 13.3, is now supported. + This results in significant performance improvements for linear algebra + operations, as well as smaller binary wheels. +- Cross-compilation should be smoother and QEMU or similar is no longer needed + to run the cross interpreter. +- Experimental array API support for the JAX backend has been added to several + parts of SciPy. ******* Authors ******* +* Name (commits) +* h-vetinari (30) +* Steven Adams (1) + +* Max Aehle (1) + +* Ataf Fazledin Ahamed (2) + +* Trinh Quoc Anh (1) + +* Miguel A. Batalla (7) + +* Tim Beyer (1) + +* Andrea Blengino (1) + +* boatwrong (1) +* Jake Bowhay (47) +* Dietrich Brunn (2) +* Evgeni Burovski (174) +* Tim Butters (7) + +* CJ Carey (5) +* Sean Cheah (46) +* Lucas Colley (72) +* Giuseppe "Peppe" Dilillo (1) + +* DWesl (2) +* Pieter Eendebak (5) +* Kenji S Emerson (1) + +* Jonas Eschle (1) +* fancidev (2) +* Anthony Frazier (1) + +* Ilan Gold (1) + +* Ralf Gommers (122) +* Rohit Goswami (28) +* Ben Greiner (1) + +* Lorenzo Gualniera (1) + +* Matt Haberland (250) +* Shawn Hsu (1) + +* Budjen Jovan (3) + +* Jozsef Kutas (1) +* Eric Larson (3) +* Gregory R. Lee (4) +* Philip Loche (1) + +* Christian Lorentzen (5) +* Sijo Valayakkad Manikandan (2) + +* marinelay (2) + +* Nikolay Mayorov (1) +* Nicholas McKibben (2) +* Melissa Weber Mendonça (6) +* João Mendes (1) + +* Tomiță Militaru (2) + +* Andrew Nelson (32) +* Lysandros Nikolaou (1) +* Nick ODell (5) + +* Jacob Ogle (1) + +* Pearu Peterson (1) +* Matti Picus (4) +* Ilhan Polat (8) +* pwcnorthrop (3) + +* Bharat Raghunathan (1) +* Tom M. Ragonneau (2) + +* Tyler Reddy (47) +* Pamphile Roy (17) +* Atsushi Sakai (9) +* Daniel Schmitz (5) +* Julien Schueller (2) + +* Dan Schult (12) +* Tomer Sery (7) +* Scott Shambaugh (4) +* Tuhin Sharma (1) + +* Sheila-nk (4) +* Skylake (1) + +* Albert Steppi (214) +* Kai Striega (6) +* Zhibing Sun (2) + +* Nimish Telang (1) + +* toofooboo (1) + +* tpl2go (1) + +* Edgar Andrés Margffoy Tuay (44) +* Valerix (1) + +* Christian Veenhuis (1) +* void (2) + +* Warren Weckesser (3) +* Xuefeng Xu (1) +* Rory Yorke (1) +* Xiao Yuan (1) +* Irwin Zaid (35) +* Elmar Zander (1) + +* ਗਗਨਦੀਪ ਸਿੰਘ (Gagandeep Singh) (2) + + +A total of 81 people contributed to this release. +People with a "+" by their names contributed a patch for the first time. +This list of names is automatically generated, and may not be fully complete. @@ -122,9 +368,322 @@ Authors Issues closed for 1.14.0 ************************ +* `#5369 `__: fsolve & root incorrect function-call count +* `#7203 `__: vtk incompatibility with scipy.interpolate (and mvpoly.rbf) +* `#8056 `__: cho_factor and cho_solve don't support (0,0)-shape matrices +* `#8083 `__: special.hyp2f1 returns the wrong values when c-a-b is an integer... +* `#8510 `__: ValueError: failed to create intent(cache|hide)|optional array--... +* `#8856 `__: LinearNDInterpolator not thread safe +* `#9307 `__: feature request: make \`scipy.stats.pearsonr\` accept 2-D arrays +* `#9459 `__: BUG: linalg: lu and decompositions don't support (0, 1) or (0,... +* `#12515 `__: scipy.linalg.pinvh gives incorrect results +* `#14244 `__: ValueError: On entry to DGESDD parameter number 10 had an illegal... +* `#14389 `__: \`linalg.inv\` fails for arrays of shape (0, 0) +* `#14806 `__: ENH: Add the Irwin-Hall (Uniform Sum) and Bates (Uniform Mean)... +* `#15722 `__: DEP: special.comb: deprecate \`exact=True\` for non-integers +* `#16131 `__: BUG: spsolve_triangular is way slower than spsolve +* `#16583 `__: Combining extensions in \`stats._boost\` into one +* `#16748 `__: None of the \`cython_\*\` APIs have any tests using Cython +* `#16926 `__: TEST/BUG: Tolerance violation in test_solvers::test_solve_discrete_are +* `#17084 `__: ENH: Exporting the removed component of detrend() +* `#17559 `__: ENH: _mannwhitneyu.py computation of exact MWU statistics may... +* `#17658 `__: Inconsistent support for empty matrices in linalg +* `#19322 `__: BUG: \`rv_discrete.expect\` fails when duplicate positions +* `#19348 `__: BUG: stats.nct.pdf inconsistent behavior when compared to MATLAB... +* `#19586 `__: BUG: scipy.signal.group_delay not correct for complex coefficients +* `#19598 `__: BUG: Bug in \`scipy.sparse.linalg.svds\` for large sparse matrices... +* `#19649 `__: ENH: as_quat() and from_quat() seams to be reverse x,y,z,w vs... +* `#19734 `__: Build warnings from HiGHS +* `#19872 `__: BUG: error in calculation of p-values in sp.stats.wilcoxon when... +* `#19905 `__: DEP: remove deprecated imports from privatized modules +* `#19918 `__: ENH: Adding COBYQA to \`scipy.optimize\`? +* `#19921 `__: BUG: Inconsistent Output from BenchGlobal Compared to BenchLeastSquares... +* `#19964 `__: MAINT:BLD:special:Overhaul _ufuncs and cython_special machinery +* `#20124 `__: BUG: stats.skewnorm.ppf returns wrong values with moderately... +* `#20128 `__: BUG: \`csr_array(int())\` errors +* `#20208 `__: BUG: Test failures due to \`invalid value encountered in _beta_ppf\`... +* `#20247 `__: ENH: Akima1DInterpolator Extrapolation +* `#20277 `__: Very noisy doc builds after jupyterlite-sphinx integration +* `#20296 `__: CI: jupyterlite-shpinx pin breaks recent doc builds +* `#20324 `__: MAINT, BUG (?): pearsonr statistic return type change +* `#20357 `__: BUG: Memory usage in griddata function in version 1.12 +* `#20377 `__: ENH: sparse: Update str dunder to handle 1D (and 2D better) +* `#20378 `__: ENH: sparse: Update repr dunder to handle 1D (and maybe 2D better) +* `#20385 `__: MAINT: special version hex cleanup +* `#20386 `__: BUG: scipy.stats.kstest returns NaN starting in scipy 1.12 +* `#20388 `__: DOC: Version switcher is not vertically centred on mobile +* `#20394 `__: BUG: unnecessary computations in iirpeak/iirnotch/iircomb filter... +* `#20399 `__: BUG: scipy.special.logsumexp raises ValueError for a zero-size... +* `#20419 `__: BUG: nightly: .special.jv now promotes float32 inputs to float64 +* `#20434 `__: BUG: sparse dia_array changes to csr after multiplication +* `#20455 `__: BUG: signal.iirfilter: overflow for integer input +* `#20458 `__: MAINT: more potential cleanups related to version bumps +* `#20461 `__: DOC: some likely changes to release process docs +* `#20466 `__: BUG: scipy.linalg.bandwidth returns incorrect upper bandwidth +* `#20488 `__: BUG: When given invalid bounds, \`_minimize_neldermead\` raises... +* `#20492 `__: DOC: linalg.solve_discrete_lyapunov: dead reference link +* `#20502 `__: BUG: special.hyp2f1: local test failure +* `#20509 `__: DOC: Clarify behavior of \`sparse.csgraph.dijkstra\` for \`directed=False\` +* `#20523 `__: CI/BLD: Nightly wheel builds failing for macOS x86_64 +* `#20535 `__: BUG: generate_f2py mod is called by the wrong interpreter +* `#20540 `__: BUG: pytest scipy/linalg/tests/test_extending.py fails with Cython... +* `#20551 `__: DOC/DEV: clearly document which code has an active upstream repo +* `#20562 `__: BUG: Invalid default bracket selection in _bracket_minimum. +* `#20564 `__: TST: stats array API failure for test_skew_constant_value[torch]... +* `#20584 `__: BUG: \`optimize.linprog\` fails with \`list\` type \`integrality\`... +* `#20598 `__: ENH: special: add log of wright_bessel +* `#20614 `__: DOC: dual_annealing optimizer does not pass bounds to minimizer... +* `#20618 `__: BUG: scipy 'minimize' with method='trust-constr' with equality... +* `#20620 `__: DOC: Suggested improvement to interp2d transition guide +* `#20641 `__: BUG: stats: Two new XSLOW test failures +* `#20661 `__: MAINT, TST: failure in test_axis_nan_policy_decorated_positional_args... +* `#20662 `__: DOC: Missing blankspace in error message raised by cont2discrete() +* `#20674 `__: DOC: A typo in authors name in signal.ellipap reference +* `#20683 `__: DOC: A typo in ValueError raised by signal.iirdesign +* `#20691 `__: ENH: Reintroduce Apple Accelerate support +* `#20697 `__: BUG: special: algorithmic Error in \`ratevl\` in \`cephes/polevl.h\` +* `#20740 `__: BLD/DEV: special: build warnings +* `#20755 `__: BUG: stats: Two new test failures +* `#20768 `__: BUG: optimize.minimize: garbage collection in \`lbfgs\` +* `#20783 `__: BUG: Build failure on PyPy3.10 7.3.16: \`error: ‘Py_Initialize’... +* `#20797 `__: BUG: special.hyp1f1: broken for complex argument +* `#20802 `__: MAINT, TST: pytest-fail-slow and local concurrent runs/variability ************************ Pull requests for 1.14.0 ************************ - +* `#13534 `__: ENH: Add more initialization methods for HessianUpdateStrategy +* `#15321 `__: ENH: fft: Add \`prev_fast_len\` to complement \`next_fast_len\` +* `#17924 `__: ENH: sparse.linalg: speed up \`spsolve_triangular\` +* `#18926 `__: ENH: Move symiirorder1/2, cspline2d, qspline2d and spline_filter... +* `#19561 `__: ENH: stats.power: add function to simulate hypothesis test power +* `#19627 `__: FIX: correctly compute group_delay for complex-coefficient TFs +* `#19673 `__: DEP: signal: raise error using medfilt and order_filter with... +* `#19706 `__: ENH: Add half=True kwarg to minimum_phase +* `#19816 `__: BLD: Add Accelerate support for macOS 13.3+ +* `#19900 `__: MAINT/TST: fft: remove xp backend skips, test \`fftfreq\` \`device\` +* `#19904 `__: MAINT: remove incidental imports from private modules +* `#19923 `__: ENH: stats.mannwhitneyu: replace exact p-value calculation +* `#19954 `__: MAINT: Translate wright_bessel function to C++ +* `#19960 `__: DOC: Add examples to \`scipy.interpolate.spalde\` +* `#19994 `__: ENH: add cobyqa to scipy.optimize. +* `#20073 `__: ENH: special: fix premature overflow in \`boxcox\` +* `#20079 `__: ENH: io: Read and write wav files of size > 4GB +* `#20085 `__: ENH: array types: add JAX support +* `#20089 `__: ENH: Translate complex valued hyp2f1 to C++ and make improvements +* `#20127 `__: ENH/TST: Refactor refguide-check, take 3 +* `#20137 `__: ENH: stats.pearsonr: add support for \`axis\` argument +* `#20187 `__: ENH: sparse.csgraph: Yen K-shortest paths +* `#20199 `__: DOC/DEV/MAINT: update core-dev guide +* `#20202 `__: DOC: Reorganize contents of stats User Guide section +* `#20255 `__: TST: linalg: reenable gges[float32] tests +* `#20257 `__: BUG: prevent file descriptor leak in \`openblas_support.py\`... +* `#20260 `__: ENH: Begin overhaul of ufunc machinery +* `#20265 `__: ENH: optimize: const qualify Cython array arguments +* `#20269 `__: REL: set version to 1.14.0dev0 +* `#20273 `__: MAINT/DEV: enforce minimum \`ruff\` version +* `#20275 `__: MAINT/DEV: add auto-fix to \`dev.py lint\` +* `#20278 `__: DEP: integrate: remove simps,trapz,cumtrapz +* `#20281 `__: BUG: optimize: correct \`nfev\` values +* `#20283 `__: DEP: sparse: deprecate conjtransp() method for dok_array/matrix... +* `#20284 `__: ENH: stats.pearsonr: add array API support +* `#20289 `__: DOC: Pin Jupyterlite Sphinx to avoid noisy doc builds +* `#20292 `__: ENH: stats.moment: add array API support +* `#20295 `__: BUG: linalg: support empty arrays +* `#20297 `__: BUG: linalg: use SYEV not SYEVR for pinvh +* `#20298 `__: DOC: linalg: mention that eigenvalues_only=True/False may change... +* `#20304 `__: ENH: interpolate: allow Akima extrapolation +* `#20310 `__: MAINT: Pin jupyterlite-sphinx to >=0.13.1 +* `#20315 `__: DOC: add docs on how to debug linear algebra related issues +* `#20317 `__: MAINT/DEV: rename \`skip_if_array_api\` to \`skip_xp_backends\` +* `#20320 `__: ENH: Generalised ufuncs in special +* `#20321 `__: BUG: Fix for scipy.special seterr, geterr, errstate +* `#20325 `__: MAINT: Improve performance of ndimage.binary_erosion +* `#20326 `__: MAINT: Replace usage of np.prod +* `#20328 `__: DOC: fix small typo in odds_ratio +* `#20329 `__: MAINT: update \`array_api_compat\` to v1.5.1 +* `#20331 `__: MAINT: Fix Cythonize bug in optimize with const view +* `#20335 `__: TST: linalg: undo xfails of QZ and DARE +* `#20342 `__: BLD: linalg: fix rebuild dependencies for .pyf.src files +* `#20354 `__: MAINT: unpin pytest for wheels +* `#20355 `__: TST: signal: bump tolerance for new \`signal.group_delay\` test +* `#20356 `__: BLD: update numpy build dependency in pyproject.toml for numpy... +* `#20367 `__: STY: always \`import numpy as np\` +* `#20373 `__: MAINT: drop Python 3.9 and NumPy 1.22.x +* `#20380 `__: MAINT: forward port 1.13.0 relnotes +* `#20382 `__: MAINT: lint: enforce \`numpy as np\` alias +* `#20384 `__: ENH:special:Re-rewrite cdflib in C +* `#20390 `__: MAINT:Translate the entirety of cephes into C++ +* `#20393 `__: MAINT/BLD: Remove \`stats._boost\` and add the distribution related... +* `#20397 `__: ENH: Support scalar-first order of quaternion components in Rotation +* `#20403 `__: ENH: special: add ufuncs for amos +* `#20404 `__: BUG: interpolate: fix high memory usage for 2 classes +* `#20405 `__: BUG: Fix pair of bugs in Amos and Cephes yv which masked each... +* `#20413 `__: MAINT: Vendor npyrandom instead of using static library +* `#20416 `__: ENH: optimize._chandrupatla: allow infinite function value at... +* `#20417 `__: ENH: Make cython_special actual code, not autogenerated +* `#20418 `__: BUG: signal: corrections to \`iir{peak,notch,comb}\` filter gain +* `#20420 `__: DOC: stats: speed up the very slow \`bootstrap\` examples +* `#20421 `__: Added float32 overloads for amos functions +* `#20422 `__: TST: Test cimporting Cython APIs +* `#20424 `__: MAINT:special: Add license to cdflib and remove old pxd file +* `#20425 `__: MAINT: Fix DOI visibility badge in README +* `#20426 `__: DOC: add hints on how to debug linalg issues with gdb +* `#20427 `__: DOC: speed up some examples +* `#20438 `__: ENH: Translate \`sph_harm\` Cython->C++, add \`sph_harm_all\`... +* `#20441 `__: BLD: Install cython_special.pxd +* `#20443 `__: MAINT: sparse: Update EfficiencyWarning message to reflect array/matrix +* `#20445 `__: ENH: sparse: special-case DIA \* scalar +* `#20446 `__: MAINT: remove repetitive word typos +* `#20450 `__: BLD: avoid setting an environment variable in a meson.build file +* `#20453 `__: DOC: special: add examples for pdtrc, pdtri, pdtrik +* `#20454 `__: DOC: Update toolchain roadmap (1/N) +* `#20456 `__: BUG: signal.iirfilter: avoid integer overflow +* `#20457 `__: ENH: Add \`scipy.special._ufuncs._iv_ratio\` +* `#20460 `__: DOC: Remove extra css colors and settings +* `#20462 `__: DOC: update readme with link to new forum +* `#20463 `__: MAINT: Refactor special function ufunc generation and consolidate... +* `#20465 `__: MAINT: special: fix compiler warning for unused variable +* `#20467 `__: MAINT: stats._contains_nan: fix bug when -inf and inf are in... +* `#20468 `__: TST: stats: mark tests slow/xslow +* `#20469 `__: MAINT/CI: Remove doctesting from refguide-check +* `#20477 `__: BLD: ensure all static libraries use hidden visibility +* `#20478 `__: CI/MAINT: Increase minimum required compiler versions to GCC... +* `#20480 `__: CI: fail slow tests +* `#20481 `__: ENH: stats: Add the Irwin-Hall distribution +* `#20482 `__: CI: standardize job names +* `#20483 `__: ENH: special: translate \`sph_bessel\` to C++, refactor \`cyl_bessel\` +* `#20487 `__: TST: adjust other very slow tests +* `#20490 `__: BUG: sparse: raise error for array classes, document/test old... +* `#20494 `__: BUG: _qmc.py::_random_oa_lhs produces correlated samples +* `#20495 `__: BUG: Remove keyword argument from ValueError in SciPy.optimize +* `#20497 `__: DEP: interpolate: replace interp2d by stub +* `#20498 `__: DEP: switch sparse methods to kwarg-only; remove tol/restrt kwargs +* `#20499 `__: DEP: execute sparse array API deprecations +* `#20500 `__: DOC: Update dead reference link in \`Scipy.linalg._solvers.py\`:... +* `#20501 `__: MAINT: optimize._chandrupatla: reduce xatol +* `#20503 `__: MAINT: spatial: Fix type annotation of \`query_ball_point\` +* `#20508 `__: DOC: Fix legacy admonition styling +* `#20510 `__: BLD: Accelerate wheels for macOS 14+ +* `#20511 `__: BUG: Fix raising ValueError on a zero-size array for SciPy.special.logsumexp +* `#20515 `__: BLD: default to C17 rather than C99 +* `#20522 `__: TST: Skip or fix some failing tests on certain macOS builds +* `#20526 `__: BLD: adjust lower bound on Clang/LLVM from 14.0 to 12.0 +* `#20529 `__: MAINT: remove repeated "is" typos +* `#20534 `__: BUG: Fixes incorrect upper_band value for scipy.linalg.bandwidth +* `#20536 `__: CI: Check whether Python.h is included first in a file +* `#20538 `__: TST: _lib: remove redundant test for missing \`stacklevel\` +* `#20541 `__: ENH: stats.skew: add array-API support +* `#20542 `__: BLD: Accelerate builds should not define \`NO_APPEND_FORTRAN\` +* `#20545 `__: ENH: stats.ttest_1samp: add array-API support +* `#20546 `__: DOC: use more correct and inclusive pronouns +* `#20547 `__: DOC: stats.linregress: split stats/mstats documentation +* `#20548 `__: TST: Skip Cython tests for editable installs +* `#20550 `__: DEP: stats: switch kendalltau to kwarg-only, remove initial_lexsort... +* `#20554 `__: DEP: integrate: switch simpson to kwarg-only, remove even kwarg +* `#20556 `__: DOC: release process updates +* `#20559 `__: DOC/DEV: add core-dev page on vendored code +* `#20560 `__: DEP: linalg: remove turbo / eigvals kwargs from linalg.{eigh,eigvalsh}... +* `#20563 `__: BUG: Fix invalid default bracket selection in _bracket_minimum +* `#20565 `__: DEP: linalg: remove cond / rcond kwargs from linalg.pinv and... +* `#20568 `__: DOC: change approx_fprime doctest +* `#20572 `__: MAINT: vendor Tempita in \`scipy/_build_utils\` +* `#20575 `__: TST: stats.skew: assert_equal -> xp_assert_equal as appropriate +* `#20577 `__: DEV: add unicode check to pre-commit-hook +* `#20578 `__: DEP: signal: remove nyq / Hz kwargs in firwin\* and switch to... +* `#20582 `__: MAINT: optimize.isotonic_regression: remove unnecessary copies +* `#20583 `__: TST: stats.rv_continuous.fit: adjust fit XSLOW/XFAIL/skip sets +* `#20585 `__: CI/BLD: use scipy-openblas wheel when building +* `#20588 `__: DEP: special: remove legacy kwarg from special.comb and switch... +* `#20590 `__: Revert "ENH: Use \`highspy\` in \`linprog\`" +* `#20593 `__: ENH: constants: add array api support +* `#20595 `__: ENH: stats.circ___: add array-API support +* `#20597 `__: ENH: stats.skewtest: add array-API support +* `#20600 `__: TYP: update supported Mypy version from 1.0.0 to 1.10.0 +* `#20604 `__: ENH: stats.monte_carlo_test: add array API support +* `#20612 `__: BLD: fix use of non-default interpreter, improve f2py handling +* `#20615 `__: ENH: stats: Implement _isf for burr12 +* `#20616 `__: DOC: integrate: remove references to deprecated and legacy functions +* `#20619 `__: ENH: spatial: serialize concurrent calls to QHull +* `#20621 `__: TYP: add type annotations to \`scipy/_lib/_array_api.py\` +* `#20625 `__: TST: add dtype dependent default rtol to xp_assert_close +* `#20627 `__: MAINT: special: Drop unused function_calls variable in kolmogorov.h +* `#20628 `__: TST: integrate.tanhsinh: make test case XSLOW +* `#20630 `__: ENH: optimize._jacobian: use _differentiate to compute accurate... +* `#20631 `__: ENH: stats.sem: add array-API support +* `#20634 `__: ENH: stats: add array-API support to kstat/kstatvar +* `#20637 `__: MAINT: Fix broken links in \`datasets._fetchers\` module +* `#20640 `__: TST: adjust new array API test, slow tests +* `#20642 `__: TST: stats.ttest_1samp: fix xslow test +* `#20643 `__: MAINT:update boost to fix \`skewnorm.ppf\` +* `#20645 `__: ENH: optimize.approx_fprime: avoid quadratic memory usage +* `#20646 `__: ENH: special: add \`log_wright_bessel\` +* `#20647 `__: ENH: stats.variation: add array-API support +* `#20649 `__: MAINT: sparse: reformat str and repr for sparse arrays, correct... +* `#20651 `__: ENH: stats.kstat/kstatvar: add native support for \`axis\` +* `#20656 `__: ENH: Micro-optimizations for spatial.transform.Rotation methods +* `#20657 `__: MAINT: remove unused variable in special +* `#20658 `__: ENH: stats.kurtosis: add array API support +* `#20663 `__: MAINT: stats.kruskal: fix no-arg behavior w/ SCIPY_ARRAY_API=1 +* `#20664 `__: Fix typo in cont2discrete +* `#20665 `__: trust-constr make origin of error message clearer when there... +* `#20667 `__: ENH: stats.describe: add array API support +* `#20673 `__: ENH: stats.entropy, special.{entr, rel_entr}: add array API support +* `#20675 `__: DOC: Fixed typo in signal.ellipap +* `#20676 `__: MAINT: clarify dual_annealing-minimizer_kwargs docstring. Closes... +* `#20677 `__: TST: test__differential_evolution tweaks for speed +* `#20679 `__: MAINT: special.wright_bessel: add comment about reference text +* `#20684 `__: MAINT: Fix missing whitespace in signal.iirdesign, spacing consistency... +* `#20685 `__: MAINT: Add graceful handling of invalid initial brackets to elementwise... +* `#20689 `__: ENH: optimize._chandrupatla: add array API support +* `#20694 `__: MAINT: stats: make reducing functions emit consistent warning... +* `#20696 `__: MAINT: stats.gstd: return result rather than raising +* `#20698 `__: DEV/BLD: add --with-accelerate flag to \`dev.py build\` +* `#20705 `__: MAINT: Add missing whitespace +* `#20711 `__: MAINT: numpy cleanup version bumps: fixes issue #20458 +* `#20712 `__: ENH/BLD: Add install tags for \`tests\` +* `#20715 `__: ENH: stats.kurtosistest: add array API support +* `#20716 `__: DEP: integrate.quad_vec: deprecate \`quadrature="trapz"\` +* `#20722 `__: ENH: sparse: Speed up \`_add_sparse\` for DIA format +* `#20726 `__: DOC: stats.{circmean, circvar, circstd}: improve accuracy/clarity +* `#20730 `__: BUG: special: fix algorithmic error in \`ratevl\` in \`cephes/polevl.h\` +* `#20732 `__: BUG: interpolate: do not segfault on bad boundary conditions +* `#20736 `__: ENH: stats.normaltest/jarque_bera: add array-API support +* `#20737 `__: TST, MAINT: run optimize array API tests and fix \`chandrupatla\` +* `#20738 `__: DOC: sparse.csgraph.dijkstra: add warning for \`directed=False\`... +* `#20741 `__: MAINT: optimize: another fail_slow exception for COBYQA +* `#20744 `__: MAINT: use PyTorch 2.3 in CI, fix CuPy failures, more type annotations... +* `#20745 `__: BUG: Fix incorrect brackets in cephes hyperg.h +* `#20746 `__: DOC: stats: update formulas given for kstat/kstatvar to reflect... +* `#20748 `__: TST: bump tolerance to address local \`test_axis_nan_policy\`... +* `#20750 `__: ENH: some micro-optimisations for differential_evolution +* `#20751 `__: ENH: stats.bartlett: add native \`axis\` and array API support +* `#20753 `__: ENH: stats.chisquare/power_divergence: add array API support +* `#20756 `__: TST: stats: refactor tests of normality tests +* `#20764 `__: TST: stats.fit: address xslow test failures +* `#20765 `__: MAINT: stats.wilcoxon: make \`method='exact'\` symmetric w/ ties +* `#20769 `__: MAINT: stats: move \`multiscale_graphcorr\` tests to save time +* `#20770 `__: MAINT: optimize: remove circular reference in \`ScalarFunction\` +* `#20775 `__: MAINT: forward port 1.13.1 relnotes +* `#20777 `__: ENH: stats: end-to-end array-API support for normality tests +* `#20778 `__: DOC: signal: Documentation improvements of \`detrend\` function +* `#20780 `__: DEP: special.comb: deprecate \`exact=True\` for non-integer intputs +* `#20781 `__: TST: stats: remove overhead of array_namespace in calls to _get_pvalue +* `#20782 `__: ENH: stats: end-to-end array-API support for NHSTs with chi-squared... +* `#20787 `__: DOC: interpolate: mention default kinds in interp2d transition... +* `#20788 `__: ENH: optimize: improve \`cobyqa\` performance by reducing overhead... +* `#20789 `__: DEP: stats.linregress: deprecate one-arg use +* `#20790 `__: BUG: special: remove redundant \`Py_Initialize\` +* `#20791 `__: TST: optimize: fix failing tests for \`_bracket_minimum\` +* `#20792 `__: BUG: sparse: Fix argmin/max shape diff between axis 0/1. And... +* `#20795 `__: MAINT: fix warnings about \`noexcept\` and \`except \*\` in Cython... +* `#20796 `__: BLD: optimize: silence build warnings coming from HiGHS +* `#20798 `__: MAINT: special: fix numpy initialization, avoid build warnings +* `#20799 `__: DOC: ndimage: improve grayscale morphology docstrings +* `#20804 `__: MAINT: remove pytest-fail-slow from pyproject.toml +* `#20805 `__: BUG: special: Restore missing line of code in the function cchg(). +* `#20807 `__: TST: stats.nbinom: adjust cdf-ppf roundtrip test +* `#20812 `__: DOC: extend "building reproducible binaries" page +* `#20815 `__: DOC: integrate: odeint user functions must not modify y. +* `#20819 `__: REV: revert accidental \`cobyqa\` update in gh-17924 From fa11c5aae340d6e042ea3938b5cf7d54bf698249 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 28 May 2024 20:53:05 -0700 Subject: [PATCH 301/500] DOC: elementwise.bracket_root: remove unexpected indent [docs only] --- scipy/optimize/_elementwise.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/optimize/_elementwise.py b/scipy/optimize/_elementwise.py index a59d0dc1cb4c..178efe2d9ba6 100644 --- a/scipy/optimize/_elementwise.py +++ b/scipy/optimize/_elementwise.py @@ -452,8 +452,8 @@ def bracket_root(f, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, args=() ----- This function generalizes an algorithm found in pieces throughout the `scipy.stats` codebase. The strategy is to iteratively grow the bracket `(l, r)` - until ``f(l) < 0 < f(r)`` or ``f(r) < 0 < f(l)``. The bracket grows to the left - as follows. + until ``f(l) < 0 < f(r)`` or ``f(r) < 0 < f(l)``. The bracket grows to the left + as follows. - If `xmin` is not provided, the distance between `xl0` and `l` is iteratively increased by `factor`. From 74c13e4406ae7a95b4509cb436e8fee5b34b2868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= <8431159+mtsokol@users.noreply.github.com> Date: Wed, 29 May 2024 14:17:48 +0200 Subject: [PATCH 302/500] BUG: sparse.csgraph, array types: support non-zero `fill_value`s (#20773) --- scipy/sparse/_sputils.py | 11 ++++-- scipy/sparse/csgraph/_min_spanning_tree.pyx | 10 +++++- scipy/sparse/csgraph/_shortest_path.pyx | 2 ++ scipy/sparse/csgraph/_tools.pyx | 10 +++++- scipy/sparse/csgraph/_validation.py | 7 +++- .../csgraph/tests/test_pydata_sparse.py | 34 +++++++++++++++++++ 6 files changed, 69 insertions(+), 5 deletions(-) diff --git a/scipy/sparse/_sputils.py b/scipy/sparse/_sputils.py index fa515606006d..258d26856679 100644 --- a/scipy/sparse/_sputils.py +++ b/scipy/sparse/_sputils.py @@ -390,14 +390,21 @@ def is_pydata_spmatrix(m) -> bool: def convert_pydata_sparse_to_scipy( - arg: Any, target_format: Optional[Literal["csc", "csr"]] = None + arg: Any, + target_format: Optional[Literal["csc", "csr"]] = None, + accept_fv: Any = None, ) -> Union[Any, "sp.spmatrix"]: """ Convert a pydata/sparse array to scipy sparse matrix, pass through anything else. """ if is_pydata_spmatrix(arg): - arg = arg.to_scipy_sparse() + # 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"): diff --git a/scipy/sparse/csgraph/_min_spanning_tree.pyx b/scipy/sparse/csgraph/_min_spanning_tree.pyx index a1eaa56ddeaa..872983e0faa7 100644 --- a/scipy/sparse/csgraph/_min_spanning_tree.pyx +++ b/scipy/sparse/csgraph/_min_spanning_tree.pyx @@ -97,6 +97,7 @@ def minimum_spanning_tree(csgraph, overwrite=False): is_pydata_sparse = is_pydata_spmatrix(csgraph) if is_pydata_sparse: pydata_sparse_cls = csgraph.__class__ + pydata_sparse_fill_value = csgraph.fill_value csgraph = validate_graph(csgraph, True, DTYPE, dense_output=False, copy_if_sparse=not overwrite) cdef int N = csgraph.shape[0] @@ -120,7 +121,14 @@ def minimum_spanning_tree(csgraph, overwrite=False): sp_tree.eliminate_zeros() if is_pydata_sparse: - sp_tree = pydata_sparse_cls.from_scipy_sparse(sp_tree) + # The `fill_value` keyword is new in PyData Sparse 0.15.4 (May 2024), + # remove the `except` once the minimum supported version is >=0.15.4 + try: + sp_tree = pydata_sparse_cls.from_scipy_sparse( + sp_tree, fill_value=pydata_sparse_fill_value + ) + except TypeError: + sp_tree = pydata_sparse_cls.from_scipy_sparse(sp_tree) return sp_tree diff --git a/scipy/sparse/csgraph/_shortest_path.pyx b/scipy/sparse/csgraph/_shortest_path.pyx index 4712f9ff3844..953bb50aab8f 100644 --- a/scipy/sparse/csgraph/_shortest_path.pyx +++ b/scipy/sparse/csgraph/_shortest_path.pyx @@ -161,6 +161,8 @@ def shortest_path(csgraph, method='auto', array([-9999, 0, 0, 1], dtype=int32) """ + csgraph = convert_pydata_sparse_to_scipy(csgraph, accept_fv=[0, np.inf, np.nan]) + # validate here to catch errors early but don't store the result; # we'll validate again later validate_graph(csgraph, directed, DTYPE, diff --git a/scipy/sparse/csgraph/_tools.pyx b/scipy/sparse/csgraph/_tools.pyx index 50dc61356fe3..6740865813b6 100644 --- a/scipy/sparse/csgraph/_tools.pyx +++ b/scipy/sparse/csgraph/_tools.pyx @@ -472,6 +472,7 @@ def reconstruct_path(csgraph, predecessors, directed=True): is_pydata_sparse = is_pydata_spmatrix(csgraph) if is_pydata_sparse: pydata_sparse_cls = csgraph.__class__ + pydata_sparse_fill_value = csgraph.fill_value csgraph = validate_graph(csgraph, directed, dense_output=False) N = csgraph.shape[0] @@ -502,7 +503,14 @@ def reconstruct_path(csgraph, predecessors, directed=True): sctree = csr_matrix((data, indices, indptr), shape=(N, N)) if is_pydata_sparse: - sctree = pydata_sparse_cls.from_scipy_sparse(sctree) + # The `fill_value` keyword is new in PyData Sparse 0.15.4 (May 2024), + # remove the `except` once the minimum supported version is >=0.15.4 + try: + sctree = pydata_sparse_cls.from_scipy_sparse( + sctree, fill_value=pydata_sparse_fill_value + ) + except TypeError: + sctree = pydata_sparse_cls.from_scipy_sparse(sctree) return sctree diff --git a/scipy/sparse/csgraph/_validation.py b/scipy/sparse/csgraph/_validation.py index e160cf5e7b0e..27c30f5347a0 100644 --- a/scipy/sparse/csgraph/_validation.py +++ b/scipy/sparse/csgraph/_validation.py @@ -18,7 +18,12 @@ def validate_graph(csgraph, directed, dtype=DTYPE, if not (csr_output or dense_output): raise ValueError("Internal: dense or csr output must be true") - csgraph = convert_pydata_sparse_to_scipy(csgraph) + 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. diff --git a/scipy/sparse/csgraph/tests/test_pydata_sparse.py b/scipy/sparse/csgraph/tests/test_pydata_sparse.py index 63ed5f61a430..3d54e83095ed 100644 --- a/scipy/sparse/csgraph/tests/test_pydata_sparse.py +++ b/scipy/sparse/csgraph/tests/test_pydata_sparse.py @@ -147,3 +147,37 @@ def test_min_weight_full_bipartite_matching(graphs): desired = func(sp.csc_matrix(A_dense)[0:2, 1:3]) assert_equal(actual, desired) + + +@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_matrix(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) From b1c2934eb71b7b3449d03ee86a8a617006108124 Mon Sep 17 00:00:00 2001 From: fancidev Date: Wed, 29 May 2024 20:26:40 +0800 Subject: [PATCH 303/500] Compute mean resultant vector from unrotated samples. --- scipy/stats/_morestats.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 90403784418f..114cc87474c7 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -4351,7 +4351,7 @@ def median_test(*samples, ties='below', correction=True, lambda_=1, return MedianTestResult(stat, p, grand_median, table) -def _circfuncs_common(samples, high, low, xp=None): +def _circfuncs_common(samples, scale, xp=None): xp = array_namespace(samples) if xp is None else xp if xp.isdtype(samples.dtype, 'integral'): @@ -4360,8 +4360,9 @@ def _circfuncs_common(samples, high, low, xp=None): # Recast samples as radians that range between 0 and 2 pi and calculate # the sine and cosine - sin_samp = xp.sin((samples - low) * (2.*pi / (high - low))) - cos_samp = xp.cos((samples - low) * (2.*pi / (high - low))) + scaled_samples = samples * scale + sin_samp = xp.sin(scaled_samples) + cos_samp = xp.cos(scaled_samples) return samples, sin_samp, cos_samp @@ -4449,13 +4450,14 @@ def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): # Apparently atan2(0, 0) is 0, even though it is mathematically undefined if xp_size(samples) == 0: return xp.mean(samples, axis=axis) - samples, sin_samp, cos_samp = _circfuncs_common(samples, high, low, xp=xp) + scale = (2.0 * pi) / (high - low) + samples, sin_samp, cos_samp = _circfuncs_common(samples, scale, 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) % (2*xp.pi) + res = xp.atan2(sin_sum, cos_sum) res = res[()] if res.ndim == 0 else res - return res * ((high-low)/(2.0*pi)) + low + return (res/scale-low) % (high-low) + low @_axis_nan_policy_factory( @@ -4540,7 +4542,8 @@ def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): """ xp = array_namespace(samples) - samples, sin_samp, cos_samp = _circfuncs_common(samples, high, low, xp=xp) + scale = (2.0 * pi) / (high - low) + samples, sin_samp, cos_samp = _circfuncs_common(samples, scale, 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 @@ -4643,7 +4646,8 @@ def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, """ xp = array_namespace(samples) - samples, sin_samp, cos_samp = _circfuncs_common(samples, high, low, xp=xp) + scale = (2.0 * pi) / (high - low) + samples, sin_samp, cos_samp = _circfuncs_common(samples, scale, 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 From 0fb9d817586114f2f86ab51d464b5567bf7695bf Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 29 May 2024 07:52:15 -0700 Subject: [PATCH 304/500] DOC: optimize.elementwise: add conditions for convergence --- scipy/optimize/_elementwise.py | 45 +++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/scipy/optimize/_elementwise.py b/scipy/optimize/_elementwise.py index 178efe2d9ba6..c417714ab250 100644 --- a/scipy/optimize/_elementwise.py +++ b/scipy/optimize/_elementwise.py @@ -12,11 +12,12 @@ def find_root(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=No provide a bracket around the root: the function values at the two endpoints must have opposite signs. - Provided a valid bracket, `find_root` is guaranteed to converge to a solution that - satisfies the provided `tolerances` under mild requirements (e.g. the function - is defined and the root exists within the bracket). + Provided a valid bracket, `find_root` is guaranteed to converge to a solution + that satisfies the provided `tolerances` if the function is continuous within + the bracket. - This function works elementwise when `init` and `args` contain broadcastable arrays. + This function works elementwise when `init` and `args` contain (broadcastable) + arrays. Parameters ---------- @@ -200,13 +201,15 @@ def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback= For each element of the output of `f`, `find_minimum` seeks the scalar minimizer that minimizes the element. This function currently uses Chandrupatla's bracketing minimization algorithm [1]_ and therefore requires argument ``init`` - to provide a three-point minimization bracket: + to provide a three-point minimization bracket: ``x1 < x2 < x3`` such that + ``func(x1) >= func(x2) <= func(x3)``, where one of the inequalities is strict. Provided a valid bracket, `find_minimum` is guaranteed to converge to a local - minimizer that satisfies the provided `tolerances` under mild requirements - (e.g. the function is defined and minimum exists within the bracket). + minimum that satisfies the provided `tolerances` if the function is continuous + within the bracket. - This function works elementwise when `init` and `args` contain broadcastable arrays. + This function works elementwise when `init` and `args` contain (broadcastable) + arrays. Parameters ---------- @@ -225,8 +228,9 @@ def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback= init : 3-tuple of real number arrays The abscissae of a standard scalar minimization bracket. A bracket is valid if arrays ``x1, x2, x3 = init`` satisfy ``x1 < x2 < x3`` and - ``func(x1) > func(x2) <= func(x3)``. Arrays must be broadcastable with - one another and the arrays of `args`. + ``func(x1) >= func(x2) <= func(x3)``, where one of the inequalities + is strict. Arrays must be broadcastable with one another and the arrays + of `args`. args : tuple of real number arrays, optional Additional positional array arguments to be passed to `f`. Arrays must be broadcastable with one another and the arrays of `init`. @@ -294,9 +298,9 @@ def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback= ----- Implemented based on Chandrupatla's original paper [1]_. - If ``xl < xm < xr`` are the points of the bracket and ``fl > fm <= fr`` - are the values of ``f`` evaluated at those points, then the algorithm is - considered to have converged when: + If ``xl < xm < xr`` are the points of the bracket and ``fl >= fm <= fr`` + (where one of the inequalities is strict) are the values of ``f`` evaluated + at those points, then the algorithm is considered to have converged when: - ``xr - xl <= abs(xm)*xrtol + xatol`` or - ``(fl - 2*fm + fr)/2 <= abs(fm)*frtol + fatol``. @@ -304,7 +308,7 @@ def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback= Note that first of these differs from the termination conditions described in [1]_. - The default values of `xrtol` is the square root of the precision of the + The default value of `xrtol` is the square root of the precision of the appropriate dtype, and ``xatol = fatol = frtol`` is the smallest normal number of the appropriate dtype. @@ -381,8 +385,11 @@ def bracket_root(f, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, args=() bracket endpoints ``xl`` and ``xr`` such that ``sign(f(xl)) == -sign(f(xr))`` elementwise. + The function is guaranteed to find a valid bracket if the function is monotonic, + but it may find a bracket under other conditions. + This function works elementwise when `xl0`, `xr0`, `xmin`, `xmax`, `factor`, and - the elements of `args` are mutually broadcastable arrays. + the elements of `args` are (mutually broadcastable) arrays. Parameters ---------- @@ -516,10 +523,14 @@ def bracket_minimum(f, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, """Bracket the minimum of a unimodal, real-valued function of a real variable. For each element of the output of `f`, `bracket_minimum` seeks the scalar - bracket points ``xl < xm < xr`` such that ``fl > fm <= fr``. + bracket points ``xl < xm < xr`` such that ``fl >= fm <= fr`` where one of the + inequalities is strict. + + The function is guaranteed to find a valid bracket if the function is + strongly unimodal, but it may find a bracket under other conditions. This function works elementwise when `xm0`, `xl0`, `xr0`, `xmin`, `xmax`, `factor`, - and the elements of `args` are mutually broadcastable arrays. + and the elements of `args` are (mutually broadcastable) arrays. Parameters ---------- From cd8ba6f7ed6110def3e7e5a6c33493cadaeb8a51 Mon Sep 17 00:00:00 2001 From: Jan-Kristian Mathisen Date: Wed, 29 May 2024 17:19:30 +0200 Subject: [PATCH 305/500] Update _differentialevolution.py Changed documentation to reflect implementation --- scipy/optimize/_differentialevolution.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index 815d27afd072..ab0c97b928e4 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -105,7 +105,7 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', of 2 after ``popsize * (N - N_equal)``. tol : float, optional Relative tolerance for convergence, the solving stops when - ``np.std(pop) <= atol + tol * np.abs(np.mean(population_energies))``, + ``np.std(population_energies) <= atol + tol * np.abs(np.mean(population_energies))``, where and `atol` and `tol` are the absolute and relative tolerance respectively. mutation : float or tuple(float, float), optional @@ -575,7 +575,7 @@ class DifferentialEvolutionSolver: of 2 after ``popsize * (N - N_equal)``. tol : float, optional Relative tolerance for convergence, the solving stops when - ``np.std(pop) <= atol + tol * np.abs(np.mean(population_energies))``, + ``np.std(population_energies) <= atol + tol * np.abs(np.mean(population_energies))``, where and `atol` and `tol` are the absolute and relative tolerance respectively. mutation : float or tuple(float, float), optional From 83ae165a6b3bc1437742184421cd0a9cc3b13968 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 29 May 2024 08:50:22 -0700 Subject: [PATCH 306/500] DOC: optimize.elementwise: expand examples --- scipy/optimize/_elementwise.py | 170 ++++++++++++++++++++++++++++----- 1 file changed, 148 insertions(+), 22 deletions(-) diff --git a/scipy/optimize/_elementwise.py b/scipy/optimize/_elementwise.py index c417714ab250..c9a59f8b1e4c 100644 --- a/scipy/optimize/_elementwise.py +++ b/scipy/optimize/_elementwise.py @@ -144,17 +144,50 @@ def find_root(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=No Examples -------- - >>> from scipy.optimize import elementwise + Suppose we wish to find the root of the following function. + >>> def f(x, c=5): ... return x**3 - 2*x - c + + First, we must find a valid bracket. The function is not monotonic, + but `bracket_root` may be able to provide a bracket. + + >>> from scipy.optimize import elementwise >>> res_bracket = elementwise.bracket_root(f, 0) + >>> res_bracket.success + True >>> res_bracket.bracket (2.0, 4.0) + + Indeed, the values of the function at the bracket endpoints have + opposite signs. + + >>> res_bracket.f_bracket + (-1.0, 51.0) + + Once we have a valid bracket, `find_root` can be used to provide + a precise root. + >>> res_root = elementwise.find_root(f, res_bracket.bracket) >>> res_root.x 2.0945514815423265 - >>> c = [3, 4, 5] + The final bracket is only a few ULPs wide, so the error between + this value and the true root cannot be much smaller within values + that are representable in double precision arithmetic. + + >>> import numpy as np + >>> xl, xr = res_root.bracket + >>> (xr - xl) / np.spacing(xl) + 2.0 + >>> res_root.f_bracket + (-8.881784197001252e-16, 9.769962616701378e-15) + + `bracket_root` and `find_root` accept arrays for most arguments. + For instance, to find the root for a few values of the parameter ``c`` + at once: + + >>> c = np.asarray([3, 4, 5]) >>> res_bracket = elementwise.bracket_root(f, 0, args=(c,)) >>> res_bracket.bracket (array([1., 1., 2.]), array([2., 2., 4.])) @@ -327,23 +360,65 @@ def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback= Examples -------- - >>> from scipy.optimize import elementwise + Suppose we wish to minimize the following function. + >>> def f(x, c=1): - ... return (x - c)**2 + ... return (x - c)**2 + 2 + + First, we must find a valid bracket. The function is unimodal, + so `bracket_minium` will easily find a bracket. + + >>> from scipy.optimize import elementwise >>> res_bracket = elementwise.bracket_minimum(f, 0) + >>> res_bracket.success + True >>> res_bracket.bracket (0.0, 0.5, 1.5) - >>> res_minimize = elementwise.find_minimum(f, res_bracket.bracket) - >>> res_minimize.x - 1.0 - >>> c = [1, 1.5, 2] + Indeed, the bracket points are ordered and the function value + at the middle bracket point is less than at the surrounding + points. + + >>> xl, xm, xr = res_bracket.bracket + >>> fl, fm, fr = res_bracket.f_bracket + >>> (xl < xm < xr) and (fl > fm <= fr) + True + + Once we have a valid bracket, `find_minimum` can be used to provide + an estimate of the minimizer. + + >>> res_minimum = elementwise.find_minimum(f, res_bracket.bracket) + >>> res_minimum.x + 1.0000000149011612 + + The function value changes by only a few ULPs within the bracket, so + the minimizer cannot be determined much more precisely by evaluating + the function alone (i.e. we would need its derivative to do better). + + >>> import numpy as np + >>> fl, fm, fr = res_minimum.f_bracket + >>> (fl - fm) / np.spacing(fm), (fr - fm) / np.spacing(fm) + (0.0, 2.0) + + Therefore, a precise minimum of the function is given by: + + >>> res_minimum.f_x + 2.0 + + `bracket_minimum` and `find_minimum` accept arrays for most arguments. + For instance, to find the minimizers and minima for a few values of the + parameter ``c`` at once: + + >>> c = np.asarray([1, 1.5, 2]) >>> res_bracket = elementwise.bracket_minimum(f, 0, args=(c,)) >>> res_bracket.bracket (array([0. , 0.5, 0.5]), array([0.5, 1.5, 1.5]), array([1.5, 2.5, 2.5])) - >>> res_minimize = elementwise.find_minimum(f, res_bracket.bracket, args=(c,)) - >>> res_minimize.x - array([1. , 1.5, 2. ]) + >>> res_minimum = elementwise.find_minimum(f, res_bracket.bracket, args=(c,)) + >>> res_minimum.x + array([1.00000001, 1.5 , 2. ]) + >>> res_minimum.f_x + array([2., 2., 2.]) + """ def reformat_result(res_in): @@ -487,17 +562,40 @@ def bracket_root(f, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, args=() Examples -------- - >>> from scipy.optimize import elementwise + Suppose we wish to find the root of the following function. + >>> def f(x, c=5): ... return x**3 - 2*x - c + + First, we must find a valid bracket. The function is not monotonic, + but `bracket_root` may be able to provide a bracket. + + >>> from scipy.optimize import elementwise >>> res_bracket = elementwise.bracket_root(f, 0) + >>> res_bracket.success + True >>> res_bracket.bracket (2.0, 4.0) + + Indeed, the values of the function at the bracket endpoints have + opposite signs. + + >>> res_bracket.f_bracket + (-1.0, 51.0) + + Once we have a valid bracket, `find_root` can be used to provide + a precise root. + >>> res_root = elementwise.find_root(f, res_bracket.bracket) >>> res_root.x 2.0945514815423265 - >>> c = [3, 4, 5] + `bracket_root` and `find_root` accept arrays for most arguments. + For instance, to find the root for a few values of the parameter ``c`` + at once: + + >>> import numpy as np + >>> c = np.asarray([3, 4, 5]) >>> res_bracket = elementwise.bracket_root(f, 0, args=(c,)) >>> res_bracket.bracket (array([1., 1., 2.]), array([2., 2., 4.])) @@ -633,23 +731,51 @@ def bracket_minimum(f, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, Examples -------- - >>> from scipy.optimize import elementwise + Suppose we wish to minimize the following function. + >>> def f(x, c=1): - ... return (x - c)**2 + ... return (x - c)**2 + 2 + + First, we must find a valid bracket. The function is unimodal, + so `bracket_minium` will easily find a bracket. + + >>> from scipy.optimize import elementwise >>> res_bracket = elementwise.bracket_minimum(f, 0) + >>> res_bracket.success + True >>> res_bracket.bracket (0.0, 0.5, 1.5) - >>> res_minimize = elementwise.find_minimum(f, res_bracket.bracket) - >>> res_minimize.x - 1.0 - >>> c = [1, 1.5, 2] + Indeed, the bracket points are ordered and the function value + at the middle bracket point is less than at the surrounding + points. + + >>> xl, xm, xr = res_bracket.bracket + >>> fl, fm, fr = res_bracket.f_bracket + >>> (xl < xm < xr) and (fl > fm <= fr) + True + + Once we have a valid bracket, `find_minimum` can be used to provide + an estimate of the minimizer. + + >>> res_minimum = elementwise.find_minimum(f, res_bracket.bracket) + >>> res_minimum.x + 1.0000000149011612 + + `bracket_minimum` and `find_minimum` accept arrays for most arguments. + For instance, to find the minimizers and minima for a few values of the + parameter ``c`` at once: + + >>> import numpy as np + >>> c = np.asarray([1, 1.5, 2]) >>> res_bracket = elementwise.bracket_minimum(f, 0, args=(c,)) >>> res_bracket.bracket (array([0. , 0.5, 0.5]), array([0.5, 1.5, 1.5]), array([1.5, 2.5, 2.5])) - >>> res_minimize = elementwise.find_minimum(f, res_bracket.bracket, args=(c,)) - >>> res_minimize.x - array([1. , 1.5, 2. ]) + >>> res_minimum = elementwise.find_minimum(f, res_bracket.bracket, args=(c,)) + >>> res_minimum.x + array([1.00000001, 1.5 , 2. ]) + >>> res_minimum.f_x + array([2., 2., 2.]) """ # noqa: E501 From bff0bd06c0c519678aa5cdedcb16e67ae283bbd5 Mon Sep 17 00:00:00 2001 From: fancidev Date: Thu, 30 May 2024 00:08:51 +0800 Subject: [PATCH 307/500] MAINT: special: Fix typo in four_gammas used by hyp2f1 (#20829) --- scipy/special/special/hyp2f1.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/special/special/hyp2f1.h b/scipy/special/special/hyp2f1.h index a4ea56513e53..a8f8e80e4300 100644 --- a/scipy/special/special/hyp2f1.h +++ b/scipy/special/special/hyp2f1.h @@ -196,8 +196,8 @@ namespace detail { SPECFUN_HOST_DEVICE inline double four_gammas(double u, double v, double w, double x) { double result; - // Without loss of generality, assume |u| >= |v| and |w| >= |x|. - if (std::abs(u) > std::abs(v)) { + // Without loss of generality, ensure |u| >= |v| and |w| >= |x|. + if (std::abs(v) > std::abs(u)) { std::swap(u, v); } if (std::abs(x) > std::abs(w)) { From f5c54773cf86a858dd23b407b39448196900a4d9 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 29 May 2024 20:49:33 +0300 Subject: [PATCH 308/500] BUG: interpolate: make BSpline.integrate always return an array Previously, the return value was either an array (possibly 0D) or a python float. Given that __call__ always returns 0D arrays, and so is PPoly.__call__ and PPoly.integrate, there is really no fundamental reason to not make the return type stable. Strictly speaking, this is a backwards compat break. --- scipy/interpolate/_bsplines.py | 2 +- scipy/interpolate/tests/test_bsplines.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/scipy/interpolate/_bsplines.py b/scipy/interpolate/_bsplines.py index 72c7b8cad80e..e7ccb65d1a6e 100644 --- a/scipy/interpolate/_bsplines.py +++ b/scipy/interpolate/_bsplines.py @@ -662,7 +662,7 @@ def integrate(self, a, b, extrapolate=None): # Fast path: use FITPACK's routine # (cf _fitpack_impl.splint). integral = _fitpack_impl.splint(a, b, self.tck) - return integral * sign + return np.asarray(integral * sign) out = np.empty((2, prod(self.c.shape[1:])), dtype=self.c.dtype) diff --git a/scipy/interpolate/tests/test_bsplines.py b/scipy/interpolate/tests/test_bsplines.py index b4008c19915c..0bee0cc8fe2e 100644 --- a/scipy/interpolate/tests/test_bsplines.py +++ b/scipy/interpolate/tests/test_bsplines.py @@ -390,6 +390,14 @@ def test_integrate_ppoly(self): assert_allclose(b.integrate(x0, x1), p.integrate(x0, x1)) + def test_integrate_0D_always(self): + # make sure the result is always a 0D array (not a python scalar) + b = BSpline.basis_element([0, 1, 2]) + for extrapolate in (True, False): + res = b.integrate(0, 1, extrapolate=extrapolate) + assert type(res) == np.ndarray + assert res.ndim == 0 + def test_subclassing(self): # classmethods should not decay to the base class class B(BSpline): From b31e60c73b7cac2646a5a6d2747c85005ae7f184 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 29 May 2024 12:00:04 -0700 Subject: [PATCH 309/500] Apply suggestions from code review [skip ci] --- scipy/optimize/__init__.py | 4 ++-- scipy/optimize/elementwise.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scipy/optimize/__init__.py b/scipy/optimize/__init__.py index 0f9ad7880b58..fce4cecd22b1 100644 --- a/scipy/optimize/__init__.py +++ b/scipy/optimize/__init__.py @@ -241,8 +241,8 @@ optimize.root-krylov optimize.root-dfsane -Elementwise Minimization and Rootfinding -======================================== +Elementwise Minimization and Root Finding +========================================= .. toctree:: :maxdepth: 3 diff --git a/scipy/optimize/elementwise.py b/scipy/optimize/elementwise.py index 87a8841f4aa6..f7be44846268 100644 --- a/scipy/optimize/elementwise.py +++ b/scipy/optimize/elementwise.py @@ -5,7 +5,7 @@ .. currentmodule:: scipy.optimize.elementwise -This module provides a collection of functions for rootfinding and +This module provides a collection of functions for root finding and minimization of scalar, real-valued functions of one variable. Unlike their counterparts in the base :mod:`scipy.optimize` namespace, these functions work elementwise, enabling the solution of many related problems in an efficient, @@ -14,8 +14,8 @@ perform all calculations using the corresponding array library (e.g. PyTorch, JAX, CuPy). -Rootfinding -=========== +Root finding +============ .. autosummary:: :toctree: generated/ @@ -23,8 +23,8 @@ find_root bracket_root -Rootfinding -=========== +Minimization +============ .. autosummary:: :toctree: generated/ From d2d6c5a3558e87b957c941970db0c4089235f36b Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 29 May 2024 12:09:26 -0700 Subject: [PATCH 310/500] DOC: optimize.elementwise: callables must not mutate arrays --- scipy/optimize/_elementwise.py | 47 ++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/scipy/optimize/_elementwise.py b/scipy/optimize/_elementwise.py index a59d0dc1cb4c..fb6d95934df6 100644 --- a/scipy/optimize/_elementwise.py +++ b/scipy/optimize/_elementwise.py @@ -8,7 +8,7 @@ def find_root(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=No For each element of the output of `f`, `find_root` seeks the scalar root that makes the element 0. This function currently uses Chandrupatla's - bracketing algorithm [1]_ and therefore requires argument ``init`` to + bracketing algorithm [1]_ and therefore requires argument `init` to provide a bracket around the root: the function values at the two endpoints must have opposite signs. @@ -27,11 +27,13 @@ def find_root(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=No where each element of ``x`` is a finite real and ``args`` is a tuple, which may contain an arbitrary number of arrays that are broadcastable - with `x`. + with ``x``. - ``f`` must be an elementwise function: each element ``f(x)[i]`` - must equal ``f(x[i])`` for all indices ``i``. `find_root` seeks an - array ``x`` such that ``f(x)`` is an array of zeros. + `f` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. It must not mutate the + array ``x`` or the arrays in ``args``. + + `find_root` seeks an array ``x`` such that ``f(x)`` is an array of zeros. init : 2-tuple of real number arrays The lower and upper endpoints of a bracket surrounding the desired root. A bracket is valid if arrays ``xl, xr = init`` satisfy ``xl < xr`` and @@ -64,7 +66,8 @@ def find_root(f, init, /, *, args=(), tolerances=None, maxiter=None, callback=No similar to that returned by `find_root` (but containing the current iterate's values of all variables). If `callback` raises a ``StopIteration``, the algorithm will terminate immediately and - `find_root` will return a result. + `find_root` will return a result. `callback` must not mutate + `res` or its attributes. Returns ------- @@ -199,7 +202,7 @@ def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback= For each element of the output of `f`, `find_minimum` seeks the scalar minimizer that minimizes the element. This function currently uses Chandrupatla's - bracketing minimization algorithm [1]_ and therefore requires argument ``init`` + bracketing minimization algorithm [1]_ and therefore requires argument `init` to provide a three-point minimization bracket: Provided a valid bracket, `find_minimum` is guaranteed to converge to a local @@ -217,11 +220,14 @@ def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback= where each element of ``x`` is a finite real and ``args`` is a tuple, which may contain an arbitrary number of arrays that are broadcastable - with `x`. + with ``x``. + + `f` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. It must not mutate the + array ``x`` or the arrays in ``args``. - ``f`` must be an elementwise function: each element ``f(x)[i]`` - must equal ``f(x[i])`` for all indices ``i``. `find_minimum` seeks an - array ``x`` such that ``f(x)`` is an array of local minima. + `find_minimum` seeks an array ``x`` such that ``f(x)`` is an array of + local minima. init : 3-tuple of real number arrays The abscissae of a standard scalar minimization bracket. A bracket is valid if arrays ``x1, x2, x3 = init`` satisfy ``x1 < x2 < x3`` and @@ -252,7 +258,8 @@ def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback= similar to that returned by `find_minimum` (but containing the current iterate's values of all variables). If `callback` raises a ``StopIteration``, the algorithm will terminate immediately and - `find_root` will return a result. + `find_root` will return a result. `callback` must not mutate + `res` or its attributes. Returns ------- @@ -295,7 +302,7 @@ def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback= Implemented based on Chandrupatla's original paper [1]_. If ``xl < xm < xr`` are the points of the bracket and ``fl > fm <= fr`` - are the values of ``f`` evaluated at those points, then the algorithm is + are the values of `f` evaluated at those points, then the algorithm is considered to have converged when: - ``xr - xl <= abs(xm)*xrtol + xatol`` or @@ -393,10 +400,11 @@ def bracket_root(f, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, args=() where each element of ``x`` is a finite real and ``args`` is a tuple, which may contain an arbitrary number of arrays that are broadcastable - with `x`. + with ``x``. - ``f`` must be an elementwise function: each element ``f(x)[i]`` - must equal ``f(x[i])`` for all indices ``i``. + `f` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. It must not mutate the + array ``x`` or the arrays in ``args``. xl0, xr0: float array Starting guess of bracket, which need not contain a root. If `xr0` is not provided, ``xr0 = xl0 + 1``. Must be broadcastable with all other @@ -530,10 +538,11 @@ def bracket_minimum(f, xm0, *, xl0=None, xr0=None, xmin=None, xmax=None, where each element of ``x`` is a finite real and ``args`` is a tuple, which may contain an arbitrary number of arrays that are broadcastable - with `x`. + with ``x``. - ``f`` must be an elementwise function: each element ``f(x)[i]`` - must equal ``f(x[i])`` for all indices ``i``. + `f` must be an elementwise function: each element ``f(x)[i]`` + must equal ``f(x[i])`` for all indices ``i``. It must not mutate the + array ``x`` or the arrays in ``args``. xm0: float array_like Starting guess for middle point of bracket. xl0, xr0: float array_like, optional From f6c9251e2582745fec7f31a6e272b2c37102960d Mon Sep 17 00:00:00 2001 From: fancidev Date: Thu, 30 May 2024 06:26:51 +0800 Subject: [PATCH 311/500] Add white-box test for numerical accuracy in conversion. --- scipy/stats/tests/test_morestats.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 6302a5891347..456f2940d95c 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2715,6 +2715,27 @@ def test_circstd_zero(self, xp): y = stats.circstd(xp.asarray([0])) assert math.copysign(1.0, y) == 1.0 + def test_circmean_accuracy(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, 1e-8, 100) + 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[:, xp.newaxis], axis=1) + assert xp.all(y == x) + class TestCircFuncsNanPolicy: # `nan_policy` is implemented by the `_axis_nan_policy` decorator, which is From e30314b277eda0a584401e9d1cd9c30131eb1ef2 Mon Sep 17 00:00:00 2001 From: fancidev Date: Thu, 30 May 2024 06:35:25 +0800 Subject: [PATCH 312/500] Pass period to _circfuncs_common to make the code clearer. --- scipy/stats/_morestats.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 114cc87474c7..a15d863ebe5a 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -4351,7 +4351,7 @@ def median_test(*samples, ties='below', correction=True, lambda_=1, return MedianTestResult(stat, p, grand_median, table) -def _circfuncs_common(samples, scale, xp=None): +def _circfuncs_common(samples, period, xp=None): xp = array_namespace(samples) if xp is None else xp if xp.isdtype(samples.dtype, 'integral'): @@ -4360,7 +4360,7 @@ def _circfuncs_common(samples, scale, xp=None): # Recast samples as radians that range between 0 and 2 pi and calculate # the sine and cosine - scaled_samples = samples * scale + scaled_samples = samples * ((2.0 * pi) / period) sin_samp = xp.sin(scaled_samples) cos_samp = xp.cos(scaled_samples) @@ -4450,14 +4450,14 @@ def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): # Apparently atan2(0, 0) is 0, even though it is mathematically undefined if xp_size(samples) == 0: return xp.mean(samples, axis=axis) - scale = (2.0 * pi) / (high - low) - samples, sin_samp, cos_samp = _circfuncs_common(samples, scale, xp=xp) + 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/scale-low) % (high-low) + low + return (res * (period / (2.0 * pi)) - low) % period + low @_axis_nan_policy_factory( @@ -4542,8 +4542,8 @@ def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): """ xp = array_namespace(samples) - scale = (2.0 * pi) / (high - low) - samples, sin_samp, cos_samp = _circfuncs_common(samples, scale, xp=xp) + 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 @@ -4646,8 +4646,8 @@ def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, """ xp = array_namespace(samples) - scale = (2.0 * pi) / (high - low) - samples, sin_samp, cos_samp = _circfuncs_common(samples, scale, xp=xp) + 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 From c186897b2335100ade4f2d73089bec7d8c54252c Mon Sep 17 00:00:00 2001 From: fancidev Date: Thu, 30 May 2024 07:21:24 +0800 Subject: [PATCH 313/500] Add white-box test for numerical accuracy in rotation. --- scipy/stats/tests/test_morestats.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 456f2940d95c..92815b686c13 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2715,7 +2715,7 @@ def test_circstd_zero(self, xp): y = stats.circstd(xp.asarray([0])) assert math.copysign(1.0, y) == 1.0 - def test_circmean_accuracy(self, xp): + 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. @@ -2736,6 +2736,15 @@ def test_circmean_accuracy(self, xp): y = stats.circmean(x[:, xp.newaxis], 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 = 1e17 + expected = math.atan2(xp.sin(x), xp.cos(x)) # -2.6584887370946806 + 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 From dfbbc33d633ec0d52d354f157406db8a579d0c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Thu, 30 May 2024 04:56:10 -0500 Subject: [PATCH 314/500] CI: Add Linux workflow to test on free-threaded Python builds (#20822) * ENH: Add Linux workflow to test on free-threaded Python builds * TST: Debug CI via SSH * ENH: Use pre-release pip * CI: Install scipy as wheel * ENH: Prevent tests from hanging (avoid `mpmath`) * TST: Skip test_decorator on Python>= 3.13 --- .github/workflows/linux.yml | 51 +++++++++++++++++++++++++++++++++ scipy/misc/tests/test_doccer.py | 1 + 2 files changed, 52 insertions(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0836cdf1ecd0..6a562606dac8 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -446,3 +446,54 @@ jobs: python3.11 -m pytest --pyargs scipy.cluster python3.11 -m pytest --pyargs scipy.linalg popd + + free-threaded: + needs: get_commit_message + runs-on: ubuntu-latest + if: > + needs.get_commit_message.outputs.message == 1 + && (github.repository == 'scipy/scipy' || github.repository == '') + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + submodules: recursive + fetch-tags: true + # TODO: replace with setup-python when there is support + - uses: deadsnakes/action@6c8b9b82fe0b4344f4b98f2775fcc395df45e494 # v3.1.0 + with: + python-version: '3.13-dev' + nogil: true + - name: Install Ubuntu dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgmp-dev libmpfr-dev libmpc-dev ccache gfortran + # TODO: remove pip pre-release install after Python 3.13 release + - name: Install pre-release pip + run: | + pip install -U --pre pip + # TODO: remove cython nightly install when cython does a release + - name: Install nightly Cython + run: | + pip install git+https://github.com/cython/cython + - name: Install nightly NumPy + run: | + pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + - name: Install Python dependencies + run: | + pip install git+https://github.com/serge-sans-paille/pythran + pip install ninja meson-python pybind11 click rich_click pydevtool + pip install --pre --upgrade pytest pytest-xdist gmpy2 threadpoolctl pooch hypothesis + pip install -r requirements/openblas.txt + - name: Build and run tests + env: + PYTHON_GIL: 0 + # TODO: For some reason the Meson installation path points to + # python3/site-packages as opposed to python3.13/site-packages, + # then the dev.py scripts do not work as expected. + run: | + # python dev.py build --with-scipy-openblas + # python dev.py --no-build test -j2 --mode full + python -c "import scipy_openblas32; print(scipy_openblas32.get_pkg_config())" > scipy-openblas.pc + PKG_CONFIG_PATH="$PWD" pip install . -vv --no-build-isolation + pushd $RUNNER_TEMP + PYTHON_GIL=0 python -m pytest --pyargs scipy -n2 --durations=10 diff --git a/scipy/misc/tests/test_doccer.py b/scipy/misc/tests/test_doccer.py index fa34228ddff7..2ba37becccec 100644 --- a/scipy/misc/tests/test_doccer.py +++ b/scipy/misc/tests/test_doccer.py @@ -72,6 +72,7 @@ def test_docformat(): @pytest.mark.skipif(DOCSTRINGS_STRIPPED, reason="docstrings stripped") +@pytest.mark.skipif(sys.version_info >= (3, 13), reason='it fails on Py3.13') def test_decorator(): with suppress_warnings() as sup: sup.filter(category=DeprecationWarning) From c2a8ee3dda5da12201f33242521cd50c5fd469fb Mon Sep 17 00:00:00 2001 From: fancidev Date: Thu, 30 May 2024 20:17:11 +0800 Subject: [PATCH 315/500] Work around PyTorch compatibility in test case. --- scipy/stats/tests/test_morestats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 92815b686c13..13cc7020ae92 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2733,14 +2733,14 @@ def test_circmean_accuracy_tiny_input(self, xp): assert xp.any(m) x = x[m] - y = stats.circmean(x[:, xp.newaxis], axis=1) + 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 = 1e17 + x = xp.asarray(1e17) expected = math.atan2(xp.sin(x), xp.cos(x)) # -2.6584887370946806 actual = stats.circmean(x, high=xp.pi, low=-xp.pi) xp_assert_close(actual, expected, rtol=1e-15, atol=0.0) From d7bab751686788d82e2968f9aa163e015ee2b9d3 Mon Sep 17 00:00:00 2001 From: Nick ODell Date: Thu, 30 May 2024 07:00:05 -0600 Subject: [PATCH 316/500] ENH: special.rel_entr: Avoid overflow before computing log (#20816) Co-authored-by: fancidev --- scipy/special/_convex_analysis.pxd | 23 +++++++++++++---- scipy/special/tests/test_basic.py | 40 ++++++++++++++++++++++++++++++ scipy/stats/_entropy.py | 2 +- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/scipy/special/_convex_analysis.pxd b/scipy/special/_convex_analysis.pxd index 2fe95b95ccaf..aff10cb0562f 100644 --- a/scipy/special/_convex_analysis.pxd +++ b/scipy/special/_convex_analysis.pxd @@ -1,4 +1,6 @@ from libc.math cimport log, fabs, expm1, log1p, isnan, NAN, INFINITY +from libc.float cimport DBL_MIN +import cython cdef inline double entr(double x) noexcept nogil: if isnan(x): @@ -20,15 +22,26 @@ cdef inline double kl_div(double x, double y) noexcept nogil: else: return INFINITY +@cython.cdivision(True) cdef inline double rel_entr(double x, double y) noexcept nogil: + cdef double ratio if isnan(x) or isnan(y): return NAN - elif x > 0 and y > 0: - return x * log(x / y) - elif x == 0 and y >= 0: - return 0 - else: + if x <= 0 or y <= 0: + if x == 0 and y >= 0: + return 0 return INFINITY + ratio = x / y + if 0.5 < ratio < 2: + # When x and y are close, this is more accurate + return x * log1p((x - y) / y) + if DBL_MIN < ratio < INFINITY: + # There are no underflow/overflow issues + return x * log(ratio) + # x and y are so far apart that taking x / y + # results in either an underflow, overflow, + # or subnormal number. Do the logarithm first + return x * (log(x) - log(y)) cdef inline double huber(double delta, double r) noexcept nogil: if delta < 0: diff --git a/scipy/special/tests/test_basic.py b/scipy/special/tests/test_basic.py index ca8845f7a3e2..0ee5d90d722a 100644 --- a/scipy/special/tests/test_basic.py +++ b/scipy/special/tests/test_basic.py @@ -4152,6 +4152,46 @@ def xfunc(x, y): 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(): + 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)) diff --git a/scipy/stats/_entropy.py b/scipy/stats/_entropy.py index 1e6c8d9e1963..4d033bae4752 100644 --- a/scipy/stats/_entropy.py +++ b/scipy/stats/_entropy.py @@ -124,7 +124,7 @@ def entropy(pk: np.typing.ArrayLike, >>> D = entropy(pk, qk, base=base) >>> D 0.7369655941662062 - >>> D == np.sum(pk * np.log(pk/qk)) / np.log(base) + >>> 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 From 9a375e0070f3270b4ece69c0ffc0b6d80e882a16 Mon Sep 17 00:00:00 2001 From: fancidev Date: Thu, 30 May 2024 21:27:26 +0800 Subject: [PATCH 317/500] Fix PyTorch compatibility in test case again. --- scipy/stats/tests/test_morestats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 13cc7020ae92..2ad50241a4e0 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2743,7 +2743,7 @@ def test_circmean_accuracy_huge_input(self, xp): x = xp.asarray(1e17) expected = math.atan2(xp.sin(x), xp.cos(x)) # -2.6584887370946806 actual = stats.circmean(x, high=xp.pi, low=-xp.pi) - xp_assert_close(actual, expected, rtol=1e-15, atol=0.0) + xp_assert_close(actual, xp.asarray(expected), rtol=1e-15, atol=0.0) class TestCircFuncsNanPolicy: From e826d515ad8a928cd8e4ab3f127411b986d33afb Mon Sep 17 00:00:00 2001 From: fancidev Date: Thu, 30 May 2024 22:08:11 +0800 Subject: [PATCH 318/500] One more PyTorch compatibility fix in test case. --- scipy/stats/tests/test_morestats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 2ad50241a4e0..d722b0d22596 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2740,7 +2740,7 @@ 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) + x = xp.asarray(1e17, dtype=xp.float64) expected = math.atan2(xp.sin(x), xp.cos(x)) # -2.6584887370946806 actual = stats.circmean(x, high=xp.pi, low=-xp.pi) xp_assert_close(actual, xp.asarray(expected), rtol=1e-15, atol=0.0) From 7ae3d4902121a76169ae641a5b649852370b71e0 Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Thu, 30 May 2024 19:13:20 +0530 Subject: [PATCH 319/500] DEV: Add const in function signatures --- scipy/linalg/_cythonized_array_utils.pyx | 2 +- scipy/linalg/_matfuncs_sqrtm_triu.pyx | 2 +- scipy/linalg/_solve_toeplitz.pyx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/linalg/_cythonized_array_utils.pyx b/scipy/linalg/_cythonized_array_utils.pyx index ad85421f5cd1..c38d8d795938 100644 --- a/scipy/linalg/_cythonized_array_utils.pyx +++ b/scipy/linalg/_cythonized_array_utils.pyx @@ -478,7 +478,7 @@ def is_sym_her_complex_c(const np_complex_numeric_t[:, ::1]A): return s @cython.initializedcheck(False) -def is_sym_her_complex_noncontig(np_complex_numeric_t[:, :]A): +def is_sym_her_complex_noncontig(const np_complex_numeric_t[:, :]A): cdef bint s with nogil: s = is_sym_her_complex_noncontig_internal(A) diff --git a/scipy/linalg/_matfuncs_sqrtm_triu.pyx b/scipy/linalg/_matfuncs_sqrtm_triu.pyx index 348199b6e007..237dc948cc51 100644 --- a/scipy/linalg/_matfuncs_sqrtm_triu.pyx +++ b/scipy/linalg/_matfuncs_sqrtm_triu.pyx @@ -9,7 +9,7 @@ cdef fused floating: complex128_t -def within_block_loop(floating[:,::1] R, floating[:,::1] T, start_stop_pairs, intp_t nblocks): +def within_block_loop(floating[:,::1] R, const floating[:,::1] T, start_stop_pairs, intp_t nblocks): cdef intp_t start, stop, i, j, k cdef floating s, denom, num diff --git a/scipy/linalg/_solve_toeplitz.pyx b/scipy/linalg/_solve_toeplitz.pyx index 59a8d32dc320..c75a28e3c8de 100644 --- a/scipy/linalg/_solve_toeplitz.pyx +++ b/scipy/linalg/_solve_toeplitz.pyx @@ -11,7 +11,7 @@ cdef fused dz: complex128_t -def levinson(dz[::1] a, dz[::1] b): +def levinson(const dz[::1] a, const dz[::1] b): """Solve a linear Toeplitz system using Levinson recursion. Parameters From 3535b3780669d5b742813638461bca79d3358483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A8=97=E0=A8=97=E0=A8=A8=E0=A8=A6=E0=A9=80=E0=A8=AA=20?= =?UTF-8?q?=E0=A8=B8=E0=A8=BF=E0=A9=B0=E0=A8=98=20=28Gagandeep=20Singh=29?= Date: Thu, 30 May 2024 23:42:08 +0530 Subject: [PATCH 320/500] BLD: Warning fix from ``scipy/special/special/gamma.h`` (#20820) * DEV: Rename gamma to gamma_complex for complex type * DEV: Use gamma_complex * DEV: Use template for std::complex * DEV: Shift gamma to gamma.h --- scipy/special/special/gamma.h | 15 ++++++++++++--- scipy/special/special/loggamma.h | 10 ---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/scipy/special/special/gamma.h b/scipy/special/special/gamma.h index f0cb1ec41680..970706ff51f5 100644 --- a/scipy/special/special/gamma.h +++ b/scipy/special/special/gamma.h @@ -15,9 +15,18 @@ SPECFUN_HOST_DEVICE T gammaln(T x) { return cephes::lgam(x); } -template -SPECFUN_HOST_DEVICE inline std::complex gamma(std::complex z) { - return gamma(z); +SPECFUN_HOST_DEVICE inline std::complex gamma(std::complex z) { + // Compute Gamma(z) using loggamma. + if (z.real() <= 0 && z == std::floor(z.real())) { + // Poles + set_error("gamma", SF_ERROR_SINGULAR, NULL); + return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; + } + return std::exp(loggamma(z)); +} + +SPECFUN_HOST_DEVICE inline std::complex gamma(std::complex z) { + return static_cast>(gamma(static_cast>(z))); } } // namespace special diff --git a/scipy/special/special/loggamma.h b/scipy/special/special/loggamma.h index 1002d1d83332..a74770fb8c28 100644 --- a/scipy/special/special/loggamma.h +++ b/scipy/special/special/loggamma.h @@ -143,16 +143,6 @@ SPECFUN_HOST_DEVICE inline std::complex loggamma(std::complex z) { return static_cast>(loggamma(static_cast>(z))); } -SPECFUN_HOST_DEVICE inline std::complex gamma(std::complex z) { - // Compute Gamma(z) using loggamma. - if (z.real() <= 0 && z == std::floor(z.real())) { - // Poles - set_error("gamma", SF_ERROR_SINGULAR, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - return std::exp(loggamma(z)); -} - SPECFUN_HOST_DEVICE inline double rgamma(double z) { return cephes::rgamma(z); } SPECFUN_HOST_DEVICE inline float rgamma(float z) { return rgamma(static_cast(z)); } From 335f5fe5704d049abab7767caaf9449f23e4e576 Mon Sep 17 00:00:00 2001 From: fancidev Date: Fri, 31 May 2024 07:13:17 +0800 Subject: [PATCH 321/500] Yet another fix for PyTorch compatibility in test case. --- scipy/stats/tests/test_morestats.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index d722b0d22596..f2f19cd40df1 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2741,9 +2741,10 @@ def test_circmean_accuracy_huge_input(self, xp): # 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) - expected = math.atan2(xp.sin(x), xp.cos(x)) # -2.6584887370946806 + 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, xp.asarray(expected), rtol=1e-15, atol=0.0) + xp_assert_close(actual, expected, rtol=1e-15, atol=0.0) class TestCircFuncsNanPolicy: From 1ead6a96990c334f66f52eab865c96988bd7b2e0 Mon Sep 17 00:00:00 2001 From: Samuel Le Meur-Diebolt Date: Fri, 31 May 2024 02:15:29 +0200 Subject: [PATCH 322/500] MAINT: stats._axis_nan_policy: fix `keepdims` when result objects have Python ints (#20734) * MAINT: stats._axis_nan_policy: fix `keepdims` when result objects have Python ints --------- Co-authored-by: Matt Haberland --- scipy/stats/_axis_nan_policy.py | 3 ++- scipy/stats/_stats_py.py | 3 +-- scipy/stats/tests/test_axis_nan_policy.py | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scipy/stats/_axis_nan_policy.py b/scipy/stats/_axis_nan_policy.py index ab7c3da69a4b..f6427c38eb63 100644 --- a/scipy/stats/_axis_nan_policy.py +++ b/scipy/stats/_axis_nan_policy.py @@ -268,7 +268,8 @@ def _add_reduced_axes(res, reduced_axes, keepdims): Add reduced axes back to all the arrays in the result object if keepdims = True. """ - return ([np.expand_dims(output, reduced_axes) for output in res] + return ([np.expand_dims(output, reduced_axes) + if not isinstance(output, int) else output for output in res] if keepdims else res) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 6623431729fa..6171fdcc1711 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -6280,8 +6280,7 @@ def unpack_TtestResult(res): 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"): +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 diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index 1c548fd63186..bf01307a14ae 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -547,11 +547,15 @@ def unpacker(res): # - Zero-sized output @pytest.mark.filterwarnings('ignore:All axis-slices of one...') @pytest.mark.filterwarnings('ignore:After omitting NaNs...') +# These were added in gh-20734 for `ttest_1samp`; they should be addressed and removed +@pytest.mark.filterwarnings('ignore:divide by zero encountered...') +@pytest.mark.filterwarnings('ignore:invalid value encountered...') @pytest.mark.parametrize("nan_policy", ("omit", "propagate")) @pytest.mark.parametrize( ("hypotest", "args", "kwds", "n_samples", "unpacker"), ((stats.gmean, tuple(), dict(), 1, lambda x: (x,)), - (stats.mannwhitneyu, tuple(), {'method': 'asymptotic'}, 2, None)) + (stats.mannwhitneyu, tuple(), {'method': 'asymptotic'}, 2, None), + (stats.ttest_1samp, (0,), dict(), 1, unpack_ttest_result)) ) @pytest.mark.parametrize( ("sample_shape", "axis_cases"), From 82d9b6306015502cc31f2c5340c3d227d74c19af Mon Sep 17 00:00:00 2001 From: h-vetinari Date: Fri, 31 May 2024 15:24:26 +1100 Subject: [PATCH 323/500] DEP: deprecate trapz alias of `stats.trapezoid` distribution (#20828) Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/stats/_continuous_distns.py | 49 ++++++++++++++++++++++--- scipy/stats/tests/test_distributions.py | 15 +++++++- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py index b06abfa7f926..a4e1dcd27a19 100644 --- a/scipy/stats/_continuous_distns.py +++ b/scipy/stats/_continuous_distns.py @@ -9683,12 +9683,51 @@ def _entropy(self, c, d): return 0.5 * (1.0-d+c) / (1.0+d-c) + np.log(0.5 * (1.0+d-c)) +# deprecation of trapz, see #20486 +deprmsg = ("`trapz` is deprecated in favour of `trapezoid` " + "and will be removed in SciPy 1.16.0.") + + +class trapz_gen(trapezoid_gen): + # override __call__ protocol from rv_generic to also + # deprecate instantiation of frozen distributions + """ + + .. deprecated:: 1.14.0 + `trapz` is deprecated and will be removed in SciPy 1.16. + Plese use `trapezoid` instead! + """ + def __call__(self, *args, **kwds): + warnings.warn(deprmsg, DeprecationWarning, stacklevel=2) + return self.freeze(*args, **kwds) + + trapezoid = trapezoid_gen(a=0.0, b=1.0, name="trapezoid") -# Note: alias kept for backwards compatibility. Rename was done -# because trapz is a slur in colloquial English (see gh-12924). -trapz = trapezoid_gen(a=0.0, b=1.0, name="trapz") -if trapz.__doc__: - trapz.__doc__ = "trapz is an alias for `trapezoid`" +trapz = trapz_gen(a=0.0, b=1.0, name="trapz") + +# since the deprecated class gets intantiated upon import (and we only want to +# warn upon use), add the deprecation to each class method +_method_names = [ + "cdf", "entropy", "expect", "fit", "interval", "isf", "logcdf", "logpdf", + "logsf", "mean", "median", "moment", "pdf", "ppf", "rvs", "sf", "stats", + "std", "var" +] + + +class _DeprecationWrapper: + def __init__(self, method): + self.msg = (f"`trapz.{method}` is deprecated in favour of trapezoid.{method}. " + "Please replace all uses of the distribution class " + "`trapz` with `trapezoid`. `trapz` will be removed in SciPy 1.16.") + self.method = getattr(trapezoid, method) + + def __call__(self, *args, **kwargs): + warnings.warn(self.msg, DeprecationWarning, stacklevel=2) + return self.method(*args, **kwargs) + + +for m in _method_names: + setattr(trapz, m, _DeprecationWrapper(m)) class triang_gen(rv_continuous): diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py index 4e8dfc5257df..ae3ffa7a6f8e 100644 --- a/scipy/stats/tests/test_distributions.py +++ b/scipy/stats/tests/test_distributions.py @@ -7338,7 +7338,20 @@ def test_trapezoid_vect(self): def test_trapz(self): # Basic test for alias x = np.linspace(0, 1, 10) - assert_almost_equal(stats.trapz.pdf(x, 0, 1), stats.uniform.pdf(x)) + with pytest.deprecated_call(match="`trapz.pdf` is deprecated"): + result = stats.trapz.pdf(x, 0, 1) + assert_almost_equal(result, stats.uniform.pdf(x)) + + @pytest.mark.parametrize('method', ['pdf', 'logpdf', 'cdf', 'logcdf', + 'sf', 'logsf', 'ppf', 'isf']) + def test_trapz_deprecation(self, method): + c, d = 0.2, 0.8 + expected = getattr(stats.trapezoid, method)(1, c, d) + with pytest.deprecated_call( + match=f"`trapz.{method}` is deprecated", + ): + result = getattr(stats.trapz, method)(1, c, d) + assert result == expected class TestTriang: From 4759208bf148dfce3f6be58b5652a92cd2d1bef3 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Fri, 31 May 2024 15:18:42 +1000 Subject: [PATCH 324/500] DOC: add cobyqa website reference (#20841) --- scipy/optimize/_cobyqa_py.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scipy/optimize/_cobyqa_py.py b/scipy/optimize/_cobyqa_py.py index 5d164771a213..4928fca9c162 100644 --- a/scipy/optimize/_cobyqa_py.py +++ b/scipy/optimize/_cobyqa_py.py @@ -10,7 +10,7 @@ def _minimize_cobyqa(fun, x0, args=(), bounds=None, constraints=(), **unknown_options): """ Minimize a scalar function of one or more variables using the - Constrained Optimization BY Quadratic Approximations (COBYQA) algorithm. + Constrained Optimization BY Quadratic Approximations (COBYQA) algorithm [1]_. .. versionadded:: 1.14.0 @@ -40,6 +40,11 @@ def _minimize_cobyqa(fun, x0, args=(), bounds=None, constraints=(), if all the lower and upper bounds are finite, the variables are scaled to be within the range :math:`[-1, 1]`. If any of the lower or upper bounds is infinite, the variables are not scaled. + + References + ---------- + .. [1] COBYQA + https://www.cobyqa.com/stable/ """ from .._lib.cobyqa import minimize # import here to avoid circular imports From 5d15fdb46570b28cd99e18887f3510640175dc27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Fri, 31 May 2024 15:34:14 -0300 Subject: [PATCH 325/500] DOC: Wrap long titles in docs pages [docs only] --- doc/source/_static/scipy.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/source/_static/scipy.css b/doc/source/_static/scipy.css index a597689b29ce..e97cfc7066b7 100644 --- a/doc/source/_static/scipy.css +++ b/doc/source/_static/scipy.css @@ -78,3 +78,8 @@ div.admonition>.admonition-title { gap: 10px; margin-bottom: 20px; } + +/* Wrap long titles in pages */ +h1 { + word-wrap: break-word; +} From f54f7d50d67c271ed3f110ea18af12d71c3fa61f Mon Sep 17 00:00:00 2001 From: Andrew Valentine Date: Sat, 1 Jun 2024 00:16:24 +0100 Subject: [PATCH 326/500] BUG: integrate: make `select_initial_step` aware of integration interval (#17348) * Added t_bound optional argument to select_initial_step. * Made t_bound a required argument for select_initial_step * Check that solve_ivp honours specified bounds * Add max_step arg to select_initial_step * Fix broken test * Test max_step honoured by select_initial_step * Fixed error in docstring order * Tidying in response to code review * Fixed some overly long lines. * Fixed bug in test_zero_interval and updated assert statements to reflect standard. * Fixed bug in test_zero_interval to check against last time step * Minor cosmetic updates * Minor cosmetic updates to function call * Apply minor formatting fixes from code review Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> * Formatting fixes --------- Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/integrate/_ivp/bdf.py | 3 +- scipy/integrate/_ivp/common.py | 17 +++- scipy/integrate/_ivp/radau.py | 2 +- scipy/integrate/_ivp/rk.py | 2 +- scipy/integrate/_ivp/tests/test_ivp.py | 112 ++++++++++++++++++++++++- 5 files changed, 128 insertions(+), 8 deletions(-) diff --git a/scipy/integrate/_ivp/bdf.py b/scipy/integrate/_ivp/bdf.py index 5b78060cc3a2..29bd94615192 100644 --- a/scipy/integrate/_ivp/bdf.py +++ b/scipy/integrate/_ivp/bdf.py @@ -204,7 +204,8 @@ def __init__(self, fun, t0, y0, t_bound, max_step=np.inf, self.rtol, self.atol = validate_tol(rtol, atol, self.n) f = self.fun(self.t, self.y) if first_step is None: - self.h_abs = select_initial_step(self.fun, self.t, self.y, f, + self.h_abs = select_initial_step(self.fun, self.t, self.y, + t_bound, max_step, f, self.direction, 1, self.rtol, self.atol) else: diff --git a/scipy/integrate/_ivp/common.py b/scipy/integrate/_ivp/common.py index eccf91f9d5ef..4ff0b7056a0e 100644 --- a/scipy/integrate/_ivp/common.py +++ b/scipy/integrate/_ivp/common.py @@ -65,7 +65,8 @@ def norm(x): return np.linalg.norm(x) / x.size ** 0.5 -def select_initial_step(fun, t0, y0, f0, direction, order, rtol, atol): +def select_initial_step(fun, t0, y0, t_bound, + max_step, f0, direction, order, rtol, atol): """Empirically select a good initial step. The algorithm is described in [1]_. @@ -78,6 +79,11 @@ def select_initial_step(fun, t0, y0, f0, direction, order, rtol, atol): Initial value of the independent variable. y0 : ndarray, shape (n,) Initial value of the dependent variable. + t_bound : float + End-point of integration interval; used to ensure that t0+step<=tbound + and that fun is only evaluated in the interval [t0,tbound] + max_step : float + Maximum allowable step size. f0 : ndarray, shape (n,) Initial value of the derivative, i.e., ``fun(t0, y0)``. direction : float @@ -103,6 +109,10 @@ def select_initial_step(fun, t0, y0, f0, direction, order, rtol, atol): if y0.size == 0: return np.inf + interval_length = abs(t_bound - t0) + if interval_length == 0.0: + return 0.0 + scale = atol + np.abs(y0) * rtol d0 = norm(y0 / scale) d1 = norm(f0 / scale) @@ -110,7 +120,8 @@ def select_initial_step(fun, t0, y0, f0, direction, order, rtol, atol): h0 = 1e-6 else: h0 = 0.01 * d0 / d1 - + # Check t0+h0*direction doesn't take us beyond t_bound + h0 = min(h0, interval_length) y1 = y0 + h0 * direction * f0 f1 = fun(t0 + h0 * direction, y1) d2 = norm((f1 - f0) / scale) / h0 @@ -120,7 +131,7 @@ def select_initial_step(fun, t0, y0, f0, direction, order, rtol, atol): else: h1 = (0.01 / max(d1, d2)) ** (1 / (order + 1)) - return min(100 * h0, h1) + return min(100 * h0, h1, interval_length, max_step) class OdeSolution: diff --git a/scipy/integrate/_ivp/radau.py b/scipy/integrate/_ivp/radau.py index 0d9109e3564e..e13cb0f14c3c 100644 --- a/scipy/integrate/_ivp/radau.py +++ b/scipy/integrate/_ivp/radau.py @@ -305,7 +305,7 @@ def __init__(self, fun, t0, y0, t_bound, max_step=np.inf, # the error. if first_step is None: self.h_abs = select_initial_step( - self.fun, self.t, self.y, self.f, self.direction, + self.fun, self.t, self.y, t_bound, max_step, self.f, self.direction, 3, self.rtol, self.atol) else: self.h_abs = validate_first_step(first_step, t0, t_bound) diff --git a/scipy/integrate/_ivp/rk.py b/scipy/integrate/_ivp/rk.py index b6076f950156..62a5347ffe91 100644 --- a/scipy/integrate/_ivp/rk.py +++ b/scipy/integrate/_ivp/rk.py @@ -94,7 +94,7 @@ def __init__(self, fun, t0, y0, t_bound, max_step=np.inf, self.f = self.fun(self.t, self.y) if first_step is None: self.h_abs = select_initial_step( - self.fun, self.t, self.y, self.f, self.direction, + self.fun, self.t, self.y, t_bound, max_step, self.f, self.direction, self.error_estimator_order, self.rtol, self.atol) else: self.h_abs = validate_first_step(first_step, t0, t_bound) diff --git a/scipy/integrate/_ivp/tests/test_ivp.py b/scipy/integrate/_ivp/tests/test_ivp.py index 2443553f65a9..9c050b3e5bd4 100644 --- a/scipy/integrate/_ivp/tests/test_ivp.py +++ b/scipy/integrate/_ivp/tests/test_ivp.py @@ -7,7 +7,7 @@ from scipy.optimize._numdiff import group_columns from scipy.integrate import solve_ivp, RK23, RK45, DOP853, Radau, BDF, LSODA from scipy.integrate import OdeSolution -from scipy.integrate._ivp.common import num_jac +from scipy.integrate._ivp.common import num_jac, select_initial_step from scipy.integrate._ivp.base import ConstantDenseOutput from scipy.sparse import coo_matrix, csc_matrix @@ -598,7 +598,7 @@ def test_max_step(): solver = method(fun_rational, t_span[0], y0, t_span[1], rtol=rtol, atol=atol, max_step=1e-20) message = solver.step() - + message = solver.step() # First step succeeds but second step fails. assert_equal(solver.status, 'failed') assert_("step size is less" in message) assert_raises(RuntimeError, solver.step) @@ -1128,9 +1128,117 @@ def fun_with_arg(t, y, a): sol = solve_ivp(fun_with_arg, (0, 0.1), [1], args=(-1,)) assert_allclose(sol.y[0, -1], np.exp(-0.1)) + @pytest.mark.parametrize("f0_fill", [np.nan, np.inf]) def test_initial_state_finiteness(f0_fill): # regression test for gh-17846 msg = "All components of the initial state `y0` must be finite." with pytest.raises(ValueError, match=msg): solve_ivp(fun_zero, [0, 10], np.full(3, f0_fill)) + + +@pytest.mark.parametrize('method', ['RK23', 'RK45', 'DOP853', 'Radau', 'BDF']) +def test_zero_interval(method): + # Case where upper and lower limits of integration are the same + # Result of integration should match initial state. + # f[y(t)] = 2y(t) + def f(t, y): + return 2 * y + res = solve_ivp(f, (0.0, 0.0), np.array([1.0]), method=method) + assert res.success + assert_allclose(res.y[0, -1], 1.0) + + +@pytest.mark.parametrize('method', ['RK23', 'RK45', 'DOP853', 'Radau', 'BDF']) +def test_tbound_respected_small_interval(method): + """Regression test for gh-17341""" + SMALL = 1e-4 + + # f[y(t)] = 2y(t) on t in [0,SMALL] + # undefined otherwise + def f(t, y): + if t > SMALL: + raise ValueError("Function was evaluated outside interval") + return 2 * y + res = solve_ivp(f, (0.0, SMALL), np.array([1]), method=method) + assert res.success + + +@pytest.mark.parametrize('method', ['RK23', 'RK45', 'DOP853', 'Radau', 'BDF']) +def test_tbound_respected_larger_interval(method): + """Regression test for gh-8848""" + def V(r): + return -11/r + 10 * r / (0.05 + r**2) + + def func(t, p): + if t < -17 or t > 2: + raise ValueError("Function was evaluated outside interval") + P = p[0] + Q = p[1] + r = np.exp(t) + dPdr = r * Q + dQdr = -2.0 * r * ((-0.2 - V(r)) * P + 1 / r * Q) + return np.array([dPdr, dQdr]) + + result = solve_ivp(func, + (-17, 2), + y0=np.array([1, -11]), + max_step=0.03, + vectorized=False, + t_eval=None, + atol=1e-8, + rtol=1e-5) + assert result.success + + +@pytest.mark.parametrize('method', ['RK23', 'RK45', 'DOP853', 'Radau', 'BDF']) +def test_tbound_respected_oscillator(method): + "Regression test for gh-9198" + def reactions_func(t, y): + if (t > 205): + raise ValueError("Called outside interval") + yprime = np.array([1.73307544e-02, + 6.49376470e-06, + 0.00000000e+00, + 0.00000000e+00]) + return yprime + + def run_sim2(t_end, n_timepoints=10, shortest_delay_line=10000000): + init_state = np.array([134.08298555, 138.82348612, 100., 0.]) + t0 = 100.0 + t1 = 200.0 + return solve_ivp(reactions_func, + (t0, t1), + init_state.copy(), + dense_output=True, + max_step=t1 - t0) + result = run_sim2(1000, 100, 100) + assert result.success + + +def test_inital_maxstep(): + """Verify that select_inital_step respects max_step""" + rtol = 1e-3 + atol = 1e-6 + y0 = np.array([1/3, 2/9]) + for (t0, t_bound) in ((5, 9), (5, 1)): + for method_order in [RK23.error_estimator_order, + RK45.error_estimator_order, + DOP853.error_estimator_order, + 3, #RADAU + 1 #BDF + ]: + step_no_max = select_initial_step(fun_rational, t0, y0, t_bound, + np.inf, + fun_rational(t0,y0), + np.sign(t_bound - t0), + method_order, + rtol, atol) + max_step = step_no_max/2 + step_with_max = select_initial_step(fun_rational, t0, y0, t_bound, + max_step, + fun_rational(t0, y0), + np.sign(t_bound - t0), + method_order, + rtol, atol) + assert_equal(max_step, step_with_max) From 964458da36364280b3171d2b99d38bd818707ccf Mon Sep 17 00:00:00 2001 From: fancidev Date: Sat, 1 Jun 2024 07:36:37 +0800 Subject: [PATCH 327/500] DOC: mention -b option in testing doc. [docs-only] --- doc/source/dev/api-dev/array_api.rst | 2 ++ doc/source/dev/contributor/devpy_test.rst | 3 +++ 2 files changed, 5 insertions(+) diff --git a/doc/source/dev/api-dev/array_api.rst b/doc/source/dev/api-dev/array_api.rst index f46cf3e92550..326e819c4e35 100644 --- a/doc/source/dev/api-dev/array_api.rst +++ b/doc/source/dev/api-dev/array_api.rst @@ -1,3 +1,5 @@ +.. _dev-arrayapi: + Support for the array API standard ================================== diff --git a/doc/source/dev/contributor/devpy_test.rst b/doc/source/dev/contributor/devpy_test.rst index 544a9daf954b..d98b9f2f7677 100644 --- a/doc/source/dev/contributor/devpy_test.rst +++ b/doc/source/dev/contributor/devpy_test.rst @@ -87,6 +87,9 @@ Other useful options include: - ``-v`` or ``--verbose``, which activates the verbose option for more detailed output. +- ``-b`` or ``--array-api-backend`` *backend* to include alternative + array backends in array-api-compatible tests. See :ref:`dev-arrayapi` + for details. - ``--coverage`` to generate a test coverage report in ``scipy/build/coverage/index.html``. *Note:* |pytest-cov|_ *must be installed.* From dd836d07040c1e52e4808285c20b71fe28e06c7f Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 1 Jun 2024 09:55:21 -0700 Subject: [PATCH 328/500] TST: fail_slow bumps to add margin --- scipy/_lib/tests/test__util.py | 2 +- scipy/_lib/tests/test_import_cycles.py | 2 +- scipy/_lib/tests/test_warnings.py | 2 +- scipy/datasets/tests/test_data.py | 2 +- scipy/integrate/_ivp/tests/test_ivp.py | 2 +- scipy/integrate/tests/test__quad_vec.py | 4 ++-- scipy/integrate/tests/test_quadpack.py | 4 ++-- scipy/interpolate/tests/test_bsplines.py | 1 + scipy/interpolate/tests/test_interpnd.py | 2 +- scipy/interpolate/tests/test_rgi.py | 6 ++--- scipy/io/tests/test_mmio.py | 2 +- scipy/linalg/tests/test_decomp.py | 4 ++-- scipy/linalg/tests/test_extending.py | 2 +- scipy/linalg/tests/test_matfuncs.py | 2 +- scipy/optimize/tests/test__basinhopping.py | 4 ++-- .../tests/test__differential_evolution.py | 22 +++++++++---------- scipy/optimize/tests/test__dual_annealing.py | 12 +++++----- scipy/optimize/tests/test__shgo.py | 4 ++-- .../tests/test_constraint_conversion.py | 4 ++-- scipy/optimize/tests/test_extending.py | 2 +- scipy/optimize/tests/test_least_squares.py | 4 ++-- scipy/optimize/tests/test_linprog.py | 4 ++-- scipy/optimize/tests/test_lsq_linear.py | 2 +- scipy/optimize/tests/test_optimize.py | 9 ++++---- .../optimize/tests/test_trustregion_exact.py | 2 +- scipy/signal/tests/test_filter_design.py | 2 +- scipy/signal/tests/test_signaltools.py | 4 ++-- .../linalg/_isolve/tests/test_iterative.py | 4 ++-- .../sparse/linalg/tests/test_expm_multiply.py | 6 ++--- scipy/sparse/tests/test_base.py | 5 ++++- scipy/spatial/tests/test_kdtree.py | 4 ++-- scipy/spatial/tests/test_qhull.py | 6 ++--- scipy/special/tests/test_basic.py | 4 ++-- scipy/special/tests/test_cython_special.py | 2 +- scipy/special/tests/test_extending.py | 2 +- scipy/special/tests/test_round.py | 4 ++-- .../test_support_alternative_backends.py | 2 +- scipy/stats/tests/test_fast_gen_inversion.py | 1 + scipy/stats/tests/test_fit.py | 1 + scipy/stats/tests/test_mgc.py | 2 +- scipy/stats/tests/test_resampling.py | 2 ++ scipy/stats/tests/test_stats.py | 2 +- 42 files changed, 84 insertions(+), 75 deletions(-) diff --git a/scipy/_lib/tests/test__util.py b/scipy/_lib/tests/test__util.py index b1f58acf3dc9..163c0ba201c4 100644 --- a/scipy/_lib/tests/test__util.py +++ b/scipy/_lib/tests/test__util.py @@ -395,7 +395,7 @@ class TestLazywhere: p = strategies.floats(min_value=0, max_value=1) data = strategies.data() - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) @pytest.mark.filterwarnings('ignore::RuntimeWarning') # overflows, etc. @skip_xp_backends('jax.numpy', reasons=["JAX arrays do not support item assignment"]) diff --git a/scipy/_lib/tests/test_import_cycles.py b/scipy/_lib/tests/test_import_cycles.py index 02177fec255e..20822b1aba42 100644 --- a/scipy/_lib/tests/test_import_cycles.py +++ b/scipy/_lib/tests/test_import_cycles.py @@ -8,7 +8,7 @@ # Check that all modules are importable in a new Python process. # This is not necessarily true if there are import cycles present. -@pytest.mark.fail_slow(20) +@pytest.mark.fail_slow(40) @pytest.mark.slow def test_public_modules_importable(): pids = [subprocess.Popen([sys.executable, '-c', f'import {module}']) diff --git a/scipy/_lib/tests/test_warnings.py b/scipy/_lib/tests/test_warnings.py index 570ea017a8f1..b76971868f47 100644 --- a/scipy/_lib/tests/test_warnings.py +++ b/scipy/_lib/tests/test_warnings.py @@ -95,7 +95,7 @@ def warning_calls(): return bad_filters, bad_stacklevels -@pytest.mark.fail_slow(20) +@pytest.mark.fail_slow(40) @pytest.mark.slow def test_warning_calls_filters(warning_calls): bad_filters, bad_stacklevels = warning_calls diff --git a/scipy/datasets/tests/test_data.py b/scipy/datasets/tests/test_data.py index d29feb72f55f..6474fd27cc75 100644 --- a/scipy/datasets/tests/test_data.py +++ b/scipy/datasets/tests/test_data.py @@ -35,7 +35,7 @@ def test_download_all(self): yield - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_existence_all(self): assert len(os.listdir(data_dir)) >= len(registry) diff --git a/scipy/integrate/_ivp/tests/test_ivp.py b/scipy/integrate/_ivp/tests/test_ivp.py index 9c050b3e5bd4..2c20e2e300b6 100644 --- a/scipy/integrate/_ivp/tests/test_ivp.py +++ b/scipy/integrate/_ivp/tests/test_ivp.py @@ -260,7 +260,7 @@ def test_integration_complex(): assert np.all(e < 5) -@pytest.mark.fail_slow(2) +@pytest.mark.fail_slow(5) def test_integration_sparse_difference(): n = 200 t_span = [0, 20] diff --git a/scipy/integrate/tests/test__quad_vec.py b/scipy/integrate/tests/test__quad_vec.py index c88650ca1010..2ef0b54ff6d9 100644 --- a/scipy/integrate/tests/test__quad_vec.py +++ b/scipy/integrate/tests/test__quad_vec.py @@ -107,7 +107,7 @@ def _lorenzian(x): return 1 / (1 + x**2) -@pytest.mark.fail_slow(5) +@pytest.mark.fail_slow(10) def test_quad_vec_pool(): f = _lorenzian res, err = quad_vec(f, -np.inf, np.inf, norm='max', epsabs=1e-4, workers=4) @@ -124,7 +124,7 @@ def _func_with_args(x, a): return x * (x + a) * np.arange(3) -@pytest.mark.fail_slow(5) +@pytest.mark.fail_slow(10) @pytest.mark.parametrize('extra_args', [2, (2,)]) @pytest.mark.parametrize('workers', [1, 10]) def test_quad_vec_pool_args(extra_args, workers): diff --git a/scipy/integrate/tests/test_quadpack.py b/scipy/integrate/tests/test_quadpack.py index a503cb54918b..e61a69df40f9 100644 --- a/scipy/integrate/tests/test_quadpack.py +++ b/scipy/integrate/tests/test_quadpack.py @@ -541,7 +541,7 @@ def tfunc(x): class TestNQuad: - @pytest.mark.fail_slow(2) + @pytest.mark.fail_slow(5) def test_fixed_limits(self): def func1(x0, x1, x2, x3): val = (x0**2 + x1*x2 - x3**3 + np.sin(x0) + @@ -556,7 +556,7 @@ def opts_basic(*args): assert_quad(res[:-1], 1.5267454070738635) assert_(res[-1]['neval'] > 0 and res[-1]['neval'] < 4e5) - @pytest.mark.fail_slow(2) + @pytest.mark.fail_slow(5) def test_variable_limits(self): scale = .1 diff --git a/scipy/interpolate/tests/test_bsplines.py b/scipy/interpolate/tests/test_bsplines.py index 0bee0cc8fe2e..a57125e610b4 100644 --- a/scipy/interpolate/tests/test_bsplines.py +++ b/scipy/interpolate/tests/test_bsplines.py @@ -1795,6 +1795,7 @@ def test_non_regularized_case(self): spline_interp(grid), atol=1e-15) + @pytest.mark.fail_slow(2) def test_weighted_smoothing_spline(self): # create data sample np.random.seed(1234) diff --git a/scipy/interpolate/tests/test_interpnd.py b/scipy/interpolate/tests/test_interpnd.py index f1a2acc30eee..55ef27672f45 100644 --- a/scipy/interpolate/tests/test_interpnd.py +++ b/scipy/interpolate/tests/test_interpnd.py @@ -359,7 +359,7 @@ def test_tripoints_input_rescale(self): yi_rescale = interpnd.CloughTocher2DInterpolator(tri.points, y, rescale=True)(x) assert_almost_equal(yi, yi_rescale) - @pytest.mark.fail_slow(2) + @pytest.mark.fail_slow(5) def test_dense(self): # Should be more accurate for dense meshes funcs = [ diff --git a/scipy/interpolate/tests/test_rgi.py b/scipy/interpolate/tests/test_rgi.py index d1bf037068c2..ed4b146b3a12 100644 --- a/scipy/interpolate/tests/test_rgi.py +++ b/scipy/interpolate/tests/test_rgi.py @@ -467,7 +467,7 @@ def f(x, y): assert_equal(res[i], np.nan) assert_equal(res[~i], interp(z[~i])) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) @parametrize_rgi_interp_methods @pytest.mark.parametrize(("ndims", "func"), [ (2, lambda x, y: 2 * x ** 3 + 3 * y ** 2), @@ -527,7 +527,7 @@ def test_fill_value(self, method): method=method, bounds_error=False) assert np.isnan(interp([10])) - @pytest.mark.fail_slow(2) + @pytest.mark.fail_slow(5) @parametrize_rgi_interp_methods def test_nonscalar_values(self, method): @@ -851,7 +851,7 @@ def test_xi_broadcast(self, method): method=method, bounds_error=False) assert_allclose(v1, v2.reshape(v1.shape)) - @pytest.mark.fail_slow(2) + @pytest.mark.fail_slow(5) @parametrize_rgi_interp_methods def test_nonscalar_values(self, method): diff --git a/scipy/io/tests/test_mmio.py b/scipy/io/tests/test_mmio.py index b7178cf448c1..f8b5ccbb2b81 100644 --- a/scipy/io/tests/test_mmio.py +++ b/scipy/io/tests/test_mmio.py @@ -127,7 +127,7 @@ def test_random_rectangular_float(self): a = np.random.random(sz) self.check(a, (20, 15, 300, 'array', 'real', 'general')) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_bad_number_of_array_header_fields(self): s = """\ %%MatrixMarket matrix array real general diff --git a/scipy/linalg/tests/test_decomp.py b/scipy/linalg/tests/test_decomp.py index 40904fea4c09..693c4ee470c6 100644 --- a/scipy/linalg/tests/test_decomp.py +++ b/scipy/linalg/tests/test_decomp.py @@ -1178,7 +1178,7 @@ class TestSVD_GESVD(TestSVD_GESDD): lapack_driver = 'gesvd' -@pytest.mark.fail_slow(5) +@pytest.mark.fail_slow(10) def test_svd_gesdd_nofegfault(): # svd(a) with {U,VT}.size > INT_MAX does not segfault # cf https://github.com/scipy/scipy/issues/14001 @@ -2601,7 +2601,7 @@ def test_sort_explicit(self): class TestOrdQZWorkspaceSize: - @pytest.mark.fail_slow(2) + @pytest.mark.fail_slow(5) def test_decompose(self): rng = np.random.RandomState(12345) N = 202 diff --git a/scipy/linalg/tests/test_extending.py b/scipy/linalg/tests/test_extending.py index e6ea695b33b7..4e581fa405a9 100644 --- a/scipy/linalg/tests/test_extending.py +++ b/scipy/linalg/tests/test_extending.py @@ -9,7 +9,7 @@ from scipy.linalg.lapack import dgtsv # type: ignore[attr-defined] -@pytest.mark.fail_slow(60) +@pytest.mark.fail_slow(120) # essential per https://github.com/scipy/scipy/pull/20487#discussion_r1567057247 @pytest.mark.skipif(IS_EDITABLE, reason='Editable install cannot find .pxd headers.') diff --git a/scipy/linalg/tests/test_matfuncs.py b/scipy/linalg/tests/test_matfuncs.py index 5f17a639eff7..c6614961a271 100644 --- a/scipy/linalg/tests/test_matfuncs.py +++ b/scipy/linalg/tests/test_matfuncs.py @@ -752,7 +752,7 @@ def test_readonly(self): a.flags.writeable = False expm(a) - @pytest.mark.fail_slow(2) + @pytest.mark.fail_slow(5) def test_gh18086(self): A = np.zeros((400, 400), dtype=float) rng = np.random.default_rng(100) diff --git a/scipy/optimize/tests/test__basinhopping.py b/scipy/optimize/tests/test__basinhopping.py index 4fbd376ac2c1..2e02dd670eac 100644 --- a/scipy/optimize/tests/test__basinhopping.py +++ b/scipy/optimize/tests/test__basinhopping.py @@ -199,7 +199,7 @@ def test_2d_nograd(self): niter=self.niter, disp=self.disp) assert_almost_equal(res.x, self.sol[i], self.tol) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_all_minimizers(self): # Test 2-D minimizations with gradient. Nelder-Mead, Powell, COBYLA, and # COBYQA don't accept jac=True, so aren't included here. @@ -213,7 +213,7 @@ def test_all_minimizers(self): niter=self.niter, disp=self.disp) assert_almost_equal(res.x, self.sol[i], self.tol) - @pytest.mark.fail_slow(10) + @pytest.mark.fail_slow(20) def test_all_nograd_minimizers(self): # Test 2-D minimizations without gradient. Newton-CG requires jac=True, # so not included here. diff --git a/scipy/optimize/tests/test__differential_evolution.py b/scipy/optimize/tests/test__differential_evolution.py index 1c71d332ee65..682bce4ea955 100644 --- a/scipy/optimize/tests/test__differential_evolution.py +++ b/scipy/optimize/tests/test__differential_evolution.py @@ -722,7 +722,7 @@ def test_immediate_updating(self): pass assert s._updating == 'deferred' - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_parallel(self): # smoke test for parallelization with deferred updating bounds = [(0., 2.), (0., 2.)] @@ -864,7 +864,7 @@ def constr_f(x): assert constr_f(res.x) <= 1.9 assert res.success - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_impossible_constraint(self): def constr_f(x): return np.array([x[0] + x[1]]) @@ -1021,7 +1021,7 @@ def test_matrix_linear_constraint(self): xtrial = np.arange(4 * 5).reshape(4, 5) assert cw.violation(xtrial).shape == (2, 5) - @pytest.mark.fail_slow(10) + @pytest.mark.fail_slow(20) def test_L1(self): # Lampinen ([5]) test problem 1 @@ -1116,7 +1116,7 @@ def c2(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_L2(self): # Lampinen ([5]) test problem 2 @@ -1156,7 +1156,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_L3(self): # Lampinen ([5]) test problem 3 @@ -1207,7 +1207,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_L4(self): # Lampinen ([5]) test problem 4 def f(x): @@ -1260,7 +1260,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_L5(self): # Lampinen ([5]) test problem 5 @@ -1291,7 +1291,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_L6(self): # Lampinen ([5]) test problem 6 def f(x): @@ -1450,7 +1450,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_integrality(self): # test fitting discrete distribution to data rng = np.random.default_rng(6519843218105) @@ -1540,7 +1540,7 @@ def f(x): DifferentialEvolutionSolver(f, bounds=bounds, polish=False, integrality=integrality) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_vectorized(self): def quadratic(x): return np.sum(x**2) @@ -1632,7 +1632,7 @@ def func(x): # "MAXCV = 0.". assert "MAXCV = 0.4" in result.message - @pytest.mark.fail_slow(10) # fail-slow exception by request - see gh-20806 + @pytest.mark.fail_slow(20) # fail-slow exception by request - see gh-20806 def test_strategy_fn(self): # examines ability to customize strategy by mimicking one of the # in-built strategies diff --git a/scipy/optimize/tests/test__dual_annealing.py b/scipy/optimize/tests/test__dual_annealing.py index 041dffc5b509..15b9307290ca 100644 --- a/scipy/optimize/tests/test__dual_annealing.py +++ b/scipy/optimize/tests/test__dual_annealing.py @@ -110,7 +110,7 @@ def test_low_dim(self): assert_allclose(ret.fun, 0., atol=1e-12) assert ret.success - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_high_dim(self): ret = dual_annealing(self.func, self.hd_bounds, seed=self.seed) assert_allclose(ret.fun, 0., atol=1e-12) @@ -121,7 +121,7 @@ def test_low_dim_no_ls(self): no_local_search=True, seed=self.seed) assert_allclose(ret.fun, 0., atol=1e-4) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_high_dim_no_ls(self): ret = dual_annealing(self.func, self.hd_bounds, no_local_search=True, seed=self.seed) @@ -140,7 +140,7 @@ def test_max_reinit(self): assert_raises(ValueError, dual_annealing, self.weirdfunc, self.ld_bounds) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_reproduce(self): res1 = dual_annealing(self.func, self.ld_bounds, seed=self.seed) res2 = dual_annealing(self.func, self.ld_bounds, seed=self.seed) @@ -282,7 +282,7 @@ def test_gradient_gnev(self): seed=self.seed) assert ret.njev == self.ngev - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_from_docstring(self): def func(x): return np.sum(x * x - 10 * np.cos(2 * np.pi * x)) + 10 * np.size(x) @@ -338,7 +338,7 @@ def test_accept_reject_probabilistic( assert_allclose(rate, accept_rate) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_bounds_class(self): # test that result does not depend on the bounds type def func(x): @@ -367,7 +367,7 @@ def func(x): assert_allclose(ret_bounds_list.fun, ret_bounds_class.fun, atol=1e-9) assert ret_bounds_list.nfev == ret_bounds_class.nfev - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_callable_jac_hess_with_args_gh11052(self): # dual_annealing used to fail when `jac` was callable and `args` were # used; check that this is resolved. Example is from gh-11052. diff --git a/scipy/optimize/tests/test__shgo.py b/scipy/optimize/tests/test__shgo.py index fea1fb70fbda..ad8466beca5e 100644 --- a/scipy/optimize/tests/test__shgo.py +++ b/scipy/optimize/tests/test__shgo.py @@ -470,7 +470,7 @@ def test_f5_2_cons_symmetry(self): options=options, iters=1, sampling_method='simplicial') - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_f5_3_cons_symmetry(self): """Assymmetrically constrained test function""" options = {'symmetry': [0, 0, 0, 3], @@ -778,7 +778,7 @@ def f(x): np.testing.assert_allclose(res_new_bounds.x, x_opt) np.testing.assert_allclose(res_new_bounds.x, res_old_bounds.x) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_19_parallelization(self): """Test the functionality to add custom sampling methods to shgo""" diff --git a/scipy/optimize/tests/test_constraint_conversion.py b/scipy/optimize/tests/test_constraint_conversion.py index df5ab6dee478..8f2cc7c356a0 100644 --- a/scipy/optimize/tests/test_constraint_conversion.py +++ b/scipy/optimize/tests/test_constraint_conversion.py @@ -61,7 +61,7 @@ def fun(x): class TestNewToOld: - + @pytest.mark.fail_slow(2) def test_multiple_constraint_objects(self): def fun(x): return (x[0] - 1) ** 2 + (x[1] - 2.5) ** 2 + (x[2] - 0.75) ** 2 @@ -90,7 +90,7 @@ def fun(x): assert_allclose(funs['cobyla'], funs['trust-constr'], rtol=1e-4) assert_allclose(funs['cobyqa'], funs['trust-constr'], rtol=1e-4) - @pytest.mark.fail_slow(10) + @pytest.mark.fail_slow(20) def test_individual_constraint_objects(self): def fun(x): return (x[0] - 1) ** 2 + (x[1] - 2.5) ** 2 + (x[2] - 0.75) ** 2 diff --git a/scipy/optimize/tests/test_extending.py b/scipy/optimize/tests/test_extending.py index 80e25f28891c..48c91a5cf4cf 100644 --- a/scipy/optimize/tests/test_extending.py +++ b/scipy/optimize/tests/test_extending.py @@ -6,7 +6,7 @@ from scipy._lib._testutils import IS_EDITABLE, _test_cython_extension, cython -@pytest.mark.fail_slow(20) +@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.') diff --git a/scipy/optimize/tests/test_least_squares.py b/scipy/optimize/tests/test_least_squares.py index 68cfc421c987..954fc8f2d941 100644 --- a/scipy/optimize/tests/test_least_squares.py +++ b/scipy/optimize/tests/test_least_squares.py @@ -468,7 +468,7 @@ def test_bounds_instances(self): bounds=Bounds(lb=[0.1, 0.1])) assert_allclose(res.x, [0.1, 0.1], atol=1e-5) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_rosenbrock_bounds(self): x0_1 = np.array([-2.0, 1.0]) x0_2 = np.array([2.0, 2.0]) @@ -554,7 +554,7 @@ def test_numerical_jac(self): assert_allclose(res_dense.cost, 0, atol=1e-20) assert_allclose(res_sparse.cost, 0, atol=1e-20) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_with_bounds(self): p = BroydenTridiagonal() for jac, jac_sparsity in product( diff --git a/scipy/optimize/tests/test_linprog.py b/scipy/optimize/tests/test_linprog.py index 1e304cd038ad..62b7018f9d01 100644 --- a/scipy/optimize/tests/test_linprog.py +++ b/scipy/optimize/tests/test_linprog.py @@ -1802,7 +1802,7 @@ def test_crossover(self): # there should be nonzero crossover iterations for IPM (only) assert_equal(res.crossover_nit == 0, self.method != "highs-ipm") - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_marginals(self): # Ensure lagrange multipliers are correct by comparing the derivative # w.r.t. b_ub/b_eq/ub/lb to the reported duals. @@ -2249,7 +2249,7 @@ class TestLinprogHiGHSMIP: method = "highs" options = {} - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) @pytest.mark.xfail(condition=(sys.maxsize < 2 ** 32 and platform.system() == "Linux"), run=False, diff --git a/scipy/optimize/tests/test_lsq_linear.py b/scipy/optimize/tests/test_lsq_linear.py index a2fdd1221851..5f00924524d6 100644 --- a/scipy/optimize/tests/test_lsq_linear.py +++ b/scipy/optimize/tests/test_lsq_linear.py @@ -207,7 +207,7 @@ def test_sparse_and_LinearOperator(self): res = lsq_linear(A, b) assert_allclose(res.optimality, 0, atol=1e-6) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_sparse_bounds(self): m = 5000 n = 1000 diff --git a/scipy/optimize/tests/test_optimize.py b/scipy/optimize/tests/test_optimize.py index 86c6ab268ee4..8a591156a80c 100644 --- a/scipy/optimize/tests/test_optimize.py +++ b/scipy/optimize/tests/test_optimize.py @@ -1261,7 +1261,7 @@ def dfunc(z): assert func(sol1.x) < func(sol2.x), \ f"{method}: {func(sol1.x)} vs. {func(sol2.x)}" - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) @pytest.mark.filterwarnings('ignore::UserWarning') @pytest.mark.filterwarnings('ignore::RuntimeWarning') # See gh-18547 @pytest.mark.parametrize('method', @@ -2495,6 +2495,7 @@ def setup_method(self): self.hessp = optimize.rosen_hess_prod self.bounds = [(0., 10.), (0., 10.)] + @pytest.mark.fail_slow(2) def test_attributes_present(self): attributes = ['nit', 'nfev', 'x', 'success', 'status', 'fun', 'message'] @@ -2584,7 +2585,7 @@ def f(x): optimize.brute(f, [(-1, 1)], Ns=3, finish=None) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_workers(self): # check that parallel evaluation works resbrute = optimize.brute(brute_func, self.rranges, args=self.params, @@ -2615,7 +2616,7 @@ def f(x, *args): assert_allclose(resbrute, 0) -@pytest.mark.fail_slow(10) +@pytest.mark.fail_slow(20) def test_cobyla_threadsafe(): # Verify that cobyla is threadsafe. Will segfault if it is not. @@ -2663,7 +2664,7 @@ def slow_func(self, v): r, t = np.sqrt(v[0]**2+v[1]**2), np.arctan2(v[0], v[1]) return np.sin(r*20 + t)+r*0.5 - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_neldermead_limit(self): self.check_limits("Nelder-Mead", 200) diff --git a/scipy/optimize/tests/test_trustregion_exact.py b/scipy/optimize/tests/test_trustregion_exact.py index 42c649218078..b48e4153d0cc 100644 --- a/scipy/optimize/tests/test_trustregion_exact.py +++ b/scipy/optimize/tests/test_trustregion_exact.py @@ -275,7 +275,7 @@ def test_for_jac_very_close_to_zero(self): -0.84954934]) assert_array_almost_equal(hits_boundary, True) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_for_random_entries(self): # Seed np.random.seed(1) diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index 47d24323edbf..59b07f8e8789 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -2493,7 +2493,7 @@ def test_invalid(self): assert_raises(ValueError, _bessel_poly, -3) assert_raises(ValueError, _bessel_poly, 3.3) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_fs_param(self): for norm in ('phase', 'mag', 'delay'): for fs in (900, 900.1, 1234.567): diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py index 5e7ebc1de9ef..ea7634736bd5 100644 --- a/scipy/signal/tests/test_signaltools.py +++ b/scipy/signal/tests/test_signaltools.py @@ -2441,7 +2441,7 @@ def check_filtfilt_gust(b, a, shape, axis, irlen=None): assert_allclose(zg2, zo2, rtol=1e-8, atol=1e-9) -@pytest.mark.fail_slow(5) +@pytest.mark.fail_slow(10) def test_choose_conv_method(): for mode in ['valid', 'same', 'full']: for ndim in [1, 2]: @@ -2473,7 +2473,7 @@ def test_choose_conv_method(): assert_equal(choose_conv_method(x, h, mode=mode), 'direct') -@pytest.mark.fail_slow(5) +@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') diff --git a/scipy/sparse/linalg/_isolve/tests/test_iterative.py b/scipy/sparse/linalg/_isolve/tests/test_iterative.py index 2fda70fcb319..4ebfa62c4e74 100644 --- a/scipy/sparse/linalg/_isolve/tests/test_iterative.py +++ b/scipy/sparse/linalg/_isolve/tests/test_iterative.py @@ -308,7 +308,7 @@ def identity(b, which=None): # Specific test for poisson1d and poisson2d cases -@pytest.mark.fail_slow(5) +@pytest.mark.fail_slow(10) @pytest.mark.parametrize('case', [x for x in IterativeParams().cases if x.name in ('poisson1d', 'poisson2d')], ids=['poisson1d', 'poisson2d']) @@ -685,7 +685,7 @@ def test_abi(self): assert_allclose(r_x, x) assert r_info == info - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_atol_legacy(self): A = eye(2) diff --git a/scipy/sparse/linalg/tests/test_expm_multiply.py b/scipy/sparse/linalg/tests/test_expm_multiply.py index 858ce11b9d4b..0afd22c94661 100644 --- a/scipy/sparse/linalg/tests/test_expm_multiply.py +++ b/scipy/sparse/linalg/tests/test_expm_multiply.py @@ -179,7 +179,7 @@ def test_complex(self): class TestExpmActionInterval: - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(20) def test_sparse_expm_multiply_interval(self): np.random.seed(1234) start = 0.1 @@ -205,7 +205,7 @@ def test_sparse_expm_multiply_interval(self): for solution, t in zip(X, samples): assert_allclose(solution, sp_expm(t*A).dot(target)) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(20) def test_expm_multiply_interval_vector(self): np.random.seed(1234) interval = {'start': 0.1, 'stop': 3.2, 'endpoint': True} @@ -232,7 +232,7 @@ def test_expm_multiply_interval_vector(self): assert_allclose(sol_given, correct) assert_allclose(sol_wrong, correct) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(20) def test_expm_multiply_interval_matrix(self): np.random.seed(1234) interval = {'start': 0.1, 'stop': 3.2, 'endpoint': True} diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index 1aa76b8ff26e..3c2a4d352a2a 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -2580,6 +2580,7 @@ def test_slicing_2(self): 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 = asmatrix(arange(50).reshape(5,10)) A = self.spcreator(B) @@ -3383,7 +3384,7 @@ def __arith_init(self): self.__Asp = self.spcreator(self.__A) self.__Bsp = self.spcreator(self.__B) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(20) def test_add_sub(self): self.__arith_init() @@ -5153,6 +5154,7 @@ def check(cls, method_name): def test_resiliency_limit_10(self, cls, method_name): self._check_resiliency(cls, method_name, maxval_limit=10) + @pytest.mark.fail_slow(2) @pytest.mark.parametrize('cls,method_name', cases_64bit()) def test_resiliency_random(self, cls, method_name): # bsr_matrix.eliminate_zeros relies on csr_matrix constructor @@ -5168,6 +5170,7 @@ def test_resiliency_all_32(self, cls, method_name): def test_resiliency_all_64(self, cls, method_name): self._check_resiliency(cls, method_name, fixed_dtype=np.int64) + @pytest.mark.fail_slow(5) @pytest.mark.parametrize('cls,method_name', cases_64bit()) def test_no_64(self, cls, method_name): self._check_resiliency(cls, method_name, assert_32bit=True) diff --git a/scipy/spatial/tests/test_kdtree.py b/scipy/spatial/tests/test_kdtree.py index b710c9df6a8f..462907ef9a58 100644 --- a/scipy/spatial/tests/test_kdtree.py +++ b/scipy/spatial/tests/test_kdtree.py @@ -971,7 +971,7 @@ def test_kdtree_list_k(kdtree_type): assert_equal(dd, np.ravel(dd1)) assert_equal(ii, np.ravel(ii1)) -@pytest.mark.fail_slow(5) +@pytest.mark.fail_slow(10) def test_kdtree_box(kdtree_type): # check ckdtree periodic boundary n = 2000 @@ -1146,7 +1146,7 @@ def test_kdtree_weights(kdtree_type): assert_raises(ValueError, tree1.count_neighbors, tree2, np.linspace(0, 10, 100), weights=w1) -@pytest.mark.fail_slow(5) +@pytest.mark.fail_slow(10) def test_kdtree_count_neighbous_multiple_r(kdtree_type): n = 2000 m = 2 diff --git a/scipy/spatial/tests/test_qhull.py b/scipy/spatial/tests/test_qhull.py index 94ed6c40a8dd..66b3c4f96193 100644 --- a/scipy/spatial/tests/test_qhull.py +++ b/scipy/spatial/tests/test_qhull.py @@ -327,7 +327,7 @@ def barycentric_transform(tr, x): ok = (j != -1) | at_boundary assert_(ok.all(), f"{err_msg} {np.nonzero(~ok)}") - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) def test_degenerate_barycentric_transforms(self): # The triangulation should not produce invalid barycentric # transforms that stump the simplex finding @@ -346,7 +346,7 @@ def test_degenerate_barycentric_transforms(self): self._check_barycentric_transforms(tri) @pytest.mark.slow - @pytest.mark.fail_slow(10) + @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 @@ -962,7 +962,7 @@ def test_furthest_site_flag(self): vor = Voronoi(points,furthest_site=True) assert_equal(vor.furthest_site,True) - @pytest.mark.fail_slow(5) + @pytest.mark.fail_slow(10) @pytest.mark.parametrize("name", sorted(INCREMENTAL_DATASETS)) def test_incremental(self, name): # Test incremental construction of the triangulation diff --git a/scipy/special/tests/test_basic.py b/scipy/special/tests/test_basic.py index 0ee5d90d722a..e33c44528353 100644 --- a/scipy/special/tests/test_basic.py +++ b/scipy/special/tests/test_basic.py @@ -1063,7 +1063,7 @@ def test_ai_zeros(self): array([0.5357]), array([0.7012])),4) - @pytest.mark.fail_slow(2) + @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) @@ -1088,7 +1088,7 @@ def test_ai_zeros_big(self): [-1.0187929716, -3.2481975822, -4.8200992112, -6.1633073556, -7.3721772550, -8.4884867340], rtol=1e-10) - @pytest.mark.fail_slow(2) + @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) diff --git a/scipy/special/tests/test_cython_special.py b/scipy/special/tests/test_cython_special.py index bfd28744747c..c5fd4530ee91 100644 --- a/scipy/special/tests/test_cython_special.py +++ b/scipy/special/tests/test_cython_special.py @@ -320,7 +320,7 @@ def test_cython_api_completeness(): raise RuntimeError(f"{name} missing from tests!") -@pytest.mark.fail_slow(5) +@pytest.mark.fail_slow(20) @pytest.mark.parametrize("param", PARAMS, ids=IDS) def test_cython_api(param): pyfunc, cyfunc, specializations, knownfailure = param diff --git a/scipy/special/tests/test_extending.py b/scipy/special/tests/test_extending.py index 57ab39a9d489..f5e941da953d 100644 --- a/scipy/special/tests/test_extending.py +++ b/scipy/special/tests/test_extending.py @@ -7,7 +7,7 @@ from scipy.special import beta, gamma -@pytest.mark.fail_slow(20) +@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.') diff --git a/scipy/special/tests/test_round.py b/scipy/special/tests/test_round.py index 07d3850f1084..877cb4a9dd22 100644 --- a/scipy/special/tests/test_round.py +++ b/scipy/special/tests/test_round.py @@ -4,14 +4,14 @@ from scipy.special import _test_internal -@pytest.mark.fail_slow(5) +@pytest.mark.fail_slow(20) @pytest.mark.skipif(not _test_internal.have_fenv(), reason="no fenv()") def test_add_round_up(): np.random.seed(1234) _test_internal.test_add_round(10**5, 'up') -@pytest.mark.fail_slow(5) +@pytest.mark.fail_slow(20) @pytest.mark.skipif(not _test_internal.have_fenv(), reason="no fenv()") def test_add_round_down(): np.random.seed(1234) diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index 6d0798409e51..fd5b16597da3 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -47,7 +47,7 @@ def test_rel_entr_generic(dtype): xp_assert_close(res, xp.asarray(ref), xp=xp) -@pytest.mark.fail_slow(2) +@pytest.mark.fail_slow(5) @array_api_compatible @given(data=strategies.data()) @pytest.mark.parametrize('f_name_n_args', array_special_func_map.items()) diff --git a/scipy/stats/tests/test_fast_gen_inversion.py b/scipy/stats/tests/test_fast_gen_inversion.py index e2d00134ccbf..63052f748bef 100644 --- a/scipy/stats/tests/test_fast_gen_inversion.py +++ b/scipy/stats/tests/test_fast_gen_inversion.py @@ -142,6 +142,7 @@ def test_geninvgauss_uerror(): # TODO: add more distributions +@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 diff --git a/scipy/stats/tests/test_fit.py b/scipy/stats/tests/test_fit.py index f187794d6fa3..5fae9aa4cb66 100644 --- a/scipy/stats/tests/test_fit.py +++ b/scipy/stats/tests/test_fit.py @@ -589,6 +589,7 @@ def test_truncpareto(self): assert_nlff_less_or_close(dist, data, res.params, shapes, **self.tols) + @pytest.mark.fail_slow(5) 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. diff --git a/scipy/stats/tests/test_mgc.py b/scipy/stats/tests/test_mgc.py index c82b81530d58..320f0b1edf98 100644 --- a/scipy/stats/tests/test_mgc.py +++ b/scipy/stats/tests/test_mgc.py @@ -194,7 +194,7 @@ def test_dist_perm(self): assert_approx_equal(stat_dist, 0.163, significant=1) assert_approx_equal(pvalue_dist, 0.001, significant=1) - @pytest.mark.fail_slow(10) # all other tests are XSLOW; we need at least one to run + @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): np.random.seed(12345678) diff --git a/scipy/stats/tests/test_resampling.py b/scipy/stats/tests/test_resampling.py index 2d296f4d0dc7..fecb32939a88 100644 --- a/scipy/stats/tests/test_resampling.py +++ b/scipy/stats/tests/test_resampling.py @@ -1097,6 +1097,7 @@ def statistic(*args, axis): 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 @@ -1924,6 +1925,7 @@ def test_batch_generator(self, iterable, batch, expected): got = list(_resampling._batch_generator(iterable, batch)) assert got == expected + @pytest.mark.fail_slow(2) def test_finite_precision_statistic(self): # Some statistics return numerically distinct values when the values # should be equal in theory. Test that `permutation_test` accounts diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 6d91e108ad25..b41fa77c94b0 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -588,7 +588,7 @@ def test_input_validation(self): with pytest.raises(ValueError, match=message): res.confidence_interval(method="exact") - @pytest.mark.fail_slow(2) + @pytest.mark.fail_slow(5) @pytest.mark.skip_xp_backends(np_only=True) @pytest.mark.xfail_on_32bit("Monte Carlo method needs > a few kB of memory") @pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided')) From 1f8133761692f41917042d91346b05c0d1fb1345 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 1 Jun 2024 17:37:35 -0700 Subject: [PATCH 329/500] MAINT: stats.bootstrap: FutureWarning about broadcasting --- scipy/stats/_resampling.py | 28 ++++++++++++++++++++++++++-- scipy/stats/tests/test_resampling.py | 21 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/scipy/stats/_resampling.py b/scipy/stats/_resampling.py index d89a0b993038..b4e06979bbda 100644 --- a/scipy/stats/_resampling.py +++ b/scipy/stats/_resampling.py @@ -185,9 +185,23 @@ def _bootstrap_iv(data, statistic, vectorized, paired, axis, confidence_level, if n_samples == 0: raise ValueError("`data` must contain at least one sample.") + message = ("Ignoring the dimension specified by `axis`, arrays in `data` do not " + "have the same shape. Beginning in SciPy 1.16.0, `bootstrap` will " + "explicitly broadcast elements of `data` to the same shape (ignoring " + "`axis`) before performing the calculation. To avoid this warning in " + "the meantime, ensure that all samples have the same shape (except " + "potentially along `axis`).") + data = [np.atleast_1d(sample) for sample in data] + reduced_shapes = set() + for sample in data: + reduced_shape = list(sample.shape) + reduced_shape.pop(axis) + reduced_shapes.add(tuple(reduced_shape)) + if len(reduced_shapes) != 1: + warnings.warn(message, FutureWarning, stacklevel=3) + data_iv = [] for sample in data: - sample = np.atleast_1d(sample) if sample.shape[axis_int] <= 1: raise ValueError("each sample in `data` must contain two or more " "observations along `axis`.") @@ -315,7 +329,17 @@ def bootstrap(data, statistic, *, n_resamples=9999, batch=None, Parameters ---------- data : sequence of array-like - Each element of data is a sample from an underlying distribution. + 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`). + + .. warning:: Beginning in SciPy 1.14.0, `bootstrap` will emit a `FutureWarning` + if the shapes of the elements of `data` are not the same (with the + exception of the dimension specified by `axis`). + Beginning in SciPy 1.16.0, `bootstrap` will explicitly broadcast + the elements to the same shape (except along `axis) before + performing the calculation. + statistic : callable Statistic for which the confidence interval is to be calculated. `statistic` must be a callable that accepts ``len(data)`` samples diff --git a/scipy/stats/tests/test_resampling.py b/scipy/stats/tests/test_resampling.py index 2d296f4d0dc7..8972d626d716 100644 --- a/scipy/stats/tests/test_resampling.py +++ b/scipy/stats/tests/test_resampling.py @@ -732,6 +732,27 @@ def statistic_extradim(*args, axis): ref.confidence_interval.high, atol=1e-15) +def test_gh_20850(): + rng = np.random.default_rng(2085020850) + x = rng.random((10, 2)) + y = 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 = "Ignoring the dimension specified by `axis`..." + with pytest.warns(FutureWarning, match=message): + stats.bootstrap((x, y[:10, 0]), statistic) # this won't work after 1.16 + with pytest.warns(FutureWarning, match=message): + stats.bootstrap((x, y[:10, 0:1]), statistic) # this will + with pytest.warns(FutureWarning, match=message): + stats.bootstrap((x.T, y.T[0:1, :10]), statistic, axis=1) # this will + + # --- Test Monte Carlo Hypothesis Test --- # class TestMonteCarloHypothesisTest: From 5ad23a9797dbb960b6b62fae0a0e05dc86668687 Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Sun, 2 Jun 2024 12:30:37 +0100 Subject: [PATCH 330/500] DEP: stats: remove `rvs_ratio_uniforms` --- doc/source/tutorial/stats/sampling.rst | 3 -- scipy/stats/__init__.py | 9 ----- scipy/stats/_rvs_sampling.py | 56 -------------------------- scipy/stats/meson.build | 1 - scipy/stats/tests/test_sampling.py | 3 -- scipy/stats/tests/test_stats.py | 18 --------- 6 files changed, 90 deletions(-) delete mode 100644 scipy/stats/_rvs_sampling.py diff --git a/doc/source/tutorial/stats/sampling.rst b/doc/source/tutorial/stats/sampling.rst index 1635a980d0e6..b406133f689a 100644 --- a/doc/source/tutorial/stats/sampling.rst +++ b/doc/source/tutorial/stats/sampling.rst @@ -49,9 +49,6 @@ Some methods to do that are: drawn between 0 to the value of the upper bound at Y. If this number is less than the PDF at Y, return the sample otherwise reject it. See :class:`~TransformedDensityRejection`. -* The Ratio-of-Uniforms Method: This is a type of acceptance-rejection - method which is uses minimal bounding rectangles to construct the hat - function. See `scipy.stats.rvs_ratio_uniforms`. * Inversion for Discrete Distributions: The difference compared to the continuous case is that :math:`F` is now a step-function. To realize this in a computer, a search algorithm is used, the simplest of which diff --git a/scipy/stats/__init__.py b/scipy/stats/__init__.py index 180f50a1756f..cf1c87a68230 100644 --- a/scipy/stats/__init__.py +++ b/scipy/stats/__init__.py @@ -521,14 +521,6 @@ stats.sampling -Random variate generation / CDF Inversion ------------------------------------------ - -.. autosummary:: - :toctree: generated/ - - rvs_ratio_uniforms - Fitting / Survival Analysis --------------------------- @@ -625,7 +617,6 @@ MonteCarloMethod, PermutationMethod, BootstrapMethod) from ._entropy import * from ._hypotests import * -from ._rvs_sampling import rvs_ratio_uniforms from ._page_trend_test import page_trend_test from ._mannwhitneyu import mannwhitneyu from ._bws_test import bws_test diff --git a/scipy/stats/_rvs_sampling.py b/scipy/stats/_rvs_sampling.py deleted file mode 100644 index 86adb251c3e5..000000000000 --- a/scipy/stats/_rvs_sampling.py +++ /dev/null @@ -1,56 +0,0 @@ -import warnings -from scipy.stats.sampling import RatioUniforms - -def rvs_ratio_uniforms(pdf, umax, vmin, vmax, size=1, c=0, random_state=None): - """ - Generate random samples from a probability density function using the - ratio-of-uniforms method. - - .. deprecated:: 1.12.0 - `rvs_ratio_uniforms` is deprecated in favour of - `scipy.stats.sampling.RatioUniforms` from version 1.12.0 and will - be removed in SciPy 1.15.0 - - 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. - size : int or tuple of ints, optional - Defining number of random variates (default is 1). - 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. - - Returns - ------- - rvs : ndarray - The random variates distributed according to the probability - distribution defined by the pdf. - - Notes - ----- - Please refer to `scipy.stats.sampling.RatioUniforms` for the documentation. - """ - warnings.warn("Please use `RatioUniforms` from the " - "`scipy.stats.sampling` namespace. The " - "`scipy.stats.rvs_ratio_uniforms` namespace is deprecated " - "and will be removed in SciPy 1.15.0", - category=DeprecationWarning, stacklevel=2) - gen = RatioUniforms(pdf, umax=umax, vmin=vmin, vmax=vmax, - c=c, random_state=random_state) - return gen.rvs(size) diff --git a/scipy/stats/meson.build b/scipy/stats/meson.build index bb43e3b2e98a..b52dc85128cd 100644 --- a/scipy/stats/meson.build +++ b/scipy/stats/meson.build @@ -137,7 +137,6 @@ py3.install_sources([ '_relative_risk.py', '_resampling.py', '_result_classes.py', - '_rvs_sampling.py', '_sampling.py', '_sensitivity_analysis.py', '_stats_mstats_common.py', diff --git a/scipy/stats/tests/test_sampling.py b/scipy/stats/tests/test_sampling.py index 82fe9c36ea19..99921adb0d9b 100644 --- a/scipy/stats/tests/test_sampling.py +++ b/scipy/stats/tests/test_sampling.py @@ -1377,9 +1377,6 @@ def test_bad_args(self): class TestRatioUniforms: - """ Tests for rvs_ratio_uniforms. - """ - def test_rv_generation(self): # use KS test to check distribution of rvs # normal distribution diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 6d91e108ad25..ae8cf0037f4c 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -8468,24 +8468,6 @@ def test_brunnermunzel_normal_dist(self): assert_equal(p, 0) -class TestRatioUniforms: - """ Tests for rvs_ratio_uniforms are in test_sampling.py, - as rvs_ratio_uniforms is deprecated and moved to stats.sampling """ - def test_consistency(self): - f = stats.norm.pdf - v = np.sqrt(f(np.sqrt(2))) * np.sqrt(2) - umax = np.sqrt(f(0)) - gen = stats.sampling.RatioUniforms(f, umax=umax, vmin=-v, vmax=v, - random_state=12345) - r1 = gen.rvs(10) - deprecation_msg = ("Please use `RatioUniforms` from the " - "`scipy.stats.sampling` namespace.") - with pytest.warns(DeprecationWarning, match=deprecation_msg): - r2 = stats.rvs_ratio_uniforms(f, umax, -v, v, size=10, - random_state=12345) - assert_equal(r1, r2) - - class TestQuantileTest: r""" Test the non-parametric quantile test, including the computation of confidence intervals From 850171ab633440181ca5baac60a69fb2711d680e Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Sun, 2 Jun 2024 12:46:32 +0100 Subject: [PATCH 331/500] DEP: signal: remove wavelet functions --- doc/source/conf.py | 7 - doc/source/dev/roadmap-detailed.rst | 5 - scipy/signal/__init__.py | 15 - scipy/signal/_wavelets.py | 527 ---------------------------- scipy/signal/tests/test_wavelets.py | 202 +++-------- scipy/signal/wavelets.py | 5 +- 6 files changed, 51 insertions(+), 710 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 7afb898c9a3d..51c18bf6babc 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -351,13 +351,6 @@ 'interp2d` is deprecated', # Deprecation of scipy.interpolate.interp2d 'scipy.misc', # scipy.misc deprecated in v1.10.0; use scipy.datasets '`kurtosistest` p-value may be', # intentionally "bad" example in docstring - 'scipy.signal.daub is deprecated', - 'scipy.signal.qmf is deprecated', - 'scipy.signal.cascade is deprecated', - 'scipy.signal.morlet is deprecated', - 'scipy.signal.morlet2 is deprecated', - 'scipy.signal.ricker is deprecated', - 'scipy.signal.cwt is deprecated', ): warnings.filterwarnings(action='ignore', message='.*' + key + '.*') diff --git a/doc/source/dev/roadmap-detailed.rst b/doc/source/dev/roadmap-detailed.rst index f528872497b1..bcdaf799f641 100644 --- a/doc/source/dev/roadmap-detailed.rst +++ b/doc/source/dev/roadmap-detailed.rst @@ -316,11 +316,6 @@ stable conversions to and from other filter representations. SOS filters could be considered as the default filtering method for ltisys objects, for their numerical stability. -*Wavelets*: what's there now doesn't make much sense. Continuous wavelets -only at the moment - decide whether to completely rewrite or remove them. -Discrete wavelet transforms are out of scope (PyWavelets does a good job -for those). - sparse `````` diff --git a/scipy/signal/__init__.py b/scipy/signal/__init__.py index f56cf4ef1fd3..374cb21f0846 100644 --- a/scipy/signal/__init__.py +++ b/scipy/signal/__init__.py @@ -236,20 +236,6 @@ get_window -- Return a window of a given length and type. -Wavelets -======== - -.. autosummary:: - :toctree: generated/ - - cascade -- Compute scaling function and wavelet from coefficients. - daub -- Return low-pass. - morlet -- Complex Morlet wavelet. - qmf -- Return quadrature mirror filter from low-pass. - ricker -- Return ricker wavelet. - morlet2 -- Return Morlet wavelet, compatible with cwt. - cwt -- Perform continuous wavelet transform. - Peak finding ============ @@ -323,7 +309,6 @@ from ._savitzky_golay import savgol_coeffs, savgol_filter from ._spectral_py import * from ._short_time_fft import * -from ._wavelets import * from ._peak_finding import * from ._czt import * from .windows import get_window # keep this one in signal namespace diff --git a/scipy/signal/_wavelets.py b/scipy/signal/_wavelets.py index 7cfc9f77d6f7..2b9f8fa32672 100644 --- a/scipy/signal/_wavelets.py +++ b/scipy/signal/_wavelets.py @@ -1,363 +1,6 @@ -import warnings - import numpy as np -from scipy.linalg import eig -from scipy.special import comb from scipy.signal import convolve -__all__ = ['daub', 'qmf', 'cascade', 'morlet', 'ricker', 'morlet2', 'cwt'] - - -_msg="""scipy.signal.%s is deprecated in SciPy 1.12 and will be removed -in SciPy 1.15. We recommend using PyWavelets instead. -""" - - -def daub(p): - """ - The coefficients for the FIR low-pass filter producing Daubechies wavelets. - - .. deprecated:: 1.12.0 - - scipy.signal.daub is deprecated in SciPy 1.12 and will be removed - in SciPy 1.15. We recommend using PyWavelets instead. - - p>=1 gives the order of the zero at f=1/2. - There are 2p filter coefficients. - - Parameters - ---------- - p : int - Order of the zero at f=1/2, can have values from 1 to 34. - - Returns - ------- - daub : ndarray - Return - - """ - warnings.warn(_msg % 'daub', DeprecationWarning, stacklevel=2) - - sqrt = np.sqrt - if p < 1: - raise ValueError("p must be at least 1.") - if p == 1: - c = 1 / sqrt(2) - return np.array([c, c]) - elif p == 2: - f = sqrt(2) / 8 - c = sqrt(3) - return f * np.array([1 + c, 3 + c, 3 - c, 1 - c]) - elif p == 3: - tmp = 12 * sqrt(10) - z1 = 1.5 + sqrt(15 + tmp) / 6 - 1j * (sqrt(15) + sqrt(tmp - 15)) / 6 - z1c = np.conj(z1) - f = sqrt(2) / 8 - d0 = np.real((1 - z1) * (1 - z1c)) - a0 = np.real(z1 * z1c) - a1 = 2 * np.real(z1) - return f / d0 * np.array([a0, 3 * a0 - a1, 3 * a0 - 3 * a1 + 1, - a0 - 3 * a1 + 3, 3 - a1, 1]) - elif p < 35: - # construct polynomial and factor it - if p < 35: - P = [comb(p - 1 + k, k, exact=1) for k in range(p)][::-1] - yj = np.roots(P) - else: # try different polynomial --- needs work - P = [comb(p - 1 + k, k, exact=1) / 4.0**k - for k in range(p)][::-1] - yj = np.roots(P) / 4 - # for each root, compute two z roots, select the one with |z|>1 - # Build up final polynomial - c = np.poly1d([1, 1])**p - q = np.poly1d([1]) - for k in range(p - 1): - yval = yj[k] - part = 2 * sqrt(yval * (yval - 1)) - const = 1 - 2 * yval - z1 = const + part - if (abs(z1)) < 1: - z1 = const - part - q = q * [1, -z1] - - q = c * np.real(q) - # Normalize result - q = q / np.sum(q) * sqrt(2) - return q.c[::-1] - else: - raise ValueError("Polynomial factorization does not work " - "well for p too large.") - - -def qmf(hk): - """ - Return high-pass qmf filter from low-pass - - .. deprecated:: 1.12.0 - - scipy.signal.qmf is deprecated in SciPy 1.12 and will be removed - in SciPy 1.15. We recommend using PyWavelets instead. - - Parameters - ---------- - hk : array_like - Coefficients of high-pass filter. - - Returns - ------- - array_like - High-pass filter coefficients. - - """ - warnings.warn(_msg % 'qmf', DeprecationWarning, stacklevel=2) - - N = len(hk) - 1 - asgn = [{0: 1, 1: -1}[k % 2] for k in range(N + 1)] - return hk[::-1] * np.array(asgn) - - -def cascade(hk, J=7): - """ - Return (x, phi, psi) at dyadic points ``K/2**J`` from filter coefficients. - - .. deprecated:: 1.12.0 - - scipy.signal.cascade is deprecated in SciPy 1.12 and will be removed - in SciPy 1.15. We recommend using PyWavelets instead. - - Parameters - ---------- - hk : array_like - Coefficients of low-pass filter. - J : int, optional - Values will be computed at grid points ``K/2**J``. Default is 7. - - Returns - ------- - x : ndarray - The dyadic points ``K/2**J`` for ``K=0...N * (2**J)-1`` where - ``len(hk) = len(gk) = N+1``. - phi : ndarray - The scaling function ``phi(x)`` at `x`: - ``phi(x) = sum(hk * phi(2x-k))``, where k is from 0 to N. - psi : ndarray, optional - The wavelet function ``psi(x)`` at `x`: - ``phi(x) = sum(gk * phi(2x-k))``, where k is from 0 to N. - `psi` is only returned if `gk` is not None. - - Notes - ----- - The algorithm uses the vector cascade algorithm described by Strang and - Nguyen in "Wavelets and Filter Banks". It builds a dictionary of values - and slices for quick reuse. Then inserts vectors into final vector at the - end. - - """ - warnings.warn(_msg % 'cascade', DeprecationWarning, stacklevel=2) - - N = len(hk) - 1 - - if (J > 30 - np.log2(N + 1)): - raise ValueError("Too many levels.") - if (J < 1): - raise ValueError("Too few levels.") - - # construct matrices needed - nn, kk = np.ogrid[:N, :N] - s2 = np.sqrt(2) - # append a zero so that take works - thk = np.r_[hk, 0] - gk = qmf(hk) - tgk = np.r_[gk, 0] - - indx1 = np.clip(2 * nn - kk, -1, N + 1) - indx2 = np.clip(2 * nn - kk + 1, -1, N + 1) - m = np.empty((2, 2, N, N), 'd') - m[0, 0] = np.take(thk, indx1, 0) - m[0, 1] = np.take(thk, indx2, 0) - m[1, 0] = np.take(tgk, indx1, 0) - m[1, 1] = np.take(tgk, indx2, 0) - m *= s2 - - # construct the grid of points - x = np.arange(0, N * (1 << J), dtype=float) / (1 << J) - phi = 0 * x - - psi = 0 * x - - # find phi0, and phi1 - lam, v = eig(m[0, 0]) - ind = np.argmin(np.absolute(lam - 1)) - # a dictionary with a binary representation of the - # evaluation points x < 1 -- i.e. position is 0.xxxx - v = np.real(v[:, ind]) - # need scaling function to integrate to 1 so find - # eigenvector normalized to sum(v,axis=0)=1 - sm = np.sum(v) - if sm < 0: # need scaling function to integrate to 1 - v = -v - sm = -sm - bitdic = {'0': v / sm} - bitdic['1'] = np.dot(m[0, 1], bitdic['0']) - step = 1 << J - phi[::step] = bitdic['0'] - phi[(1 << (J - 1))::step] = bitdic['1'] - psi[::step] = np.dot(m[1, 0], bitdic['0']) - psi[(1 << (J - 1))::step] = np.dot(m[1, 1], bitdic['0']) - # descend down the levels inserting more and more values - # into bitdic -- store the values in the correct location once we - # have computed them -- stored in the dictionary - # for quicker use later. - prevkeys = ['1'] - for level in range(2, J + 1): - newkeys = ['%d%s' % (xx, yy) for xx in [0, 1] for yy in prevkeys] - fac = 1 << (J - level) - for key in newkeys: - # convert key to number - num = 0 - for pos in range(level): - if key[pos] == '1': - num += (1 << (level - 1 - pos)) - pastphi = bitdic[key[1:]] - ii = int(key[0]) - temp = np.dot(m[0, ii], pastphi) - bitdic[key] = temp - phi[num * fac::step] = temp - psi[num * fac::step] = np.dot(m[1, ii], pastphi) - prevkeys = newkeys - - return x, phi, psi - - -def morlet(M, w=5.0, s=1.0, complete=True): - """ - Complex Morlet wavelet. - - .. deprecated:: 1.12.0 - - scipy.signal.morlet is deprecated in SciPy 1.12 and will be removed - in SciPy 1.15. We recommend using PyWavelets instead. - - Parameters - ---------- - M : int - Length of the wavelet. - w : float, optional - Omega0. Default is 5 - s : float, optional - Scaling factor, windowed from ``-s*2*pi`` to ``+s*2*pi``. Default is 1. - complete : bool, optional - Whether to use the complete or the standard version. - - Returns - ------- - morlet : (M,) ndarray - - See Also - -------- - morlet2 : Implementation of Morlet wavelet, compatible with `cwt`. - scipy.signal.gausspulse - - Notes - ----- - The standard version:: - - pi**-0.25 * exp(1j*w*x) * exp(-0.5*(x**2)) - - This commonly used wavelet is often referred to simply as the - Morlet wavelet. Note that this simplified version can cause - admissibility problems at low values of `w`. - - The complete version:: - - pi**-0.25 * (exp(1j*w*x) - exp(-0.5*(w**2))) * exp(-0.5*(x**2)) - - This version has a correction - term to improve admissibility. For `w` greater than 5, the - correction term is negligible. - - Note that the energy of the return wavelet is not normalised - according to `s`. - - The fundamental frequency of this wavelet in Hz is given - by ``f = 2*s*w*r / M`` where `r` is the sampling rate. - - Note: This function was created before `cwt` and is not compatible - with it. - - Examples - -------- - >>> from scipy import signal - >>> import matplotlib.pyplot as plt - - >>> M = 100 - >>> s = 4.0 - >>> w = 2.0 - >>> wavelet = signal.morlet(M, s, w) - >>> plt.plot(wavelet.real, label="real") - >>> plt.plot(wavelet.imag, label="imag") - >>> plt.legend() - >>> plt.show() - - """ - warnings.warn(_msg % 'morlet', DeprecationWarning, stacklevel=2) - - x = np.linspace(-s * 2 * np.pi, s * 2 * np.pi, M) - output = np.exp(1j * w * x) - - if complete: - output -= np.exp(-0.5 * (w**2)) - - output *= np.exp(-0.5 * (x**2)) * np.pi**(-0.25) - - return output - - -def ricker(points, a): - """ - Return a Ricker wavelet, also known as the "Mexican hat wavelet". - - .. deprecated:: 1.12.0 - - scipy.signal.ricker is deprecated in SciPy 1.12 and will be removed - in SciPy 1.15. We recommend using PyWavelets instead. - - It models the function: - - ``A * (1 - (x/a)**2) * exp(-0.5*(x/a)**2)``, - - where ``A = 2/(sqrt(3*a)*(pi**0.25))``. - - Parameters - ---------- - points : int - Number of points in `vector`. - Will be centered around 0. - a : scalar - Width parameter of the wavelet. - - Returns - ------- - vector : (N,) ndarray - Array of length `points` in shape of ricker curve. - - Examples - -------- - >>> from scipy import signal - >>> import matplotlib.pyplot as plt - - >>> points = 100 - >>> a = 4.0 - >>> vec2 = signal.ricker(points, a) - >>> print(len(vec2)) - 100 - >>> plt.plot(vec2) - >>> plt.show() - - """ - warnings.warn(_msg % 'ricker', DeprecationWarning, stacklevel=2) - return _ricker(points, a) - def _ricker(points, a): A = 2 / (np.sqrt(3 * a) * (np.pi**0.25)) @@ -370,176 +13,6 @@ def _ricker(points, a): return total -def morlet2(M, s, w=5): - """ - Complex Morlet wavelet, designed to work with `cwt`. - - .. deprecated:: 1.12.0 - - scipy.signal.morlet2 is deprecated in SciPy 1.12 and will be removed - in SciPy 1.15. We recommend using PyWavelets instead. - - Returns the complete version of morlet wavelet, normalised - according to `s`:: - - exp(1j*w*x/s) * exp(-0.5*(x/s)**2) * pi**(-0.25) * sqrt(1/s) - - Parameters - ---------- - M : int - Length of the wavelet. - s : float - Width parameter of the wavelet. - w : float, optional - Omega0. Default is 5 - - Returns - ------- - morlet : (M,) ndarray - - See Also - -------- - morlet : Implementation of Morlet wavelet, incompatible with `cwt` - - Notes - ----- - - .. versionadded:: 1.4.0 - - This function was designed to work with `cwt`. Because `morlet2` - returns an array of complex numbers, the `dtype` argument of `cwt` - should be set to `complex128` for best results. - - Note the difference in implementation with `morlet`. - The fundamental frequency of this wavelet in Hz is given by:: - - f = w*fs / (2*s*np.pi) - - where ``fs`` is the sampling rate and `s` is the wavelet width parameter. - Similarly we can get the wavelet width parameter at ``f``:: - - s = w*fs / (2*f*np.pi) - - Examples - -------- - >>> import numpy as np - >>> from scipy import signal - >>> import matplotlib.pyplot as plt - - >>> M = 100 - >>> s = 4.0 - >>> w = 2.0 - >>> wavelet = signal.morlet2(M, s, w) - >>> plt.plot(abs(wavelet)) - >>> plt.show() - - This example shows basic use of `morlet2` with `cwt` in time-frequency - analysis: - - >>> t, dt = np.linspace(0, 1, 200, retstep=True) - >>> fs = 1/dt - >>> w = 6. - >>> sig = np.cos(2*np.pi*(50 + 10*t)*t) + np.sin(40*np.pi*t) - >>> freq = np.linspace(1, fs/2, 100) - >>> widths = w*fs / (2*freq*np.pi) - >>> cwtm = signal.cwt(sig, signal.morlet2, widths, w=w) - >>> plt.pcolormesh(t, freq, np.abs(cwtm), cmap='viridis', shading='gouraud') - >>> plt.show() - - """ - warnings.warn(_msg % 'morlet2', DeprecationWarning, stacklevel=2) - - x = np.arange(0, M) - (M - 1.0) / 2 - x = x / s - wavelet = np.exp(1j * w * x) * np.exp(-0.5 * x**2) * np.pi**(-0.25) - output = np.sqrt(1/s) * wavelet - return output - - -def cwt(data, wavelet, widths, dtype=None, **kwargs): - """ - Continuous wavelet transform. - - .. deprecated:: 1.12.0 - - scipy.signal.cwt is deprecated in SciPy 1.12 and will be removed - in SciPy 1.15. We recommend using PyWavelets instead. - - Performs a continuous wavelet transform on `data`, - using the `wavelet` function. A CWT performs a convolution - with `data` using the `wavelet` function, which is characterized - by a width parameter and length parameter. The `wavelet` function - is allowed to be complex. - - Parameters - ---------- - data : (N,) ndarray - data on which to perform the transform. - wavelet : function - Wavelet function, which should take 2 arguments. - The first argument is the number of points that the returned vector - will have (len(wavelet(length,width)) == length). - The second is a width parameter, defining the size of the wavelet - (e.g. standard deviation of a gaussian). See `ricker`, which - satisfies these requirements. - widths : (M,) sequence - Widths to use for transform. - dtype : data-type, optional - The desired data type of output. Defaults to ``float64`` if the - output of `wavelet` is real and ``complex128`` if it is complex. - - .. versionadded:: 1.4.0 - - kwargs - Keyword arguments passed to wavelet function. - - .. versionadded:: 1.4.0 - - Returns - ------- - cwt: (M, N) ndarray - Will have shape of (len(widths), len(data)). - - Notes - ----- - - .. versionadded:: 1.4.0 - - For non-symmetric, complex-valued wavelets, the input signal is convolved - with the time-reversed complex-conjugate of the wavelet data [1]. - - :: - - length = min(10 * width[ii], len(data)) - cwt[ii,:] = signal.convolve(data, np.conj(wavelet(length, width[ii], - **kwargs))[::-1], mode='same') - - References - ---------- - .. [1] S. Mallat, "A Wavelet Tour of Signal Processing (3rd Edition)", - Academic Press, 2009. - - Examples - -------- - >>> import numpy as np - >>> from scipy import signal - >>> import matplotlib.pyplot as plt - >>> t = np.linspace(-1, 1, 200, endpoint=False) - >>> sig = np.cos(2 * np.pi * 7 * t) + signal.gausspulse(t - 0.4, fc=2) - >>> widths = np.arange(1, 31) - >>> cwtmatr = signal.cwt(sig, signal.ricker, widths) - - .. note:: For cwt matrix plotting it is advisable to flip the y-axis - - >>> cwtmatr_yflip = np.flipud(cwtmatr) - >>> plt.imshow(cwtmatr_yflip, extent=[-1, 1, 1, 31], cmap='PRGn', aspect='auto', - ... vmax=abs(cwtmatr).max(), vmin=-abs(cwtmatr).max()) - >>> plt.show() - """ - warnings.warn(_msg % 'cwt', DeprecationWarning, stacklevel=2) - return _cwt(data, wavelet, widths, dtype, **kwargs) - - def _cwt(data, wavelet, widths, dtype=None, **kwargs): # Determine output type if dtype is None: diff --git a/scipy/signal/tests/test_wavelets.py b/scipy/signal/tests/test_wavelets.py index e83e6918429b..7a357d2eaf4a 100644 --- a/scipy/signal/tests/test_wavelets.py +++ b/scipy/signal/tests/test_wavelets.py @@ -1,161 +1,59 @@ import numpy as np -from numpy.testing import (assert_equal, - assert_array_equal, assert_array_almost_equal, assert_array_less, assert_,) -import pytest +from numpy.testing import assert_array_equal, assert_array_almost_equal import scipy.signal._wavelets as wavelets class TestWavelets: - def test_qmf(self): - with pytest.deprecated_call(): - assert_array_equal(wavelets.qmf([1, 1]), [1, -1]) - - def test_daub(self): - with pytest.deprecated_call(): - for i in range(1, 15): - assert_equal(len(wavelets.daub(i)), i * 2) - - def test_cascade(self): - with pytest.deprecated_call(): - for J in range(1, 7): - for i in range(1, 5): - lpcoef = wavelets.daub(i) - k = len(lpcoef) - x, phi, psi = wavelets.cascade(lpcoef, J) - assert_(len(x) == len(phi) == len(psi)) - assert_equal(len(x), (k - 1) * 2 ** J) - - def test_morlet(self): - with pytest.deprecated_call(): - x = wavelets.morlet(50, 4.1, complete=True) - y = wavelets.morlet(50, 4.1, complete=False) - # Test if complete and incomplete wavelet have same lengths: - assert_equal(len(x), len(y)) - # Test if complete wavelet is less than incomplete wavelet: - assert_array_less(x, y) - - x = wavelets.morlet(10, 50, complete=False) - y = wavelets.morlet(10, 50, complete=True) - # For large widths complete and incomplete wavelets should be - # identical within numerical precision: - assert_equal(x, y) - - # miscellaneous tests: - x = np.array([1.73752399e-09 + 9.84327394e-25j, - 6.49471756e-01 + 0.00000000e+00j, - 1.73752399e-09 - 9.84327394e-25j]) - y = wavelets.morlet(3, w=2, complete=True) - assert_array_almost_equal(x, y) - - x = np.array([2.00947715e-09 + 9.84327394e-25j, - 7.51125544e-01 + 0.00000000e+00j, - 2.00947715e-09 - 9.84327394e-25j]) - y = wavelets.morlet(3, w=2, complete=False) - assert_array_almost_equal(x, y, decimal=2) - - x = wavelets.morlet(10000, s=4, complete=True) - y = wavelets.morlet(20000, s=8, complete=True)[5000:15000] - assert_array_almost_equal(x, y, decimal=2) - - x = wavelets.morlet(10000, s=4, complete=False) - assert_array_almost_equal(y, x, decimal=2) - y = wavelets.morlet(20000, s=8, complete=False)[5000:15000] - assert_array_almost_equal(x, y, decimal=2) - - x = wavelets.morlet(10000, w=3, s=5, complete=True) - y = wavelets.morlet(20000, w=3, s=10, complete=True)[5000:15000] - assert_array_almost_equal(x, y, decimal=2) - - x = wavelets.morlet(10000, w=3, s=5, complete=False) - assert_array_almost_equal(y, x, decimal=2) - y = wavelets.morlet(20000, w=3, s=10, complete=False)[5000:15000] - assert_array_almost_equal(x, y, decimal=2) - - x = wavelets.morlet(10000, w=7, s=10, complete=True) - y = wavelets.morlet(20000, w=7, s=20, complete=True)[5000:15000] - assert_array_almost_equal(x, y, decimal=2) - - x = wavelets.morlet(10000, w=7, s=10, complete=False) - assert_array_almost_equal(x, y, decimal=2) - y = wavelets.morlet(20000, w=7, s=20, complete=False)[5000:15000] - assert_array_almost_equal(x, y, decimal=2) - - def test_morlet2(self): - with pytest.deprecated_call(): - w = wavelets.morlet2(1.0, 0.5) - expected = (np.pi**(-0.25) * np.sqrt(1/0.5)).astype(complex) - assert_array_equal(w, expected) - - lengths = [5, 11, 15, 51, 101] - for length in lengths: - w = wavelets.morlet2(length, 1.0) - assert_(len(w) == length) - max_loc = np.argmax(w) - assert_(max_loc == (length // 2)) - - points = 100 - w = abs(wavelets.morlet2(points, 2.0)) - half_vec = np.arange(0, points // 2) - assert_array_almost_equal(w[half_vec], w[-(half_vec + 1)]) - - x = np.array([5.03701224e-09 + 2.46742437e-24j, - 1.88279253e+00 + 0.00000000e+00j, - 5.03701224e-09 - 2.46742437e-24j]) - y = wavelets.morlet2(3, s=1/(2*np.pi), w=2) - assert_array_almost_equal(x, y) - def test_ricker(self): - with pytest.deprecated_call(): - 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) + 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): - with pytest.deprecated_call(): - 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)) + 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/scipy/signal/wavelets.py b/scipy/signal/wavelets.py index d29deac58eb9..fc897a248353 100644 --- a/scipy/signal/wavelets.py +++ b/scipy/signal/wavelets.py @@ -4,10 +4,7 @@ from scipy._lib.deprecation import _sub_module_deprecation -__all__ = [ # noqa: F822 - 'daub', 'qmf', 'cascade', 'morlet', 'ricker', 'morlet2', 'cwt', - 'convolve' -] +__all__: list[str] = [] def __dir__(): From 860ede04e2d1a673dcc6c22fb4be16720f78ba1e Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Sun, 2 Jun 2024 12:57:54 +0100 Subject: [PATCH 332/500] DEP: integrate: remove quadrature and romberg --- scipy/integrate/__init__.py | 3 - scipy/integrate/_quadrature.py | 335 +---------------------- scipy/integrate/tests/test_quadrature.py | 77 +----- 3 files changed, 5 insertions(+), 410 deletions(-) diff --git a/scipy/integrate/__init__.py b/scipy/integrate/__init__.py index 039234777d35..b452d5fe87a8 100644 --- a/scipy/integrate/__init__.py +++ b/scipy/integrate/__init__.py @@ -17,12 +17,9 @@ tplquad -- General purpose triple integration nquad -- General purpose N-D integration fixed_quad -- Integrate func(x) using Gaussian quadrature of order n - quadrature -- Integrate with given tolerance using Gaussian quadrature - romberg -- Integrate func using Romberg integration newton_cotes -- Weights and error coefficient for Newton-Cotes integration qmc_quad -- N-D integration using Quasi-Monte Carlo quadrature IntegrationWarning -- Warning on issues during integration - AccuracyWarning -- Warning on issues during quadrature integration Integrating functions, given fixed samples ========================================== diff --git a/scipy/integrate/_quadrature.py b/scipy/integrate/_quadrature.py index 7fe4ef9424eb..9da7b85329ff 100644 --- a/scipy/integrate/_quadrature.py +++ b/scipy/integrate/_quadrature.py @@ -9,13 +9,12 @@ from scipy.special import roots_legendre from scipy.special import gammaln, logsumexp from scipy._lib._util import _rng_spawn -from scipy._lib.deprecation import _deprecated -__all__ = ['fixed_quad', 'quadrature', 'romberg', 'romb', +__all__ = ['fixed_quad', 'romb', 'trapezoid', 'simpson', 'cumulative_trapezoid', 'newton_cotes', - 'qmc_quad', 'AccuracyWarning', 'cumulative_simpson'] + 'qmc_quad', 'cumulative_simpson'] def trapezoid(y, x=None, dx=1.0, axis=-1): @@ -148,10 +147,6 @@ def trapezoid(y, x=None, dx=1.0, axis=-1): return ret -class AccuracyWarning(Warning): - pass - - if TYPE_CHECKING: # workaround for mypy function attributes see: # https://github.com/python/mypy/issues/2087#issuecomment-462726600 @@ -250,142 +245,6 @@ def fixed_quad(func, a, b, args=(), n=5): return (b-a)/2.0 * np.sum(w*func(y, *args), axis=-1), None -def vectorize1(func, args=(), vec_func=False): - """Vectorize the call to a function. - - This is an internal utility function used by `romberg` and - `quadrature` to create a vectorized version of a function. - - If `vec_func` is True, the function `func` is assumed to take vector - arguments. - - Parameters - ---------- - func : callable - User defined function. - args : tuple, optional - Extra arguments for the function. - vec_func : bool, optional - True if the function func takes vector arguments. - - Returns - ------- - vfunc : callable - A function that will take a vector argument and return the - result. - - """ - if vec_func: - def vfunc(x): - return func(x, *args) - else: - def vfunc(x): - if np.isscalar(x): - return func(x, *args) - x = np.asarray(x) - # call with first point to get output type - y0 = func(x[0], *args) - n = len(x) - dtype = getattr(y0, 'dtype', type(y0)) - output = np.empty((n,), dtype=dtype) - output[0] = y0 - for i in range(1, n): - output[i] = func(x[i], *args) - return output - return vfunc - - -@_deprecated("`scipy.integrate.quadrature` is deprecated as of SciPy 1.12.0" - "and will be removed in SciPy 1.15.0. Please use" - "`scipy.integrate.quad` instead.") -def quadrature(func, a, b, args=(), tol=1.49e-8, rtol=1.49e-8, maxiter=50, - vec_func=True, miniter=1): - """ - Compute a definite integral using fixed-tolerance Gaussian quadrature. - - .. deprecated:: 1.12.0 - - This function is deprecated as of SciPy 1.12.0 and will be removed - in SciPy 1.15.0. Please use `scipy.integrate.quad` instead. - - Integrate `func` from `a` to `b` using Gaussian quadrature - with absolute tolerance `tol`. - - Parameters - ---------- - func : function - A Python function or method to integrate. - a : float - Lower limit of integration. - b : float - Upper limit of integration. - args : tuple, optional - Extra arguments to pass to function. - tol, rtol : float, optional - Iteration stops when error between last two iterates is less than - `tol` OR the relative change is less than `rtol`. - maxiter : int, optional - Maximum order of Gaussian quadrature. - vec_func : bool, optional - True or False if func handles arrays as arguments (is - a "vector" function). Default is True. - miniter : int, optional - Minimum order of Gaussian quadrature. - - Returns - ------- - val : float - Gaussian quadrature approximation (within tolerance) to integral. - err : float - Difference between last two estimates of the integral. - - See Also - -------- - fixed_quad : fixed-order Gaussian quadrature - quad : adaptive quadrature using QUADPACK - dblquad : double integrals - tplquad : triple integrals - romb : integrator for sampled data - simpson : integrator for sampled data - cumulative_trapezoid : cumulative integration for sampled data - - Examples - -------- - >>> from scipy import integrate - >>> import numpy as np - >>> f = lambda x: x**8 - >>> integrate.quadrature(f, 0.0, 1.0) - (0.11111111111111106, 4.163336342344337e-17) - >>> print(1/9.0) # analytical result - 0.1111111111111111 - - >>> integrate.quadrature(np.cos, 0.0, np.pi/2) - (0.9999999999999536, 3.9611425250996035e-11) - >>> np.sin(np.pi/2)-np.sin(0) # analytical result - 1.0 - - """ - if not isinstance(args, tuple): - args = (args,) - vfunc = vectorize1(func, args, vec_func=vec_func) - val = np.inf - err = np.inf - maxiter = max(miniter+1, maxiter) - for n in range(miniter, maxiter+1): - newval = fixed_quad(vfunc, a, b, (), n)[0] - err = abs(newval-val) - val = newval - - if err < tol or err < rtol*abs(val): - break - else: - warnings.warn( - "maxiter (%d) exceeded. Latest difference = %e" % (maxiter, err), - AccuracyWarning, stacklevel=2 - ) - return val, err - - def tupleset(t, i, value): l = list(t) l[i] = value @@ -1064,196 +923,6 @@ def romb(y, dx=1.0, axis=-1, show=False): return R[(k, k)] -# Romberg quadratures for numeric integration. -# -# Written by Scott M. Ransom -# last revision: 14 Nov 98 -# -# Cosmetic changes by Konrad Hinsen -# last revision: 1999-7-21 -# -# Adapted to SciPy by Travis Oliphant -# last revision: Dec 2001 - - -def _difftrap(function, interval, numtraps): - """ - Perform part of the trapezoidal rule to integrate a function. - Assume that we had called difftrap with all lower powers-of-2 - starting with 1. Calling difftrap only returns the summation - of the new ordinates. It does _not_ multiply by the width - of the trapezoids. This must be performed by the caller. - 'function' is the function to evaluate (must accept vector arguments). - 'interval' is a sequence with lower and upper limits - of integration. - 'numtraps' is the number of trapezoids to use (must be a - power-of-2). - """ - if numtraps <= 0: - raise ValueError("numtraps must be > 0 in difftrap().") - elif numtraps == 1: - return 0.5*(function(interval[0])+function(interval[1])) - else: - numtosum = numtraps/2 - h = float(interval[1]-interval[0])/numtosum - lox = interval[0] + 0.5 * h - points = lox + h * np.arange(numtosum) - s = np.sum(function(points), axis=0) - return s - - -def _romberg_diff(b, c, k): - """ - Compute the differences for the Romberg quadrature corrections. - See Forman Acton's "Real Computing Made Real," p 143. - """ - tmp = 4.0**k - return (tmp * c - b)/(tmp - 1.0) - - -def _printresmat(function, interval, resmat): - # Print the Romberg result matrix. - i = j = 0 - print('Romberg integration of', repr(function), end=' ') - print('from', interval) - print('') - print('%6s %9s %9s' % ('Steps', 'StepSize', 'Results')) - for i in range(len(resmat)): - print('%6d %9f' % (2**i, (interval[1]-interval[0])/(2.**i)), end=' ') - for j in range(i+1): - print('%9f' % (resmat[i][j]), end=' ') - print('') - print('') - print('The final result is', resmat[i][j], end=' ') - print('after', 2**(len(resmat)-1)+1, 'function evaluations.') - - -@_deprecated("`scipy.integrate.romberg` is deprecated as of SciPy 1.12.0" - "and will be removed in SciPy 1.15.0. Please use" - "`scipy.integrate.quad` instead.") -def romberg(function, a, b, args=(), tol=1.48e-8, rtol=1.48e-8, show=False, - divmax=10, vec_func=False): - """ - Romberg integration of a callable function or method. - - .. deprecated:: 1.12.0 - - This function is deprecated as of SciPy 1.12.0 and will be removed - in SciPy 1.15.0. Please use `scipy.integrate.quad` instead. - - Returns the integral of `function` (a function of one variable) - over the interval (`a`, `b`). - - If `show` is 1, the triangular array of the intermediate results - will be printed. If `vec_func` is True (default is False), then - `function` is assumed to support vector arguments. - - Parameters - ---------- - function : callable - Function to be integrated. - a : float - Lower limit of integration. - b : float - Upper limit of integration. - - Returns - ------- - results : float - Result of the integration. - - Other Parameters - ---------------- - args : tuple, optional - Extra arguments to pass to function. Each element of `args` will - be passed as a single argument to `func`. Default is to pass no - extra arguments. - tol, rtol : float, optional - The desired absolute and relative tolerances. Defaults are 1.48e-8. - show : bool, optional - Whether to print the results. Default is False. - divmax : int, optional - Maximum order of extrapolation. Default is 10. - vec_func : bool, optional - Whether `func` handles arrays as arguments (i.e., whether it is a - "vector" function). Default is False. - - See Also - -------- - fixed_quad : Fixed-order Gaussian quadrature. - quad : Adaptive quadrature using QUADPACK. - dblquad : Double integrals. - tplquad : Triple integrals. - romb : Integrators for sampled data. - simpson : Integrators for sampled data. - cumulative_trapezoid : Cumulative integration for sampled data. - - References - ---------- - .. [1] 'Romberg's method' https://en.wikipedia.org/wiki/Romberg%27s_method - - Examples - -------- - Integrate a gaussian from 0 to 1 and compare to the error function. - - >>> from scipy import integrate - >>> from scipy.special import erf - >>> import numpy as np - >>> gaussian = lambda x: 1/np.sqrt(np.pi) * np.exp(-x**2) - >>> result = integrate.romberg(gaussian, 0, 1, show=True) - Romberg integration of from [0, 1] - - :: - - Steps StepSize Results - 1 1.000000 0.385872 - 2 0.500000 0.412631 0.421551 - 4 0.250000 0.419184 0.421368 0.421356 - 8 0.125000 0.420810 0.421352 0.421350 0.421350 - 16 0.062500 0.421215 0.421350 0.421350 0.421350 0.421350 - 32 0.031250 0.421317 0.421350 0.421350 0.421350 0.421350 0.421350 - - The final result is 0.421350396475 after 33 function evaluations. - - >>> print("%g %g" % (2*result, erf(1))) - 0.842701 0.842701 - - """ - if np.isinf(a) or np.isinf(b): - raise ValueError("Romberg integration only available " - "for finite limits.") - vfunc = vectorize1(function, args, vec_func=vec_func) - n = 1 - interval = [a, b] - intrange = b - a - ordsum = _difftrap(vfunc, interval, n) - result = intrange * ordsum - resmat = [[result]] - err = np.inf - last_row = resmat[0] - for i in range(1, divmax+1): - n *= 2 - ordsum += _difftrap(vfunc, interval, n) - row = [intrange * ordsum / n] - for k in range(i): - row.append(_romberg_diff(last_row[k], row[k], k+1)) - result = row[i] - lastresult = last_row[i-1] - if show: - resmat.append(row) - err = abs(result - lastresult) - if err < tol or err < rtol * abs(result): - break - last_row = row - else: - warnings.warn( - "divmax (%d) exceeded. Latest difference = %e" % (divmax, err), - AccuracyWarning, stacklevel=2) - - if show: - _printresmat(vfunc, interval, resmat) - return result - # Coefficients for Newton-Cotes quadrature # diff --git a/scipy/integrate/tests/test_quadrature.py b/scipy/integrate/tests/test_quadrature.py index 9006fb414152..25c28e342530 100644 --- a/scipy/integrate/tests/test_quadrature.py +++ b/scipy/integrate/tests/test_quadrature.py @@ -1,16 +1,14 @@ # mypy: disable-error-code="attr-defined" import pytest import numpy as np -from numpy import cos, sin, pi -from numpy.testing import (assert_equal, assert_almost_equal, assert_allclose, - assert_, suppress_warnings) +from numpy.testing import assert_equal, assert_almost_equal, assert_allclose from hypothesis import given import hypothesis.strategies as st import hypothesis.extra.numpy as hyp_num -from scipy.integrate import (quadrature, romberg, romb, newton_cotes, +from scipy.integrate import (romb, newton_cotes, cumulative_trapezoid, trapezoid, - quad, simpson, fixed_quad, AccuracyWarning, + quad, simpson, fixed_quad, qmc_quad, cumulative_simpson) from scipy.integrate._quadrature import _cumulative_simpson_unequal_intervals from scipy import stats, special @@ -32,59 +30,10 @@ def test_vector(self): assert_allclose(got, expected, rtol=1e-12) -@pytest.mark.filterwarnings('ignore::DeprecationWarning') class TestQuadrature: def quad(self, x, a, b, args): raise NotImplementedError - def test_quadrature(self): - # Typical function with two extra arguments: - def myfunc(x, n, z): # Bessel function integrand - return cos(n*x-z*sin(x))/pi - val, err = quadrature(myfunc, 0, pi, (2, 1.8)) - table_val = 0.30614353532540296487 - assert_almost_equal(val, table_val, decimal=7) - - def test_quadrature_rtol(self): - def myfunc(x, n, z): # Bessel function integrand - return 1e90 * cos(n*x-z*sin(x))/pi - val, err = quadrature(myfunc, 0, pi, (2, 1.8), rtol=1e-10) - table_val = 1e90 * 0.30614353532540296487 - assert_allclose(val, table_val, rtol=1e-10) - - def test_quadrature_miniter(self): - # Typical function with two extra arguments: - def myfunc(x, n, z): # Bessel function integrand - return cos(n*x-z*sin(x))/pi - table_val = 0.30614353532540296487 - for miniter in [5, 52]: - val, err = quadrature(myfunc, 0, pi, (2, 1.8), miniter=miniter) - assert_almost_equal(val, table_val, decimal=7) - assert_(err < 1.0) - - def test_quadrature_single_args(self): - def myfunc(x, n): - return 1e90 * cos(n*x-1.8*sin(x))/pi - val, err = quadrature(myfunc, 0, pi, args=2, rtol=1e-10) - table_val = 1e90 * 0.30614353532540296487 - assert_allclose(val, table_val, rtol=1e-10) - - def test_romberg(self): - # Typical function with two extra arguments: - def myfunc(x, n, z): # Bessel function integrand - return cos(n*x-z*sin(x))/pi - val = romberg(myfunc, 0, pi, args=(2, 1.8)) - table_val = 0.30614353532540296487 - assert_almost_equal(val, table_val, decimal=7) - - def test_romberg_rtol(self): - # Typical function with two extra arguments: - def myfunc(x, n, z): # Bessel function integrand - return 1e19*cos(n*x-z*sin(x))/pi - val = romberg(myfunc, 0, pi, args=(2, 1.8), rtol=1e-10) - table_val = 1e19*0.30614353532540296487 - assert_allclose(val, table_val, rtol=1e-10) - def test_romb(self): assert_equal(romb(np.arange(17)), 128) @@ -96,19 +45,6 @@ def test_romb_gh_3731(self): val2, err = quad(lambda x: np.cos(0.2*x), x.min(), x.max()) assert_allclose(val, val2, rtol=1e-8, atol=0) - # should be equal to romb with 2**k+1 samples - with suppress_warnings() as sup: - sup.filter(AccuracyWarning, "divmax .4. exceeded") - val3 = romberg(lambda x: np.cos(0.2*x), x.min(), x.max(), divmax=4) - assert_allclose(val, val3, rtol=1e-12, atol=0) - - def test_non_dtype(self): - # Check that we work fine with functions returning float - import math - valmath = romberg(math.sin, 0, 1) - expected_val = 0.45969769413185085 - assert_almost_equal(valmath, expected_val, decimal=7) - def test_newton_cotes(self): """Test the first few degrees, for evenly spaced points.""" n = 1 @@ -239,13 +175,6 @@ def test_simpson_2d_integer_no_x(self, droplast): assert_equal(result, expected) -@pytest.mark.parametrize('func', [romberg, quadrature]) -def test_deprecate_integrator(func): - message = f"`scipy.integrate.{func.__name__}` is deprecated..." - with pytest.deprecated_call(match=message): - func(np.exp, 0, 1) - - class TestCumulative_trapezoid: def test_1d(self): x = np.linspace(-2, 2, num=5) From a38caca929d33ea119547c4802374c0b8aae4fd3 Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Mon, 3 Jun 2024 02:26:06 +0100 Subject: [PATCH 333/500] DOC: remove reference to removed functions --- scipy/signal/_peak_finding.py | 2 -- scipy/signal/_waveforms.py | 4 ---- 2 files changed, 6 deletions(-) diff --git a/scipy/signal/_peak_finding.py b/scipy/signal/_peak_finding.py index 3dbb04126325..b5ba280afb3f 100644 --- a/scipy/signal/_peak_finding.py +++ b/scipy/signal/_peak_finding.py @@ -1255,8 +1255,6 @@ def find_peaks_cwt(vector, widths, wavelet=None, max_distances=None, See Also -------- - cwt - Continuous wavelet transform. find_peaks Find peaks inside a signal based on peak properties. diff --git a/scipy/signal/_waveforms.py b/scipy/signal/_waveforms.py index 55bd045cdbf2..324e99c1c78c 100644 --- a/scipy/signal/_waveforms.py +++ b/scipy/signal/_waveforms.py @@ -203,10 +203,6 @@ def gausspulse(t, fc=1000, bw=0.5, bwr=-6, tpr=-60, retquad=False, yenv : ndarray Envelope of signal. Only returned if `retenv` is True. - See Also - -------- - scipy.signal.morlet - Examples -------- Plot real component, imaginary component, and envelope for a 5 Hz pulse, From fe9816c11255c1ec8f3c0ed566c392cc6bafb791 Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Mon, 3 Jun 2024 02:28:00 +0100 Subject: [PATCH 334/500] DOC: remove reference to removed functions --- scipy/special/_orthogonal.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/scipy/special/_orthogonal.py b/scipy/special/_orthogonal.py index c4b49cd8c6d4..e444195ae12b 100644 --- a/scipy/special/_orthogonal.py +++ b/scipy/special/_orthogonal.py @@ -239,7 +239,6 @@ def roots_jacobi(n, alpha, beta, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad References @@ -422,7 +421,6 @@ def roots_sh_jacobi(n, p1, q1, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad References @@ -534,7 +532,6 @@ def roots_genlaguerre(n, alpha, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad References @@ -704,7 +701,6 @@ def roots_laguerre(n, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad numpy.polynomial.laguerre.laggauss @@ -843,7 +839,6 @@ def roots_hermite(n, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad numpy.polynomial.hermite.hermgauss roots_hermitenorm @@ -1375,7 +1370,6 @@ def roots_hermitenorm(n, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad numpy.polynomial.hermite_e.hermegauss @@ -1508,7 +1502,6 @@ def roots_gegenbauer(n, alpha, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad References @@ -1668,7 +1661,6 @@ def roots_chebyt(n, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad numpy.polynomial.chebyshev.chebgauss @@ -1829,7 +1821,6 @@ def roots_chebyu(n, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad References @@ -1979,7 +1970,6 @@ def roots_chebyc(n, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad References @@ -2085,7 +2075,6 @@ def roots_chebys(n, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad References @@ -2192,7 +2181,6 @@ def roots_sh_chebyt(n, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad References @@ -2272,7 +2260,6 @@ def roots_sh_chebyu(n, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad References @@ -2355,7 +2342,6 @@ def roots_legendre(n, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad numpy.polynomial.legendre.leggauss @@ -2538,7 +2524,6 @@ def roots_sh_legendre(n, mu=False): See Also -------- - scipy.integrate.quadrature scipy.integrate.fixed_quad References From 83f8a1bf92a1c06ab673df5ce07005c2927c4932 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Mon, 3 Jun 2024 17:18:14 +1000 Subject: [PATCH 335/500] DOC: Update _differentialevolution.py [docs only] --- scipy/optimize/_differentialevolution.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index ab0c97b928e4..33007437b53d 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -105,7 +105,8 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', of 2 after ``popsize * (N - N_equal)``. tol : float, optional Relative tolerance for convergence, the solving stops when - ``np.std(population_energies) <= atol + tol * np.abs(np.mean(population_energies))``, + ``np.std(population_energies) <= atol + tol * + np.abs(np.mean(population_energies))``, where and `atol` and `tol` are the absolute and relative tolerance respectively. mutation : float or tuple(float, float), optional From e567cf41bcd197e8c7ab10cea97c654713e89775 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Mon, 3 Jun 2024 17:18:50 +1000 Subject: [PATCH 336/500] DOC: Update _differentialevolution.py [docs only] --- scipy/optimize/_differentialevolution.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index 33007437b53d..d5b0b8a6e3b6 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -576,7 +576,8 @@ class DifferentialEvolutionSolver: of 2 after ``popsize * (N - N_equal)``. tol : float, optional Relative tolerance for convergence, the solving stops when - ``np.std(population_energies) <= atol + tol * np.abs(np.mean(population_energies))``, + ``np.std(population_energies) <= atol + tol * + np.abs(np.mean(population_energies))``, where and `atol` and `tol` are the absolute and relative tolerance respectively. mutation : float or tuple(float, float), optional From 4944a8f86bb3110e0c91f6d45787358f9e834a48 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Mon, 3 Jun 2024 18:18:23 +1000 Subject: [PATCH 337/500] DOC: Update _differentialevolution.py --- scipy/optimize/_differentialevolution.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index d5b0b8a6e3b6..4d2c0d339055 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -105,8 +105,7 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', of 2 after ``popsize * (N - N_equal)``. tol : float, optional Relative tolerance for convergence, the solving stops when - ``np.std(population_energies) <= atol + tol * - np.abs(np.mean(population_energies))``, + ``np.std(population_energies) <= atol + tol * np.abs(np.mean(population_energies))``, where and `atol` and `tol` are the absolute and relative tolerance respectively. mutation : float or tuple(float, float), optional @@ -482,7 +481,7 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', ... trial = np.where(crossovers, bprime, trial) ... return trial - """ + """# noqa: E501 # using a context manager means that any created Pool objects are # cleared up. @@ -576,8 +575,7 @@ class DifferentialEvolutionSolver: of 2 after ``popsize * (N - N_equal)``. tol : float, optional Relative tolerance for convergence, the solving stops when - ``np.std(population_energies) <= atol + tol * - np.abs(np.mean(population_energies))``, + ``np.std(population_energies) <= atol + tol * np.abs(np.mean(population_energies))``, where and `atol` and `tol` are the absolute and relative tolerance respectively. mutation : float or tuple(float, float), optional @@ -725,7 +723,7 @@ class DifferentialEvolutionSolver: ignored if ``workers != 1``. This option will override the `updating` keyword to ``updating='deferred'``. - """ + """ # noqa: E501 # Dispatch of mutation strategy method (binomial or exponential). _binomial = {'best1bin': '_best1', From 2550014d4941fa3963defd7563288c0993f8f155 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Mon, 3 Jun 2024 10:01:10 +0100 Subject: [PATCH 338/500] DEP: signal: remove `cmplx_sort` (#20859) --- scipy/signal/__init__.py | 1 - scipy/signal/_signaltools.py | 14 +------------- scipy/signal/signaltools.py | 2 +- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/scipy/signal/__init__.py b/scipy/signal/__init__.py index f56cf4ef1fd3..27a498c90a12 100644 --- a/scipy/signal/__init__.py +++ b/scipy/signal/__init__.py @@ -125,7 +125,6 @@ buttap -- Return (z,p,k) for analog prototype of Butterworth filter. cheb1ap -- Return (z,p,k) for type I Chebyshev filter. cheb2ap -- Return (z,p,k) for type II Chebyshev filter. - cmplx_sort -- Sort roots based on magnitude. ellipap -- Return (z,p,k) for analog prototype of elliptic filter. lp2bp -- Transform a lowpass filter prototype to a bandpass filter. lp2bp_zpk -- Transform a lowpass filter prototype to a bandpass filter. diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index 620ae3f5ac45..ef1d50fdb74b 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -31,7 +31,7 @@ 'convolve', 'convolve2d', 'fftconvolve', 'oaconvolve', 'order_filter', 'medfilt', 'medfilt2d', 'wiener', 'lfilter', 'lfiltic', 'sosfilt', 'deconvolve', 'hilbert', 'hilbert2', - 'cmplx_sort', 'unique_roots', 'invres', 'invresz', 'residue', + 'unique_roots', 'invres', 'invresz', 'residue', 'residuez', 'resample', 'resample_poly', 'detrend', 'lfilter_zi', 'sosfilt_zi', 'sosfiltfilt', 'choose_conv_method', 'filtfilt', 'decimate', 'vectorstrength'] @@ -2452,18 +2452,6 @@ def hilbert2(x, N=None): return x -_msg_cplx_sort="""cmplx_sort was deprecated in SciPy 1.12 and will be removed -in SciPy 1.15. The exact equivalent for a numpy array argument is ->>> def cmplx_sort(p): -... idx = np.argsort(abs(p)) -... return np.take(p, idx, 0), idx -""" - -def cmplx_sort(p): - warnings.warn(_msg_cplx_sort, DeprecationWarning, stacklevel=2) - return _cmplx_sort(p) - - def _cmplx_sort(p): """Sort roots based on magnitude. diff --git a/scipy/signal/signaltools.py b/scipy/signal/signaltools.py index 310c81c93cd7..85d426f5fb26 100644 --- a/scipy/signal/signaltools.py +++ b/scipy/signal/signaltools.py @@ -9,7 +9,7 @@ 'convolve', 'convolve2d', 'fftconvolve', 'oaconvolve', 'order_filter', 'medfilt', 'medfilt2d', 'wiener', 'lfilter', 'lfiltic', 'sosfilt', 'deconvolve', 'hilbert', 'hilbert2', - 'cmplx_sort', 'unique_roots', 'invres', 'invresz', 'residue', + 'unique_roots', 'invres', 'invresz', 'residue', 'residuez', 'resample', 'resample_poly', 'detrend', 'lfilter_zi', 'sosfilt_zi', 'sosfiltfilt', 'choose_conv_method', 'filtfilt', 'decimate', 'vectorstrength', From 1f286a6c758d55e13aeeb36fe6f1f3c9ef8a5f5e Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Mon, 3 Jun 2024 15:10:28 +0530 Subject: [PATCH 339/500] DEV: Use const wherever possible --- scipy/interpolate/_bspl.pyx | 10 +++++----- scipy/interpolate/_ppoly.pyx | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scipy/interpolate/_bspl.pyx b/scipy/interpolate/_bspl.pyx index 04f2ca88fbd3..c1de323ee847 100644 --- a/scipy/interpolate/_bspl.pyx +++ b/scipy/interpolate/_bspl.pyx @@ -264,7 +264,7 @@ def insert(double xval, if (interval + 1 <= 2*k) and (interval + 1 >= t.shape[0] - 2*k): # in case of a periodic spline (iopt.ne.0) there must be # either at least k interior knots t(j) satisfying t(k+1) Date: Mon, 3 Jun 2024 13:20:32 +0100 Subject: [PATCH 340/500] DEP: special.factorial: raise error for non-integer scalars and `exact=True` (#20869) Co-authored-by: Lucas Colley --- scipy/special/_basic.py | 4 ++-- scipy/special/tests/test_basic.py | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/scipy/special/_basic.py b/scipy/special/_basic.py index ca54215b533f..c041a1084ff0 100644 --- a/scipy/special/_basic.py +++ b/scipy/special/_basic.py @@ -3020,8 +3020,8 @@ def factorial(n, exact=False): return math.factorial(n) elif exact: msg = ("Non-integer values of `n` together with `exact=True` are " - "deprecated. Either ensure integer `n` or use `exact=False`.") - warnings.warn(msg, DeprecationWarning, stacklevel=2) + "not supported. Either ensure integer `n` or use `exact=False`.") + raise ValueError(msg) return _factorialx_approx_core(n, k=1) # arrays & array-likes diff --git a/scipy/special/tests/test_basic.py b/scipy/special/tests/test_basic.py index e33c44528353..179d9842d976 100644 --- a/scipy/special/tests/test_basic.py +++ b/scipy/special/tests/test_basic.py @@ -2202,10 +2202,9 @@ def test_factorial_float_reference(self): def _check(n, expected): assert_allclose(special.factorial(n), expected) assert_allclose(special.factorial([n])[0], expected) - # using floats with exact=True is deprecated for scalars... - with pytest.deprecated_call(match="Non-integer values.*"): + # using floats with `exact=True` raises an error for scalars and arrays + with pytest.raises(ValueError, match="Non-integer values.*"): assert_allclose(special.factorial(n, exact=True), expected) - # ... and already an error for arrays with pytest.raises(ValueError, match="factorial with `exact=Tr.*"): special.factorial([n], exact=True) @@ -2270,15 +2269,14 @@ def assert_really_equal(x, y): def test_factorial_scalar_corner_cases(self, n, exact): if (n is None or n is np.nan or np.issubdtype(type(n), np.integer) or np.issubdtype(type(n), np.floating)): - # no error if (np.issubdtype(type(n), np.floating) and exact and n is not np.nan): - with pytest.deprecated_call(match="Non-integer values.*"): - result = special.factorial(n, exact=exact) + with pytest.raises(ValueError, match="Non-integer values.*"): + special.factorial(n, exact=exact) else: result = special.factorial(n, exact=exact) - exp = np.nan if n is np.nan or n is None else special.factorial(n) - assert_equal(result, exp) + exp = np.nan if n is np.nan or n is None else special.factorial(n) + assert_equal(result, exp) else: with pytest.raises(ValueError, match="Unsupported datatype*"): special.factorial(n, exact=exact) From 656f4846be6c8d1e3dcd462bec079ded7e6d6b09 Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Mon, 3 Jun 2024 19:16:39 +0530 Subject: [PATCH 341/500] DEV: Use const wherever possible in scipy.sparse.csgraph --- scipy/sparse/csgraph/_flow.pyx | 24 ++++++------ scipy/sparse/csgraph/_matching.pyx | 8 ++-- scipy/sparse/csgraph/_min_spanning_tree.pyx | 21 +++++------ scipy/sparse/csgraph/_shortest_path.pyx | 42 ++++++++++----------- 4 files changed, 47 insertions(+), 48 deletions(-) diff --git a/scipy/sparse/csgraph/_flow.pyx b/scipy/sparse/csgraph/_flow.pyx index 45656eaed3a8..c2ca11719e55 100644 --- a/scipy/sparse/csgraph/_flow.pyx +++ b/scipy/sparse/csgraph/_flow.pyx @@ -377,13 +377,13 @@ def _make_tails(a): cdef ITYPE_t[:] _edmonds_karp( - ITYPE_t[:] edge_ptr, - ITYPE_t[:] tails, - ITYPE_t[:] heads, - ITYPE_t[:] capacities, - ITYPE_t[:] rev_edge_ptr, - ITYPE_t source, - ITYPE_t sink) noexcept: + const ITYPE_t[:] edge_ptr, + const ITYPE_t[:] tails, + const ITYPE_t[:] heads, + const ITYPE_t[:] capacities, + const ITYPE_t[:] rev_edge_ptr, + const ITYPE_t source, + const ITYPE_t sink) noexcept: """Solves the maximum flow problem using the Edmonds--Karp algorithm. This assumes that for every edge in the graph, the edge in the opposite @@ -619,12 +619,12 @@ cdef bint _augment_paths( progress[current] += 1 cdef ITYPE_t[:] _dinic( - ITYPE_t[:] edge_ptr, - ITYPE_t[:] heads, + const ITYPE_t[:] edge_ptr, + const ITYPE_t[:] heads, ITYPE_t[:] capacities, - ITYPE_t[:] rev_edge_ptr, - ITYPE_t source, - ITYPE_t sink) noexcept: + const ITYPE_t[:] rev_edge_ptr, + const ITYPE_t source, + const ITYPE_t sink) noexcept: """Solves the maximum flow problem using the Dinic's algorithm. This assumes that for every edge in the graph, the edge in the opposite diff --git a/scipy/sparse/csgraph/_matching.pyx b/scipy/sparse/csgraph/_matching.pyx index e1145fde1115..758d00dbfa74 100644 --- a/scipy/sparse/csgraph/_matching.pyx +++ b/scipy/sparse/csgraph/_matching.pyx @@ -147,8 +147,8 @@ def maximum_bipartite_matching(graph, perm_type='row'): @cython.boundscheck(False) @cython.wraparound(False) -cdef tuple _hopcroft_karp(ITYPE_t[:] indices, ITYPE_t[:] indptr, - ITYPE_t i, ITYPE_t j): +cdef tuple _hopcroft_karp(const ITYPE_t[:] indices, const ITYPE_t[:] indptr, + const ITYPE_t i, const ITYPE_t j): cdef ITYPE_t INF = np.iinfo(ITYPE).max # x will end up containing the matchings of rows to columns, while # y will contain the matchings of columns to rows. @@ -528,8 +528,8 @@ ctypedef np.uint8_t BTYPE_t cdef ITYPE_t[:] _lapjvsp(ITYPE_t[:] first, ITYPE_t[:] kk, DTYPE_t[:] cc, - ITYPE_t nr, - ITYPE_t nc) noexcept: + const ITYPE_t nr, + const ITYPE_t nc) noexcept: """Solves the minimum weight bipartite matching problem using LAPJVsp. The implementation at hand is a straightforward port of the original Pascal diff --git a/scipy/sparse/csgraph/_min_spanning_tree.pyx b/scipy/sparse/csgraph/_min_spanning_tree.pyx index 872983e0faa7..41b536b19ecc 100644 --- a/scipy/sparse/csgraph/_min_spanning_tree.pyx +++ b/scipy/sparse/csgraph/_min_spanning_tree.pyx @@ -93,7 +93,7 @@ def minimum_spanning_tree(csgraph, overwrite=False): [0, 0, 0, 0]]) """ global NULL_IDX - + is_pydata_sparse = is_pydata_spmatrix(csgraph) if is_pydata_sparse: pydata_sparse_cls = csgraph.__class__ @@ -135,9 +135,9 @@ def minimum_spanning_tree(csgraph, overwrite=False): @cython.boundscheck(False) @cython.wraparound(False) cdef void _min_spanning_tree(DTYPE_t[::1] data, - ITYPE_t[::1] col_indices, - ITYPE_t[::1] indptr, - ITYPE_t[::1] i_sort, + const ITYPE_t[::1] col_indices, + const ITYPE_t[::1] indptr, + const ITYPE_t[::1] i_sort, ITYPE_t[::1] row_indices, ITYPE_t[::1] predecessors, ITYPE_t[::1] rank) noexcept nogil: @@ -147,13 +147,13 @@ cdef void _min_spanning_tree(DTYPE_t[::1] data, cdef unsigned int i, j, V1, V2, R1, R2, n_edges_in_mst, n_verts, n_data n_verts = predecessors.shape[0] n_data = i_sort.shape[0] - + # Arrange `row_indices` to contain the row index of each value in `data`. # Note that the array `col_indices` already contains the column index. for i in range(n_verts): for j in range(indptr[i], indptr[i + 1]): row_indices[j] = i - + # step through the edges from smallest to largest. # V1 and V2 are connected vertices. n_edges_in_mst = 0 @@ -176,13 +176,13 @@ cdef void _min_spanning_tree(DTYPE_t[::1] data, predecessors[V1] = R1 while predecessors[V2] != R2: predecessors[V2] = R2 - + # if the subtrees are different, then we connect them and keep the # edge. Otherwise, we remove the edge: it duplicates one already # in the spanning tree. if R1 != R2: n_edges_in_mst += 1 - + # Use approximate (because of path-compression) rank to try # to keep balanced trees. if rank[R1] > rank[R2]: @@ -194,12 +194,11 @@ cdef void _min_spanning_tree(DTYPE_t[::1] data, rank[R1] += 1 else: data[j] = 0 - + i += 1 - + # We may have stopped early if we found a full-sized MST so zero out the rest while i < n_data: j = i_sort[i] data[j] = 0 i += 1 - diff --git a/scipy/sparse/csgraph/_shortest_path.pyx b/scipy/sparse/csgraph/_shortest_path.pyx index 953bb50aab8f..0ae423ae135d 100644 --- a/scipy/sparse/csgraph/_shortest_path.pyx +++ b/scipy/sparse/csgraph/_shortest_path.pyx @@ -76,14 +76,14 @@ def shortest_path(csgraph, method='auto', 'BF' -- Bellman-Ford algorithm. This algorithm can be used when weights are negative. If a negative cycle is encountered, an error will be raised. - Computational cost is approximately ``O[N(N^2 k)]``, where - ``k`` is the average number of connected edges per node. + Computational cost is approximately ``O[N(N^2 k)]``, where + ``k`` is the average number of connected edges per node. The input csgraph will be converted to a csr representation. 'J' -- Johnson's algorithm. - Like the Bellman-Ford algorithm, Johnson's algorithm is - designed for use when the weights are negative. It combines - the Bellman-Ford algorithm with Dijkstra's algorithm for + Like the Bellman-Ford algorithm, Johnson's algorithm is + designed for use when the weights are negative. It combines + the Bellman-Ford algorithm with Dijkstra's algorithm for faster computation. directed : bool, optional @@ -886,13 +886,13 @@ cdef int _dijkstra_undirected( @cython.boundscheck(False) cdef int _dijkstra_undirected_multi( - int[:] source_indices, - double[:] csr_weights, - int[:] csr_indices, - int[:] csr_indptr, - double[:] csrT_weights, - int[:] csrT_indices, - int[:] csrT_indptr, + const int[:] source_indices, + const double[:] csr_weights, + const int[:] csr_indices, + const int[:] csr_indptr, + const double[:] csrT_weights, + const int[:] csrT_indices, + const int[:] csrT_indptr, double[:] dist_matrix, int[:] pred, int[:] sources, @@ -1348,9 +1348,9 @@ def johnson(csgraph, directed=True, indices=None, cdef void _johnson_add_weights( double[:] csr_weights, - int[:] csr_indices, - int[:] csr_indptr, - double[:] dist_array) noexcept: + const int[:] csr_indices, + const int[:] csr_indptr, + const double[:] dist_array) noexcept: # let w(u, v) = w(u, v) + h(u) - h(v) cdef unsigned int j, k, N = dist_array.shape[0] @@ -1497,7 +1497,7 @@ cdef void add_sibling(FibonacciNode* node, FibonacciNode* new_sibling) noexcept: # Assumptions: - node is a valid pointer # - new_sibling is a valid pointer # - new_sibling is not the child or sibling of another node - + # Insert new_sibling between node and node.right_sibling if node.right_sibling: node.right_sibling.left_sibling = new_sibling @@ -1549,7 +1549,7 @@ cdef void insert_node(FibonacciHeap* heap, # - node is not the child or sibling of another node if heap.min_node: if node.val < heap.min_node.val: - # Replace heap.min_node with node, which is always + # Replace heap.min_node with node, which is always # at the leftmost end of the roots' linked-list. node.left_sibling = NULL node.right_sibling = heap.min_node @@ -1574,7 +1574,7 @@ cdef void decrease_val(FibonacciHeap* heap, remove(node) insert_node(heap, node) elif heap.min_node.val > node.val: - # Replace heap.min_node with node, which is always + # Replace heap.min_node with node, which is always # at the leftmost end of the roots' linked-list. remove(node) node.right_sibling = heap.min_node @@ -1628,7 +1628,7 @@ cdef FibonacciNode* remove_min(FibonacciHeap* heap) noexcept: temp = heap.min_node.right_sibling remove(heap.min_node) heap.min_node = temp - + if temp == NULL: # There is a unique root in the tree, hence a unique node # which is the minimum that we return here. @@ -1644,7 +1644,7 @@ cdef FibonacciNode* remove_min(FibonacciHeap* heap) noexcept: temp_right = temp.right_sibling link(heap, temp) temp = temp_right - + # move heap.min_node to the leftmost end of the linked-list of roots temp = leftmost_sibling(heap.min_node) if heap.min_node != temp: @@ -2024,7 +2024,7 @@ cdef void _yen( if ( total_distance != INFINITY and _yen_is_path_in_candidates(candidate_predecessors, - shortest_paths_predecessors[k-1], + shortest_paths_predecessors[k-1], predecessor_matrix[0], spur_node, sink) == 0 ): From 85865e80706a246a4823230b04521aac9af38ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= <8431159+mtsokol@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:48:05 +0200 Subject: [PATCH 342/500] Skip csgraph non-zero fill-value tests for sparse < 0.15.4 (#20871) --- scipy/sparse/csgraph/tests/test_pydata_sparse.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scipy/sparse/csgraph/tests/test_pydata_sparse.py b/scipy/sparse/csgraph/tests/test_pydata_sparse.py index 3d54e83095ed..025aeb67c1f7 100644 --- a/scipy/sparse/csgraph/tests/test_pydata_sparse.py +++ b/scipy/sparse/csgraph/tests/test_pydata_sparse.py @@ -3,6 +3,7 @@ import numpy as np import scipy.sparse as sp import scipy.sparse.csgraph as spgraph +from scipy._lib import _pep440 from numpy.testing import assert_equal @@ -22,6 +23,15 @@ 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( + _pep440.parse(sparse.__version__) < _pep440.Version(min_ver), + reason=f"sparse version >= {min_ver} required" + ) + + @pytest.fixture(params=sparse_params) def sparse_cls(request): return getattr(sparse, request.param) @@ -149,6 +159,7 @@ def test_min_weight_full_bipartite_matching(graphs): assert_equal(actual, desired) +@check_sparse_version("0.15.4") @pytest.mark.parametrize( "func", [ From 1d683b7d940042cab07cf47c9319307aa0e53a22 Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Mon, 3 Jun 2024 20:25:58 +0530 Subject: [PATCH 343/500] DEV: Use const wherever possible in spatial --- scipy/spatial/_hausdorff.pyx | 2 +- scipy/spatial/_voronoi.pyx | 2 +- scipy/spatial/transform/_rotation.pyx | 76 +++++++++++++-------------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/scipy/spatial/_hausdorff.pyx b/scipy/spatial/_hausdorff.pyx index 01cfce9df3f3..315fec38380b 100644 --- a/scipy/spatial/_hausdorff.pyx +++ b/scipy/spatial/_hausdorff.pyx @@ -22,7 +22,7 @@ np.import_array() __all__ = ['directed_hausdorff'] @cython.boundscheck(False) -def directed_hausdorff(double[:,::1] ar1, double[:,::1] ar2, seed=0): +def directed_hausdorff(const double[:,::1] ar1, const double[:,::1] ar2, seed=0): cdef double cmax, cmin, d = 0 cdef Py_ssize_t N1 = ar1.shape[0] diff --git a/scipy/spatial/_voronoi.pyx b/scipy/spatial/_voronoi.pyx index 662068d138f0..70ce781106fa 100644 --- a/scipy/spatial/_voronoi.pyx +++ b/scipy/spatial/_voronoi.pyx @@ -20,7 +20,7 @@ __all__ = ['sort_vertices_of_regions'] @cython.boundscheck(False) -def sort_vertices_of_regions(int[:,::1] simplices, list regions): +def sort_vertices_of_regions(const int[:,::1] simplices, list regions): cdef np.npy_intp n, k, s, i, max_len cdef np.npy_intp num_regions = len(regions) cdef np.npy_intp current_simplex, current_vertex diff --git a/scipy/spatial/transform/_rotation.pyx b/scipy/spatial/transform/_rotation.pyx index 2a9bfc89fd8e..52db31c24da2 100644 --- a/scipy/spatial/transform/_rotation.pyx +++ b/scipy/spatial/transform/_rotation.pyx @@ -63,7 +63,7 @@ cdef inline double _normalize4(double[:] elems) noexcept nogil: @cython.boundscheck(False) @cython.wraparound(False) -cdef inline int _argmax4(double[:] a) noexcept nogil: +cdef inline int _argmax4(const double[:] a) noexcept nogil: cdef int imax = 0 cdef double vmax = a[0] @@ -86,7 +86,7 @@ cdef inline const double[:] _elementary_basis_vector(uchar axis) noexcept: if axis == b'x': return _ex elif axis == b'y': return _ey elif axis == b'z': return _ez - + @cython.boundscheck(False) @cython.wraparound(False) cdef inline int _elementary_basis_index(uchar axis) noexcept: @@ -118,9 +118,9 @@ cdef inline void _quat_canonical(double[:, :] q) noexcept: @cython.boundscheck(False) @cython.wraparound(False) cdef inline void _get_angles( - double[:] angles, bint extrinsic, bint symmetric, bint sign, + double[:] angles, bint extrinsic, bint symmetric, bint sign, double lamb, double a, double b, double c, double d): - + # intrinsic/extrinsic conversion helpers cdef int angle_first, angle_third if extrinsic: @@ -149,18 +149,18 @@ cdef inline void _get_angles( # compute first and third angles, according to case half_sum = atan2(b, a) half_diff = atan2(d, c) - + if case == 0: # no singularities angles[angle_first] = half_sum - half_diff angles[angle_third] = half_sum + half_diff - + else: # any degenerate case angles[2] = 0 if case == 1: angles[0] = 2 * half_sum else: angles[0] = 2 * half_diff * (-1 if extrinsic else 1) - + # for Tait-Bryan/asymmetric sequences if not symmetric: angles[angle_third] *= sign @@ -328,12 +328,12 @@ cdef double[:, :] _compute_euler_from_quat( # 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 + # Adapt the algorithm for our case by reversing both axis sequence and # angles for intrinsic rotations when needed - + if not extrinsic: seq = seq[::-1] - + cdef int i = _elementary_basis_index(seq[0]) cdef int j = _elementary_basis_index(seq[1]) cdef int k = _elementary_basis_index(seq[2]) @@ -341,9 +341,9 @@ cdef double[:, :] _compute_euler_from_quat( cdef bint symmetric = i == k if symmetric: k = 3 - i - j # get third axis - + # Step 0 - # Check if permutation is even (+1) or odd (-1) + # Check if permutation is even (+1) or odd (-1) cdef int sign = (i - j) * (j - k) * (k - i) // 2 cdef Py_ssize_t num_rotations = quat.shape[0] @@ -355,7 +355,7 @@ cdef double[:, :] _compute_euler_from_quat( for ind in range(num_rotations): # Step 1 - # Permutate quaternion elements + # Permutate quaternion elements if symmetric: a = quat[ind, 3] b = quat[ind, i] @@ -1392,9 +1392,9 @@ cdef class Rotation: (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 + 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) @@ -1406,13 +1406,13 @@ cdef class Rotation: Parameters ---------- axes : array_like, shape (3,) or ([1 or 2 or 3], 3) - Axis of rotation, if one dimensional. If two dimensional, describes the + Axis of rotation, if one dimensional. If two 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 : string - If it is equal to 'e' or 'extrinsic', the sequence will be - extrinsic. If it is equal to 'i' or 'intrinsic', sequence + 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 (N,) or (N, [1 or 2 or 3]) Euler angles specified in radians (`degrees` is False) or degrees @@ -1430,11 +1430,11 @@ cdef class Rotation: - array_like with shape (W,) where `W` is the number of rows of `axes`, which corresponds to a single rotation with `W` axes - array_like with shape (N, W) where each `angle[i]` - corresponds to a sequence of Davenport angles describing a + corresponds to a sequence of Davenport angles describing a single rotation degrees : bool, optional - If True, then the given angles are assumed to be in degrees. + If True, then the given angles are assumed to be in degrees. Default is False. Returns @@ -1520,10 +1520,10 @@ cdef class Rotation: norm = np.repeat(np.linalg.norm(axes, axis=1), 3) axes = axes / norm.reshape(num_axes, 3) - if (num_axes > 1 and abs(np.dot(axes[0], axes[1])) >= 1e-7 or + if (num_axes > 1 and abs(np.dot(axes[0], axes[1])) >= 1e-7 or num_axes > 2 and abs(np.dot(axes[1], axes[2])) >= 1e-7): raise ValueError("Consecutive axes must be orthogonal.") - + angles, is_single = _format_angles(angles, degrees, num_axes) q = Rotation.identity(len(angles)) @@ -1665,7 +1665,7 @@ cdef class Rotation: 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. + rotation. Parameters ---------- @@ -1937,7 +1937,7 @@ cdef class Rotation: @cython.embedsignature(True) def _compute_euler(self, seq, degrees, algorithm): # Prepare axis sequence to call Euler angles conversion algorithm. - + if len(seq) != 3: raise ValueError("Expected 3 axes, got {}.".format(seq)) @@ -1953,7 +1953,7 @@ cdef class Rotation: "got {}".format(seq)) seq = seq.lower() - + if algorithm == 'from_matrix': matrix = self.as_matrix() if matrix.ndim == 2: @@ -1969,7 +1969,7 @@ cdef class Rotation: else: # algorithm can only be 'from_quat' or 'from_matrix' assert False - + if degrees: angles = np.rad2deg(angles) @@ -2037,7 +2037,7 @@ cdef class Rotation: 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 + 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 @@ -2075,9 +2075,9 @@ cdef class Rotation: 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. + .. [2] Bernardes E, Viollet S (2022) Quaternion to Euler angles + conversion: A direct, general and computationally efficient + method. PLoS ONE 17(11): e0276302. https://doi.org/10.1371/journal.pone.0276302 .. [3] https://en.wikipedia.org/wiki/Gimbal_lock#In_applied_mathematics @@ -2125,9 +2125,9 @@ cdef class Rotation: 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 + 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) @@ -2150,13 +2150,13 @@ cdef class Rotation: Parameters ---------- axes : array_like, shape (3,) or ([1 or 2 or 3], 3) - Axis of rotation, if one dimensional. If two dimensional, describes the + Axis of rotation, if one dimensional. If two 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 : string - If it belongs to the set {'e', 'extrinsic'}, the sequence will be - extrinsic. If if belongs to the set {'i', 'intrinsic'}, sequence + If it belongs to the set {'e', 'extrinsic'}, the sequence will be + extrinsic. If if belongs to the set {'i', 'intrinsic'}, sequence will be treated as intrinsic. degrees : boolean, optional Returned angles are in degrees if this flag is True, else they are @@ -2170,7 +2170,7 @@ cdef class Rotation: - 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, + - 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. From e262f3c8e7347d10625424a1d4dce088f192ad7b Mon Sep 17 00:00:00 2001 From: Irwin Zaid Date: Mon, 3 Jun 2024 17:34:20 +0200 Subject: [PATCH 344/500] MAINT: special: Add kokkos `mdspan` (#20862) * replaced mdspan implementation with kokkos version * Put mdspan in third_party subdirectory * Added license to LICENSES_bundled.txt --------- Co-authored-by: izaid --- LICENSES_bundled.txt | 5 + scipy/special/special/mdspan.h | 311 - scipy/special/special/sph_harm.h | 2 +- .../special/third_party/kokkos/mdspan.hpp | 5674 +++++++++++++++++ scipy/special/ufunc.h | 2 +- 5 files changed, 5681 insertions(+), 313 deletions(-) delete mode 100644 scipy/special/special/mdspan.h create mode 100644 scipy/special/special/third_party/kokkos/mdspan.hpp diff --git a/LICENSES_bundled.txt b/LICENSES_bundled.txt index 4828b64e30aa..e12ec9c16e4b 100644 --- a/LICENSES_bundled.txt +++ b/LICENSES_bundled.txt @@ -286,3 +286,8 @@ Name: Tempita Files: scipy/_build_utils/tempita/* License: MIT For details, see scipy/_build_utils/tempita/LICENCE.txt + +Name: mdspan +Files: scipy/special/special/third_party/kokkos/mdspan.hpp +License: Apache License v2.0 with LLVM Exceptions + For details, see scipy/special/special/third_party/kokkos/mdspan.hpp \ No newline at end of file diff --git a/scipy/special/special/mdspan.h b/scipy/special/special/mdspan.h deleted file mode 100644 index 4dce5c3805b1..000000000000 --- a/scipy/special/special/mdspan.h +++ /dev/null @@ -1,311 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace std { - -template -class extents; - -namespace detail { - - template - struct fill_extents { - using type = typename fill_extents::type; - }; - - template - struct fill_extents { - using type = extents; - }; - - template - using fill_extents_t = typename fill_extents::type; - -} // namespace detail - -inline constexpr size_t dynamic_extent = numeric_limits::max(); - -template -class extents { - static_assert(((Extents == dynamic_extent) && ... && true), "extents must all be dynamic"); - - public: - using index_type = Index; - using size_type = make_unsigned_t; - using rank_type = size_t; - - private: - array m_dexts; - - public: - constexpr extents() = default; - - template - constexpr explicit extents(OtherIndex... exts) : m_dexts{exts...} {} - - template - constexpr extents(const array &dexts) noexcept : m_dexts(dexts) {} - - constexpr index_type extent(rank_type i) const noexcept { return m_dexts[i]; } - - static constexpr rank_type rank() noexcept { return sizeof...(Extents); } -}; - -template -using dextents = detail::fill_extents_t; - -struct full_extent_t { - explicit full_extent_t() = default; -}; - -inline constexpr full_extent_t full_extent; - -template -struct strided_slice { - using offset_type = Offset; - using extent_type = Extent; - using stride_type = Stride; - - strided_slice() = default; - - strided_slice(offset_type offset, extent_type extent, stride_type stride) - : offset(offset), extent(extent), stride(stride) {} - - offset_type offset; - extent_type extent; - stride_type stride; -}; - -namespace detail { - - template - Index submdspan_extent(Index ext, strided_slice slice) { - return (slice.extent - slice.offset) / slice.stride; - } - - template - Index submdspan_extent(Index ext, std::tuple slice) { - return std::get<1>(slice) - std::get<0>(slice); - } - - template - Index submdspan_extent(Index ext, full_extent_t slice) { - return ext; - } - - template - auto submdspan_extents(std::index_sequence, const extents exts, Slices... slices) { - return extents{submdspan_extent(exts.extent(I), slices)...}; - } - - template - auto submdspan_extents(const extents exts, Slices... slices) { - return submdspan_extents(std::index_sequence_for(), exts, slices...); - } - -} // namespace detail - -template -auto submdspan_extents(const extents &exts, Slices... slices) { - return detail::submdspan_extents(exts, slices...); -} - -template -auto submdspan_mapping(const Mapping &, Slices...); - -struct layout_left; - -struct layout_right; - -struct layout_stride { - template - class mapping { - public: - using extents_type = Extents; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using layout_type = layout_stride; - - private: - extents_type m_exts; - array m_strides; - - public: - constexpr mapping() = default; - - constexpr mapping(const Extents &exts, const array &strides) - : m_exts(exts), m_strides(strides) {} - - constexpr const extents_type &extents() const noexcept { return m_exts; } - - constexpr const array &strides() const noexcept { return m_strides; } - - constexpr index_type extent(rank_type i) const noexcept { return m_exts.extent(i); } - - constexpr index_type stride(rank_type i) const noexcept { return m_strides[i]; } - - template - constexpr index_type operator()(Args... args) const noexcept { - static_assert(sizeof...(Args) == extents_type::rank(), "index must have same rank as extents"); - - index_type indices[extents_type::rank()] = {args...}; - index_type res = 0; - for (rank_type i = 0; i < extents_type::rank(); ++i) { - res += indices[i] * m_strides[i]; - } - - return res; - } - }; -}; - -namespace detail { - - template - Index submdspan_stride(Index stride, strided_slice slice) { - return stride * slice.stride; - } - - template - Index submdspan_stride(Index stride, std::tuple slice) { - return stride; - } - - template - Index submdspan_stride(Index stride, full_extent_t slice) { - return stride; - } - - template - auto submdspan_strides(std::index_sequence, const array strides, Slices... slices) { - array res{submdspan_stride(strides[I], slices)...}; - return res; - } - - template - auto submdspan_strides(const array strides, Slices... slices) { - return submdspan_strides(std::index_sequence_for(), strides, slices...); - } - -} // namespace detail - -template -auto submdspan_strides(const array &strides, Slices... slices) { - return detail::submdspan_strides(strides, slices...); -} - -template -auto submdspan_mapping(const layout_stride::mapping &map, Slices... slices) { - return layout_stride::mapping(submdspan_extents(map.extents(), slices...), - submdspan_strides(map.strides(), slices...)); -} - -template -class default_accessor { - public: - using offset_policy = default_accessor; - using element_type = Element; - using reference = Element &; - using data_handle_type = Element *; - - constexpr reference access(data_handle_type p, size_t i) const noexcept { return p[i]; } - - constexpr data_handle_type offset(data_handle_type p, size_t i) const noexcept { return p + i; } -}; - -template > -class mdspan { - public: - using extents_type = Extents; - using layout_type = LayoutPolicy; - using accessor_type = AccessorPolicy; - using mapping_type = typename LayoutPolicy::template mapping; - using element_type = T; - using value_type = remove_cv_t; - using index_type = typename Extents::index_type; - using size_type = typename Extents::size_type; - using rank_type = typename Extents::rank_type; - using data_handle_type = typename AccessorPolicy::data_handle_type; - using reference = typename AccessorPolicy::reference; - - private: - data_handle_type m_ptr; - mapping_type m_map; - accessor_type m_acc; - - public: - constexpr mdspan() = default; - - constexpr mdspan(data_handle_type p, const mapping_type &m) : m_ptr(p), m_map(m) {} - - constexpr mdspan(data_handle_type p, const mapping_type &m, const accessor_type &a) - : m_ptr(p), m_map(m), m_acc(a) {} - - template - constexpr reference operator()(OtherIndices... indices) const { - return m_acc.access(m_ptr, m_map(static_cast(std::move(indices))...)); - } - - template - constexpr reference operator[](OtherIndex index) const { - return m_acc.access(m_ptr, m_map(static_cast(index))); - } - - constexpr const data_handle_type &data_handle() const noexcept { return m_ptr; } - - constexpr const mapping_type &mapping() const noexcept { return m_map; } - - constexpr const accessor_type &accessor() const noexcept { return m_acc; } - - constexpr index_type stride(rank_type r) const { return m_map.stride(r); } - - constexpr const extents_type &extents() const noexcept { return m_map.extents(); } - - constexpr index_type extent(rank_type r) const noexcept { return m_map.extent(r); } - - constexpr size_type size() const noexcept { - size_type res = 1; - for (rank_type i = 0; i < extents_type::rank(); ++i) { - res *= m_map.extent(i); - } - - return res; - } -}; - -namespace detail { - - template - auto submdspan_offset(strided_slice slice) { - return slice.offset; - } - - template - auto submdspan_offset(std::tuple slice) { - return std::get<0>(slice); - } - - inline auto submdspan_offset(full_extent_t slice) { return 0; } - -} // namespace detail - -template -auto submdspan(const mdspan &src, SliceArgs... args) { - static_assert(Extents::rank() == sizeof...(SliceArgs), "number of slices must equal extents rank"); - - using submdspan_type = mdspan; - - auto src_map = src.mapping(); - auto src_acc = src.accessor(); - return submdspan_type(src_acc.offset(src.data_handle(), src_map(detail::submdspan_offset(args)...)), - submdspan_mapping(src.mapping(), args...), src_acc); -} - -} // namespace std diff --git a/scipy/special/special/sph_harm.h b/scipy/special/special/sph_harm.h index 4fc4ac2f10d4..5c1bdd790850 100644 --- a/scipy/special/special/sph_harm.h +++ b/scipy/special/special/sph_harm.h @@ -2,8 +2,8 @@ #include "error.h" #include "legendre.h" -#include "mdspan.h" #include "specfun.h" +#include "third_party/kokkos/mdspan.hpp" #include "cephes/poch.h" diff --git a/scipy/special/special/third_party/kokkos/mdspan.hpp b/scipy/special/special/third_party/kokkos/mdspan.hpp new file mode 100644 index 000000000000..ecfa332cf010 --- /dev/null +++ b/scipy/special/special/third_party/kokkos/mdspan.hpp @@ -0,0 +1,5674 @@ +#ifndef _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ +#define _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ + +//BEGIN_FILE_INCLUDE: mdspan/include/mdspan/mdspan.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +#ifndef MDSPAN_HPP_ +#define MDSPAN_HPP_ + +#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE + #define MDSPAN_IMPL_STANDARD_NAMESPACE std +#endif + +#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE + #define MDSPAN_IMPL_PROPOSED_NAMESPACE experimental +#endif + +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/default_accessor.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/macros.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/config.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +#ifndef __has_include +# define __has_include(x) 0 +#endif + +#if __has_include() +# include +#else +# include +# include +#endif + +#ifdef _MSVC_LANG +#define _MDSPAN_CPLUSPLUS _MSVC_LANG +#else +#define _MDSPAN_CPLUSPLUS __cplusplus +#endif + +#define MDSPAN_CXX_STD_14 201402L +#define MDSPAN_CXX_STD_17 201703L +#define MDSPAN_CXX_STD_20 202002L +// Note GCC has not updated this in version 13 +#ifdef __clang__ +#define MDSPAN_CXX_STD_23 202302L +#else +#define MDSPAN_CXX_STD_23 202100L +#endif + +#define MDSPAN_HAS_CXX_14 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_14) +#define MDSPAN_HAS_CXX_17 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_17) +#define MDSPAN_HAS_CXX_20 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_20) +#define MDSPAN_HAS_CXX_23 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_23) + +static_assert(_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_14, "mdspan requires C++14 or later."); + +#ifndef _MDSPAN_COMPILER_CLANG +# if defined(__clang__) +# define _MDSPAN_COMPILER_CLANG __clang__ +# endif +#endif + +#if !defined(_MDSPAN_COMPILER_MSVC) && !defined(_MDSPAN_COMPILER_MSVC_CLANG) +# if defined(_MSC_VER) +# if !defined(_MDSPAN_COMPILER_CLANG) +# define _MDSPAN_COMPILER_MSVC _MSC_VER +# else +# define _MDSPAN_COMPILER_MSVC_CLANG _MSC_VER +# endif +# endif +#endif + +#ifndef _MDSPAN_COMPILER_INTEL +# ifdef __INTEL_COMPILER +# define _MDSPAN_COMPILER_INTEL __INTEL_COMPILER +# endif +#endif + +#ifndef _MDSPAN_COMPILER_APPLECLANG +# ifdef __apple_build_version__ +# define _MDSPAN_COMPILER_APPLECLANG __apple_build_version__ +# endif +#endif + +#ifndef _MDSPAN_HAS_CUDA +# if defined(__CUDACC__) +# define _MDSPAN_HAS_CUDA __CUDACC__ +# endif +#endif + +#ifndef _MDSPAN_HAS_HIP +# if defined(__HIPCC__) +# define _MDSPAN_HAS_HIP __HIPCC__ +# endif +#endif + +#ifndef _MDSPAN_HAS_SYCL +# if defined(SYCL_LANGUAGE_VERSION) +# define _MDSPAN_HAS_SYCL SYCL_LANGUAGE_VERSION +# endif +#endif + +#ifndef __has_cpp_attribute +# define __has_cpp_attribute(x) 0 +#endif + +#ifndef _MDSPAN_PRESERVE_STANDARD_LAYOUT +// Preserve standard layout by default, but we're not removing the old version +// that turns this off until we're sure this doesn't have an unreasonable cost +// to the compiler or optimizer. +# define _MDSPAN_PRESERVE_STANDARD_LAYOUT 1 +#endif + +#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) +# if ((__has_cpp_attribute(no_unique_address) >= 201803L) && \ + (!defined(__NVCC__) || MDSPAN_HAS_CXX_20) && \ + (!defined(_MDSPAN_COMPILER_MSVC) || MDSPAN_HAS_CXX_20)) +# define _MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1 +# define _MDSPAN_NO_UNIQUE_ADDRESS [[no_unique_address]] +# else +# define _MDSPAN_NO_UNIQUE_ADDRESS +# endif +#endif + +// NVCC older than 11.6 chokes on the no-unique-address-emulation +// so just pretend to use it (to avoid the full blown EBO workaround +// which NVCC also doesn't like ...), and leave the macro empty +#ifndef _MDSPAN_NO_UNIQUE_ADDRESS +# if defined(__NVCC__) +# define _MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1 +# define _MDSPAN_USE_FAKE_ATTRIBUTE_NO_UNIQUE_ADDRESS +# endif +# define _MDSPAN_NO_UNIQUE_ADDRESS +#endif + +// AMDs HIP compiler seems to have issues with concepts +// it pretends concepts exist, but doesn't ship +#ifndef __HIPCC__ +#ifndef _MDSPAN_USE_CONCEPTS +# if defined(__cpp_concepts) && __cpp_concepts >= 201507L +# define _MDSPAN_USE_CONCEPTS 1 +# endif +#endif +#endif + +#ifndef _MDSPAN_USE_FOLD_EXPRESSIONS +# if (defined(__cpp_fold_expressions) && __cpp_fold_expressions >= 201603L) \ + || (!defined(__cpp_fold_expressions) && MDSPAN_HAS_CXX_17) +# define _MDSPAN_USE_FOLD_EXPRESSIONS 1 +# endif +#endif + +#ifndef _MDSPAN_USE_INLINE_VARIABLES +# if defined(__cpp_inline_variables) && __cpp_inline_variables >= 201606L \ + || (!defined(__cpp_inline_variables) && MDSPAN_HAS_CXX_17) +# define _MDSPAN_USE_INLINE_VARIABLES 1 +# endif +#endif + +#ifndef _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS +# if (!(defined(__cpp_lib_type_trait_variable_templates) && __cpp_lib_type_trait_variable_templates >= 201510L) \ + || !MDSPAN_HAS_CXX_17) +# if !(defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_17) +# define _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS 1 +# endif +# endif +#endif + +#ifndef _MDSPAN_USE_VARIABLE_TEMPLATES +# if (defined(__cpp_variable_templates) && __cpp_variable_templates >= 201304 && MDSPAN_HAS_CXX_17) \ + || (!defined(__cpp_variable_templates) && MDSPAN_HAS_CXX_17) +# define _MDSPAN_USE_VARIABLE_TEMPLATES 1 +# endif +#endif // _MDSPAN_USE_VARIABLE_TEMPLATES + +#ifndef _MDSPAN_USE_CONSTEXPR_14 +# if (defined(__cpp_constexpr) && __cpp_constexpr >= 201304) \ + || (!defined(__cpp_constexpr) && MDSPAN_HAS_CXX_14) \ + && (!(defined(__INTEL_COMPILER) && __INTEL_COMPILER <= 1700)) +# define _MDSPAN_USE_CONSTEXPR_14 1 +# endif +#endif + +#ifndef _MDSPAN_USE_INTEGER_SEQUENCE +# if defined(_MDSPAN_COMPILER_MSVC) +# if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304) +# define _MDSPAN_USE_INTEGER_SEQUENCE 1 +# endif +# endif +#endif +#ifndef _MDSPAN_USE_INTEGER_SEQUENCE +# if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304) \ + || (!defined(__cpp_lib_integer_sequence) && MDSPAN_HAS_CXX_14) \ + /* as far as I can tell, libc++ seems to think this is a C++11 feature... */ \ + || (defined(__GLIBCXX__) && __GLIBCXX__ > 20150422 && __GNUC__ < 5 && !defined(__INTEL_CXX11_MODE__)) + // several compilers lie about integer_sequence working properly unless the C++14 standard is used +# define _MDSPAN_USE_INTEGER_SEQUENCE 1 +# elif defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14 + // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14 making + // integer_sequence work +# define _MDSPAN_USE_INTEGER_SEQUENCE 1 +# endif +#endif + +#ifndef _MDSPAN_USE_RETURN_TYPE_DEDUCTION +# if (defined(__cpp_return_type_deduction) && __cpp_return_type_deduction >= 201304) \ + || (!defined(__cpp_return_type_deduction) && MDSPAN_HAS_CXX_14) +# define _MDSPAN_USE_RETURN_TYPE_DEDUCTION 1 +# endif +#endif + +#ifndef _MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION +# if (!defined(__NVCC__) || (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10 >= 1170)) && \ + ((defined(__cpp_deduction_guides) && __cpp_deduction_guides >= 201703) || \ + (!defined(__cpp_deduction_guides) && MDSPAN_HAS_CXX_17)) +# define _MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION 1 +# endif +#endif + +#ifndef _MDSPAN_USE_STANDARD_TRAIT_ALIASES +# if (defined(__cpp_lib_transformation_trait_aliases) && __cpp_lib_transformation_trait_aliases >= 201304) \ + || (!defined(__cpp_lib_transformation_trait_aliases) && MDSPAN_HAS_CXX_14) +# define _MDSPAN_USE_STANDARD_TRAIT_ALIASES 1 +# elif defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14 + // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14 +# define _MDSPAN_USE_STANDARD_TRAIT_ALIASES 1 +# endif +#endif + +#ifndef _MDSPAN_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND +# ifdef __GNUC__ +# if __GNUC__ < 9 +# define _MDSPAN_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND 1 +# endif +# endif +#endif + +#ifndef MDSPAN_CONDITIONAL_EXPLICIT +# if MDSPAN_HAS_CXX_20 +# define MDSPAN_CONDITIONAL_EXPLICIT(COND) explicit(COND) +# else +# define MDSPAN_CONDITIONAL_EXPLICIT(COND) +# endif +#endif + +#ifndef MDSPAN_USE_BRACKET_OPERATOR +# if defined(__cpp_multidimensional_subscript) +# define MDSPAN_USE_BRACKET_OPERATOR 1 +# else +# define MDSPAN_USE_BRACKET_OPERATOR 0 +# endif +#endif + +#ifndef MDSPAN_USE_PAREN_OPERATOR +# if !MDSPAN_USE_BRACKET_OPERATOR +# define MDSPAN_USE_PAREN_OPERATOR 1 +# else +# define MDSPAN_USE_PAREN_OPERATOR 0 +# endif +#endif + +#if MDSPAN_USE_BRACKET_OPERATOR +# define __MDSPAN_OP(mds,...) mds[__VA_ARGS__] +// Corentins demo compiler for subscript chokes on empty [] call, +// though I believe the proposal supports it? +#ifdef MDSPAN_NO_EMPTY_BRACKET_OPERATOR +# define __MDSPAN_OP0(mds) mds.accessor().access(mds.data_handle(),0) +#else +# define __MDSPAN_OP0(mds) mds[] +#endif +# define __MDSPAN_OP1(mds, a) mds[a] +# define __MDSPAN_OP2(mds, a, b) mds[a,b] +# define __MDSPAN_OP3(mds, a, b, c) mds[a,b,c] +# define __MDSPAN_OP4(mds, a, b, c, d) mds[a,b,c,d] +# define __MDSPAN_OP5(mds, a, b, c, d, e) mds[a,b,c,d,e] +# define __MDSPAN_OP6(mds, a, b, c, d, e, f) mds[a,b,c,d,e,f] +#else +# define __MDSPAN_OP(mds,...) mds(__VA_ARGS__) +# define __MDSPAN_OP0(mds) mds() +# define __MDSPAN_OP1(mds, a) mds(a) +# define __MDSPAN_OP2(mds, a, b) mds(a,b) +# define __MDSPAN_OP3(mds, a, b, c) mds(a,b,c) +# define __MDSPAN_OP4(mds, a, b, c, d) mds(a,b,c,d) +# define __MDSPAN_OP5(mds, a, b, c, d, e) mds(a,b,c,d,e) +# define __MDSPAN_OP6(mds, a, b, c, d, e, f) mds(a,b,c,d,e,f) +#endif +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/config.hpp + +#include +#include +#include // std::is_void +#if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_SYCL) +#include "assert.h" +#endif + +#ifndef _MDSPAN_HOST_DEVICE +# if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP) +# define _MDSPAN_HOST_DEVICE __host__ __device__ +# else +# define _MDSPAN_HOST_DEVICE +# endif +#endif + +#ifndef MDSPAN_FORCE_INLINE_FUNCTION +# ifdef _MDSPAN_COMPILER_MSVC // Microsoft compilers +# define MDSPAN_FORCE_INLINE_FUNCTION __forceinline _MDSPAN_HOST_DEVICE +# else +# define MDSPAN_FORCE_INLINE_FUNCTION __attribute__((always_inline)) _MDSPAN_HOST_DEVICE +# endif +#endif + +#ifndef MDSPAN_INLINE_FUNCTION +# define MDSPAN_INLINE_FUNCTION inline _MDSPAN_HOST_DEVICE +#endif + +#ifndef MDSPAN_FUNCTION +# define MDSPAN_FUNCTION _MDSPAN_HOST_DEVICE +#endif + +#ifdef _MDSPAN_HAS_HIP +# define MDSPAN_DEDUCTION_GUIDE _MDSPAN_HOST_DEVICE +#else +# define MDSPAN_DEDUCTION_GUIDE +#endif + +// In CUDA defaulted functions do not need host device markup +#ifndef MDSPAN_INLINE_FUNCTION_DEFAULTED +# define MDSPAN_INLINE_FUNCTION_DEFAULTED +#endif + +//============================================================================== +// {{{1 + +#define MDSPAN_PP_COUNT(...) \ + _MDSPAN_PP_INTERNAL_EXPAND_ARGS_PRIVATE( \ + _MDSPAN_PP_INTERNAL_ARGS_AUGMENTER(__VA_ARGS__) \ + ) + +#define _MDSPAN_PP_INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__ +#define _MDSPAN_PP_INTERNAL_EXPAND(x) x +#define _MDSPAN_PP_INTERNAL_EXPAND_ARGS_PRIVATE(...) \ + _MDSPAN_PP_INTERNAL_EXPAND( \ + _MDSPAN_PP_INTERNAL_COUNT_PRIVATE( \ + __VA_ARGS__, 69, 68, 67, 66, 65, 64, 63, 62, 61, \ + 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, \ + 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, \ + 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, \ + 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, \ + 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 \ + ) \ + ) +# define _MDSPAN_PP_INTERNAL_COUNT_PRIVATE( \ + _1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, \ + _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, \ + _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, \ + _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, \ + _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, \ + _70, count, ...) count \ + /**/ + +#define MDSPAN_PP_STRINGIFY_IMPL(x) #x +#define MDSPAN_PP_STRINGIFY(x) MDSPAN_PP_STRINGIFY_IMPL(x) + +#define MDSPAN_PP_CAT_IMPL(x, y) x ## y +#define MDSPAN_PP_CAT(x, y) MDSPAN_PP_CAT_IMPL(x, y) + +#define MDSPAN_PP_EVAL(X, ...) X(__VA_ARGS__) + +#define MDSPAN_PP_REMOVE_PARENS_IMPL(...) __VA_ARGS__ +#define MDSPAN_PP_REMOVE_PARENS(...) MDSPAN_PP_REMOVE_PARENS_IMPL __VA_ARGS__ + +#define MDSPAN_IMPL_STANDARD_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE) +#define MDSPAN_IMPL_PROPOSED_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE) "::" MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_PROPOSED_NAMESPACE) + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +#if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP) +MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) +{ + printf("%s:%u: precondition failure: `%s`\n", file, line, cond); + assert(0); +} +#elif defined(_MDSPAN_HAS_SYCL) +MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) +{ + sycl::ext::oneapi::experimental::printf("%s:%u: precondition failure: `%s`\n", file, line, cond); + assert(0); +} +#else +MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) +{ + std::fprintf(stderr, "%s:%u: precondition failure: `%s`\n", file, line, cond); + std::abort(); +} +#endif + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#ifndef MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER +#define MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER(cond, file, line) \ + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::default_precondition_violation_handler(cond, file, line) +#endif + +#ifndef MDSPAN_IMPL_CHECK_PRECONDITION + #ifndef NDEBUG + #define MDSPAN_IMPL_CHECK_PRECONDITION 0 + #else + #define MDSPAN_IMPL_CHECK_PRECONDITION 1 + #endif +#endif + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +template +MDSPAN_FUNCTION constexpr void precondition(const char* cond, const char* file, unsigned line) +{ + if (not check) { return; } + // in case the macro doesn't use the arguments for custom macros + (void) cond; + (void) file; + (void) line; + MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER(cond, file, line); +} + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#define MDSPAN_IMPL_PRECONDITION(...) \ + do { \ + if (not (__VA_ARGS__)) { \ + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::precondition(#__VA_ARGS__, __FILE__, __LINE__); \ + } \ + } while (0) + +// end Preprocessor helpers }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +// These compatibility macros don't help with partial ordering, but they should do the trick +// for what we need to do with concepts in mdspan +#ifdef _MDSPAN_USE_CONCEPTS +# define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) > requires REQ +# define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \ + MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS requires REQ \ + /**/ +#else +# define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) , typename ::std::enable_if<(REQ), int>::type = 0> +# define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \ + MDSPAN_TEMPLATE_REQUIRES( \ + class __function_requires_ignored=void, \ + (std::is_void<__function_requires_ignored>::value && REQ) \ + ) MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS \ + /**/ +#endif + +#if defined(_MDSPAN_COMPILER_MSVC) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL) +# define MDSPAN_TEMPLATE_REQUIRES(...) \ + MDSPAN_PP_CAT( \ + MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__))\ + (__VA_ARGS__), \ + ) \ + /**/ +#else +# define MDSPAN_TEMPLATE_REQUIRES(...) \ + MDSPAN_PP_EVAL( \ + MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__)), \ + __VA_ARGS__ \ + ) \ + /**/ +#endif + +#define MDSPAN_TEMPLATE_REQUIRES_2(TP1, REQ) \ + template end Concept emulation }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#ifdef _MDSPAN_USE_INLINE_VARIABLES +# define _MDSPAN_INLINE_VARIABLE inline +#else +# define _MDSPAN_INLINE_VARIABLE +#endif + +// end inline variables }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if _MDSPAN_USE_RETURN_TYPE_DEDUCTION +# define _MDSPAN_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); } +# define _MDSPAN_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + decltype(auto) MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); } +#else +# define _MDSPAN_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \ + -> std::remove_cv_t> \ + { return MDSPAN_PP_REMOVE_PARENS(BODY); } +# define _MDSPAN_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \ + -> decltype(BODY) \ + { return MDSPAN_PP_REMOVE_PARENS(BODY); } + +#endif + +// end Return type deduction }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +struct __mdspan_enable_fold_comma { }; + +#ifdef _MDSPAN_USE_FOLD_EXPRESSIONS +# define _MDSPAN_FOLD_AND(...) ((__VA_ARGS__) && ...) +# define _MDSPAN_FOLD_AND_TEMPLATE(...) ((__VA_ARGS__) && ...) +# define _MDSPAN_FOLD_OR(...) ((__VA_ARGS__) || ...) +# define _MDSPAN_FOLD_ASSIGN_LEFT(INIT, ...) (INIT = ... = (__VA_ARGS__)) +# define _MDSPAN_FOLD_ASSIGN_RIGHT(PACK, ...) (PACK = ... = (__VA_ARGS__)) +# define _MDSPAN_FOLD_TIMES_RIGHT(PACK, ...) (PACK * ... * (__VA_ARGS__)) +# define _MDSPAN_FOLD_PLUS_RIGHT(PACK, ...) (PACK + ... + (__VA_ARGS__)) +# define _MDSPAN_FOLD_COMMA(...) ((__VA_ARGS__), ...) +#else + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +namespace __fold_compatibility_impl { + +// We could probably be more clever here, but at the (small) risk of losing some compiler understanding. For the +// few operations we need, it's not worth generalizing over the operation + +#if _MDSPAN_USE_RETURN_TYPE_DEDUCTION + +MDSPAN_FORCE_INLINE_FUNCTION +constexpr decltype(auto) __fold_right_and_impl() { + return true; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr decltype(auto) __fold_right_and_impl(Arg&& arg, Args&&... args) { + return ((Arg&&)arg) && __fold_compatibility_impl::__fold_right_and_impl((Args&&)args...); +} + +MDSPAN_FORCE_INLINE_FUNCTION +constexpr decltype(auto) __fold_right_or_impl() { + return false; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_or_impl(Arg&& arg, Args&&... args) { + return ((Arg&&)arg) || __fold_compatibility_impl::__fold_right_or_impl((Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_left_assign_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_left_assign_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return __fold_compatibility_impl::__fold_left_assign_impl((((Arg1&&)arg1) = ((Arg2&&)arg2)), (Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_assign_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_assign_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return ((Arg1&&)arg1) = __fold_compatibility_impl::__fold_right_assign_impl((Arg2&&)arg2, (Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_plus_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_plus_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return ((Arg1&&)arg1) + __fold_compatibility_impl::__fold_right_plus_impl((Arg2&&)arg2, (Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_times_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto __fold_right_times_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return ((Arg1&&)arg1) * __fold_compatibility_impl::__fold_right_times_impl((Arg2&&)arg2, (Args&&)args...); +} + +#else + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_right_and_impl_; +template <> +struct __fold_right_and_impl_<> { + using __rv = bool; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl() noexcept { + return true; + } +}; +template +struct __fold_right_and_impl_ { + using __next_t = __fold_right_and_impl_; + using __rv = decltype(std::declval() && std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg, Args&&... args) noexcept { + return ((Arg&&)arg) && __next_t::__impl((Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_right_and_impl_::__rv +__fold_right_and_impl(Args&&... args) { + return __fold_right_and_impl_::__impl((Args&&)args...); +} + +// end right and }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_right_or_impl_; +template <> +struct __fold_right_or_impl_<> { + using __rv = bool; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl() noexcept { + return false; + } +}; +template +struct __fold_right_or_impl_ { + using __next_t = __fold_right_or_impl_; + using __rv = decltype(std::declval() || std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg, Args&&... args) noexcept { + return ((Arg&&)arg) || __next_t::__impl((Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_right_or_impl_::__rv +__fold_right_or_impl(Args&&... args) { + return __fold_right_or_impl_::__impl((Args&&)args...); +} + +// end right or }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_right_plus_impl_; +template +struct __fold_right_plus_impl_ { + using __rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct __fold_right_plus_impl_ { + using __next_t = __fold_right_plus_impl_; + using __rv = decltype(std::declval() + std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return ((Arg1&&)arg) + __next_t::__impl((Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_right_plus_impl_::__rv +__fold_right_plus_impl(Args&&... args) { + return __fold_right_plus_impl_::__impl((Args&&)args...); +} + +// end right plus }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_right_times_impl_; +template +struct __fold_right_times_impl_ { + using __rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct __fold_right_times_impl_ { + using __next_t = __fold_right_times_impl_; + using __rv = decltype(std::declval() * std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return ((Arg1&&)arg) * __next_t::__impl((Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_right_times_impl_::__rv +__fold_right_times_impl(Args&&... args) { + return __fold_right_times_impl_::__impl((Args&&)args...); +} + +// end right times }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_right_assign_impl_; +template +struct __fold_right_assign_impl_ { + using __rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct __fold_right_assign_impl_ { + using __next_t = __fold_right_assign_impl_; + using __rv = decltype(std::declval() = std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return ((Arg1&&)arg) = __next_t::__impl((Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_right_assign_impl_::__rv +__fold_right_assign_impl(Args&&... args) { + return __fold_right_assign_impl_::__impl((Args&&)args...); +} + +// end right assign }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct __fold_left_assign_impl_; +template +struct __fold_left_assign_impl_ { + using __rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct __fold_left_assign_impl_ { + using __assign_result_t = decltype(std::declval() = std::declval()); + using __next_t = __fold_left_assign_impl_<__assign_result_t, Args...>; + using __rv = typename __next_t::__rv; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr __rv + __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return __next_t::__impl(((Arg1&&)arg) = (Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename __fold_left_assign_impl_::__rv +__fold_left_assign_impl(Args&&... args) { + return __fold_left_assign_impl_::__impl((Args&&)args...); +} + +// end left assign }}}2 +//------------------------------------------------------------------------------ + +#endif + + +template +constexpr __mdspan_enable_fold_comma __fold_comma_impl(Args&&... args) noexcept { return { }; } + +template +struct __bools; + +} // __fold_compatibility_impl + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +# define _MDSPAN_FOLD_AND(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_and_impl((__VA_ARGS__)...) +# define _MDSPAN_FOLD_OR(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_or_impl((__VA_ARGS__)...) +# define _MDSPAN_FOLD_ASSIGN_LEFT(INIT, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_left_assign_impl(INIT, (__VA_ARGS__)...) +# define _MDSPAN_FOLD_ASSIGN_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_assign_impl((PACK)..., __VA_ARGS__) +# define _MDSPAN_FOLD_TIMES_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_times_impl((PACK)..., __VA_ARGS__) +# define _MDSPAN_FOLD_PLUS_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_plus_impl((PACK)..., __VA_ARGS__) +# define _MDSPAN_FOLD_COMMA(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_comma_impl((__VA_ARGS__)...) + +# define _MDSPAN_FOLD_AND_TEMPLATE(...) \ + _MDSPAN_TRAIT(std::is_same, __fold_compatibility_impl::__bools<(__VA_ARGS__)..., true>, __fold_compatibility_impl::__bools) + +#endif + +// end fold expressions }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if _MDSPAN_USE_VARIABLE_TEMPLATES +# define _MDSPAN_TRAIT(TRAIT, ...) TRAIT##_v<__VA_ARGS__> +#else +# define _MDSPAN_TRAIT(TRAIT, ...) TRAIT<__VA_ARGS__>::value +#endif + +// end Variable template compatibility }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if _MDSPAN_USE_CONSTEXPR_14 +# define _MDSPAN_CONSTEXPR_14 constexpr +// Workaround for a bug (I think?) in EDG frontends +# ifdef __EDG__ +# define _MDSPAN_CONSTEXPR_14_DEFAULTED +# else +# define _MDSPAN_CONSTEXPR_14_DEFAULTED constexpr +# endif +#else +# define _MDSPAN_CONSTEXPR_14 +# define _MDSPAN_CONSTEXPR_14_DEFAULTED +#endif + +// end Pre-C++14 constexpr }}}1 +//============================================================================== +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/macros.hpp + +#include // size_t + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +template +struct default_accessor { + + using offset_policy = default_accessor; + using element_type = ElementType; + using reference = ElementType&; + using data_handle_type = ElementType*; + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr default_accessor() noexcept = default; + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, OtherElementType(*)[], element_type(*)[]) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr default_accessor(default_accessor) noexcept {} + + MDSPAN_INLINE_FUNCTION + constexpr data_handle_type + offset(data_handle_type p, size_t i) const noexcept { + return p + i; + } + + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference access(data_handle_type p, size_t i) const noexcept { + return p[i]; + } + +}; + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/default_accessor.hpp +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/full_extent_t.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +struct full_extent_t { explicit full_extent_t() = default; }; + +_MDSPAN_INLINE_VARIABLE constexpr auto full_extent = full_extent_t{ }; + +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/full_extent_t.hpp +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/mdspan.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_right.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/trait_backports.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER +#ifndef MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ +#define MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ + + +#include +#include // integer_sequence + +//============================================================================== +// {{{1 + +#ifdef _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS + +#if _MDSPAN_USE_VARIABLE_TEMPLATES +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +#define _MDSPAN_BACKPORT_TRAIT(TRAIT) \ + template _MDSPAN_INLINE_VARIABLE constexpr auto TRAIT##_v = TRAIT::value; + +_MDSPAN_BACKPORT_TRAIT(is_assignable) +_MDSPAN_BACKPORT_TRAIT(is_constructible) +_MDSPAN_BACKPORT_TRAIT(is_convertible) +_MDSPAN_BACKPORT_TRAIT(is_default_constructible) +_MDSPAN_BACKPORT_TRAIT(is_trivially_destructible) +_MDSPAN_BACKPORT_TRAIT(is_same) +_MDSPAN_BACKPORT_TRAIT(is_empty) +_MDSPAN_BACKPORT_TRAIT(is_void) + +#undef _MDSPAN_BACKPORT_TRAIT + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#endif // _MDSPAN_USE_VARIABLE_TEMPLATES + +#endif // _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS + +// end Variable template trait backports (e.g., is_void_v) }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if !defined(_MDSPAN_USE_INTEGER_SEQUENCE) || !_MDSPAN_USE_INTEGER_SEQUENCE + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +template +struct integer_sequence { + static constexpr size_t size() noexcept { return sizeof...(Vals); } + using value_type = T; +}; + +template +using index_sequence = std::integer_sequence; + +namespace __detail { + +template +struct __make_int_seq_impl; + +template +struct __make_int_seq_impl> +{ + using type = integer_sequence; +}; + +template +struct __make_int_seq_impl< + T, N, I, integer_sequence +> : __make_int_seq_impl> +{ }; + +} // end namespace __detail + +template +using make_integer_sequence = typename __detail::__make_int_seq_impl>::type; + +template +using make_index_sequence = typename __detail::__make_int_seq_impl>::type; + +template +using index_sequence_for = make_index_sequence; + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#endif + +// end integer sequence (ugh...) }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if !defined(_MDSPAN_USE_STANDARD_TRAIT_ALIASES) || !_MDSPAN_USE_STANDARD_TRAIT_ALIASES + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +#define _MDSPAN_BACKPORT_TRAIT_ALIAS(TRAIT) \ + template using TRAIT##_t = typename TRAIT::type; + +_MDSPAN_BACKPORT_TRAIT_ALIAS(remove_cv) +_MDSPAN_BACKPORT_TRAIT_ALIAS(remove_reference) + +template +using enable_if_t = typename enable_if<_B, _T>::type; + +#undef _MDSPAN_BACKPORT_TRAIT_ALIAS + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#endif + +// end standard trait aliases }}}1 +//============================================================================== + +#endif //MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/trait_backports.hpp +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/extents.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/dynamic_extent.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#if defined(__cpp_lib_span) +#include +#endif + +#include // size_t +#include // numeric_limits + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +#if defined(__cpp_lib_span) +using std::dynamic_extent; +#else +_MDSPAN_INLINE_VARIABLE constexpr auto dynamic_extent = std::numeric_limits::max(); +#endif +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +//============================================================================================================== +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/dynamic_extent.hpp +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/utility.hpp + +#include +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +// type alias used for rank-based tag dispatch +// +// this is used to enable alternatives to constexpr if when building for C++14 +// +template +using with_rank = std::integral_constant; + +template +constexpr bool common_integral_compare(I1 x, I2 y) +{ + static_assert(std::is_integral::value and + std::is_integral::value, ""); + + using I = std::common_type_t; + return static_cast(x) == static_cast(y); +} + +template +constexpr bool rankwise_equal(with_rank<0>, const T1&, const T2&, F) +{ + return true; +} +template +constexpr bool rankwise_equal(with_rank, const T1& x, const T2& y, F func) +{ + bool match = true; + + for (std::size_t r = 0; r < N; r++) { + match = match && common_integral_compare(func(x, r), func(y, r)); + } + + return match; +} + +constexpr struct +{ + template + constexpr auto operator()(const T& x, I i) const + { + return x.extent(i); + } +} extent; + +constexpr struct +{ + template + constexpr auto operator()(const T& x, I i) const + { + return x.stride(i); + } +} stride; + +} // namespace detail + +constexpr struct mdspan_non_standard_tag { +} mdspan_non_standard; + +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/utility.hpp + +#ifdef __cpp_lib_span +#include +#endif +#include +#include + +#include +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +// Function used to check compatibility of extents in converting constructor +// can't be a private member function for some reason. +template +static constexpr std::integral_constant __check_compatible_extents( + std::integral_constant, + std::integer_sequence, + std::integer_sequence) noexcept { + return {}; +} + +// This helper prevents ICE's on MSVC. +template +struct __compare_extent_compatible : std::integral_constant +{}; + +template +static constexpr std::integral_constant< + bool, _MDSPAN_FOLD_AND(__compare_extent_compatible::value)> +__check_compatible_extents( + std::integral_constant, + std::integer_sequence, + std::integer_sequence) noexcept { + return {}; +} + +template +MDSPAN_INLINE_FUNCTION +static constexpr bool are_valid_indices() { + return + _MDSPAN_FOLD_AND(std::is_convertible::value) && + _MDSPAN_FOLD_AND(std::is_nothrow_constructible::value); +} + +// ------------------------------------------------------------------ +// ------------ static_array ---------------------------------------- +// ------------------------------------------------------------------ + +// array like class which provides an array of static values with get +// function and operator []. + +// Implementation of Static Array with recursive implementation of get. +template struct static_array_impl; + +template +struct static_array_impl { + MDSPAN_INLINE_FUNCTION + constexpr static T get(size_t r) { + if (r == R) + return FirstExt; + else + return static_array_impl::get(r); + } + template MDSPAN_INLINE_FUNCTION constexpr static T get() { +#if MDSPAN_HAS_CXX_17 + if constexpr (r == R) + return FirstExt; + else + return static_array_impl::template get(); +#else + get(r); +#endif + } +}; + +// End the recursion +template +struct static_array_impl { + MDSPAN_INLINE_FUNCTION + constexpr static T get(size_t) { return FirstExt; } + template MDSPAN_INLINE_FUNCTION constexpr static T get() { + return FirstExt; + } +}; + +// Don't start recursion if size 0 +template struct static_array_impl<0, T> { + MDSPAN_INLINE_FUNCTION + constexpr static T get(size_t) { return T(); } + template MDSPAN_INLINE_FUNCTION constexpr static T get() { + return T(); + } +}; + +// Static array, provides get(), get(r) and operator[r] +template struct static_array: + public static_array_impl<0, T, Values...> { + +public: + using value_type = T; + + MDSPAN_INLINE_FUNCTION + constexpr static size_t size() { return sizeof...(Values); } +}; + + +// ------------------------------------------------------------------ +// ------------ index_sequence_scan --------------------------------- +// ------------------------------------------------------------------ + +// index_sequence_scan takes compile time values and provides get(r) +// and get() which return the sum of the first r-1 values. + +// Recursive implementation for get +template struct index_sequence_scan_impl; + +template +struct index_sequence_scan_impl { + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t r) { + if (r > R) + return FirstVal + index_sequence_scan_impl::get(r); + else + return 0; + } +}; + +template +struct index_sequence_scan_impl { +#if defined(__NVCC__) || defined(__NVCOMPILER) || \ + defined(_MDSPAN_COMPILER_INTEL) + // NVCC warns about pointless comparison with 0 for R==0 and r being const + // evaluatable and also 0. + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t r) { + return static_cast(R) > static_cast(r) ? FirstVal : 0; + } +#else + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t r) { return R > r ? FirstVal : 0; } +#endif +}; +template <> struct index_sequence_scan_impl<0> { + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t) { return 0; } +}; + +// ------------------------------------------------------------------ +// ------------ possibly_empty_array ------------------------------- +// ------------------------------------------------------------------ + +// array like class which provides get function and operator [], and +// has a specialization for the size 0 case. +// This is needed to make the maybe_static_array be truly empty, for +// all static values. + +template struct possibly_empty_array { + T vals[N]{}; + MDSPAN_INLINE_FUNCTION + constexpr T &operator[](size_t r) { return vals[r]; } + MDSPAN_INLINE_FUNCTION + constexpr const T &operator[](size_t r) const { return vals[r]; } +}; + +template struct possibly_empty_array { + MDSPAN_INLINE_FUNCTION + constexpr T operator[](size_t) { return T(); } + MDSPAN_INLINE_FUNCTION + constexpr const T operator[](size_t) const { return T(); } +}; + +// ------------------------------------------------------------------ +// ------------ maybe_static_array ---------------------------------- +// ------------------------------------------------------------------ + +// array like class which has a mix of static and runtime values but +// only stores the runtime values. +// The type of the static and the runtime values can be different. +// The position of a dynamic value is indicated through a tag value. +template +struct maybe_static_array { + + static_assert(std::is_convertible::value, "maybe_static_array: TStatic must be convertible to TDynamic"); + static_assert(std::is_convertible::value, "maybe_static_array: TDynamic must be convertible to TStatic"); + +private: + // Static values member + using static_vals_t = static_array; + constexpr static size_t m_size = sizeof...(Values); + constexpr static size_t m_size_dynamic = + _MDSPAN_FOLD_PLUS_RIGHT((Values == dyn_tag), 0); + + // Dynamic values member + _MDSPAN_NO_UNIQUE_ADDRESS possibly_empty_array + m_dyn_vals; + + // static mapping of indices to the position in the dynamic values array + using dyn_map_t = index_sequence_scan_impl<0, static_cast(Values == dyn_tag)...>; +public: + + // two types for static and dynamic values + using value_type = TDynamic; + using static_value_type = TStatic; + // tag value indicating dynamic value + constexpr static static_value_type tag_value = dyn_tag; + + constexpr maybe_static_array() = default; + + // constructor for all static values + // TODO: add precondition check? + MDSPAN_TEMPLATE_REQUIRES(class... Vals, + /* requires */ ((m_size_dynamic == 0) && + (sizeof...(Vals) > 0))) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(Vals...) : m_dyn_vals{} {} + + // constructors from dynamic values only + MDSPAN_TEMPLATE_REQUIRES(class... DynVals, + /* requires */ (sizeof...(DynVals) == + m_size_dynamic && + m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(DynVals... vals) + : m_dyn_vals{static_cast(vals)...} {} + + + MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, + /* requires */ (N == m_size_dynamic && N > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::array &vals) { + for (size_t r = 0; r < N; r++) + m_dyn_vals[r] = static_cast(vals[r]); + } + + MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, + /* requires */ (N == m_size_dynamic && N == 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::array &) : m_dyn_vals{} {} + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, + /* requires */ (N == m_size_dynamic && N > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::span &vals) { + for (size_t r = 0; r < N; r++) + m_dyn_vals[r] = static_cast(vals[r]); + } + + MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, + /* requires */ (N == m_size_dynamic && N == 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::span &) : m_dyn_vals{} {} +#endif + + // constructors from all values + MDSPAN_TEMPLATE_REQUIRES(class... DynVals, + /* requires */ (sizeof...(DynVals) != + m_size_dynamic && + m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(DynVals... vals) + : m_dyn_vals{} { + static_assert((sizeof...(DynVals) == m_size), "Invalid number of values."); + TDynamic values[m_size]{static_cast(vals)...}; + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = values[r]; + } +// Precondition check +#ifdef _MDSPAN_DEBUG + else { + assert(values[r] == static_cast(static_val)); + } +#endif + } + } + + MDSPAN_TEMPLATE_REQUIRES( + class T, size_t N, + /* requires */ (N != m_size_dynamic && m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::array &vals) { + static_assert((N == m_size), "Invalid number of values."); +// Precondition check +#ifdef _MDSPAN_DEBUG + assert(N == m_size); +#endif + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); + } +// Precondition check +#ifdef _MDSPAN_DEBUG + else { + assert(static_cast(vals[r]) == + static_cast(static_val)); + } +#endif + } + } + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class T, size_t N, + /* requires */ (N != m_size_dynamic && m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::span &vals) { + static_assert((N == m_size) || (m_size == dynamic_extent)); +#ifdef _MDSPAN_DEBUG + assert(N == m_size); +#endif + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); + } +#ifdef _MDSPAN_DEBUG + else { + assert(static_cast(vals[r]) == + static_cast(static_val)); + } +#endif + } + } +#endif + + // access functions + MDSPAN_INLINE_FUNCTION + constexpr static TStatic static_value(size_t r) { return static_vals_t::get(r); } + + MDSPAN_INLINE_FUNCTION + constexpr TDynamic value(size_t r) const { + TStatic static_val = static_vals_t::get(r); + return static_val == dyn_tag ? m_dyn_vals[dyn_map_t::get(r)] + : static_cast(static_val); + } + MDSPAN_INLINE_FUNCTION + constexpr TDynamic operator[](size_t r) const { return value(r); } + + + // observers + MDSPAN_INLINE_FUNCTION + constexpr static size_t size() { return m_size; } + MDSPAN_INLINE_FUNCTION + constexpr static size_t size_dynamic() { return m_size_dynamic; } +}; + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +// ------------------------------------------------------------------ +// ------------ extents --------------------------------------------- +// ------------------------------------------------------------------ + +// Class to describe the extents of a multi dimensional array. +// Used by mdspan, mdarray and layout mappings. +// See ISO C++ standard [mdspan.extents] + +template class extents { +public: + // typedefs for integral types used + using index_type = IndexType; + using size_type = std::make_unsigned_t; + using rank_type = size_t; + + static_assert(std::is_integral::value && !std::is_same::value, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents::index_type must be a signed or unsigned integer type"); +private: + constexpr static rank_type m_rank = sizeof...(Extents); + constexpr static rank_type m_rank_dynamic = + _MDSPAN_FOLD_PLUS_RIGHT((Extents == dynamic_extent), /* + ... + */ 0); + + // internal storage type using maybe_static_array + using vals_t = + detail::maybe_static_array; + _MDSPAN_NO_UNIQUE_ADDRESS vals_t m_vals; + +public: + // [mdspan.extents.obs], observers of multidimensional index space + MDSPAN_INLINE_FUNCTION + constexpr static rank_type rank() noexcept { return m_rank; } + MDSPAN_INLINE_FUNCTION + constexpr static rank_type rank_dynamic() noexcept { return m_rank_dynamic; } + + MDSPAN_INLINE_FUNCTION + constexpr index_type extent(rank_type r) const noexcept { return m_vals.value(r); } + MDSPAN_INLINE_FUNCTION + constexpr static size_t static_extent(rank_type r) noexcept { + return vals_t::static_value(r); + } + + // [mdspan.extents.cons], constructors + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr extents() noexcept = default; + + // Construction from just dynamic or all values. + // Precondition check is deferred to maybe_static_array constructor + MDSPAN_TEMPLATE_REQUIRES( + class... OtherIndexTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, OtherIndexTypes, + index_type) /* && ... */) && + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, + OtherIndexTypes) /* && ... */) && + (sizeof...(OtherIndexTypes) == m_rank || + sizeof...(OtherIndexTypes) == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + constexpr explicit extents(OtherIndexTypes... dynvals) noexcept + : m_vals(static_cast(dynvals)...) {} + + MDSPAN_TEMPLATE_REQUIRES( + class OtherIndexType, size_t N, + /* requires */ + ( + _MDSPAN_TRAIT(std::is_convertible, const OtherIndexType&, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, + const OtherIndexType&) && + (N == m_rank || N == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic) + constexpr extents(const std::array &exts) noexcept + : m_vals(std::move(exts)) {} + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class OtherIndexType, size_t N, + /* requires */ + (_MDSPAN_TRAIT(std::is_convertible, const OtherIndexType&, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const OtherIndexType&) && + (N == m_rank || N == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic) + constexpr extents(const std::span &exts) noexcept + : m_vals(std::move(exts)) {} +#endif + +private: + // Function to construct extents storage from other extents. + // With C++ 17 the first two variants could be collapsed using if constexpr + // in which case you don't need all the requires clauses. + // in C++ 14 mode that doesn't work due to infinite recursion + MDSPAN_TEMPLATE_REQUIRES( + size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, + /* requires */ ((R < m_rank) && (static_extent(R) == dynamic_extent))) + MDSPAN_INLINE_FUNCTION + constexpr + vals_t __construct_vals_from_extents(std::integral_constant, + std::integral_constant, + const OtherExtents &exts, + DynamicValues... dynamic_values) noexcept { + return __construct_vals_from_extents( + std::integral_constant(), + std::integral_constant(), exts, dynamic_values..., + exts.extent(R)); + } + + MDSPAN_TEMPLATE_REQUIRES( + size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, + /* requires */ ((R < m_rank) && (static_extent(R) != dynamic_extent))) + MDSPAN_INLINE_FUNCTION + constexpr + vals_t __construct_vals_from_extents(std::integral_constant, + std::integral_constant, + const OtherExtents &exts, + DynamicValues... dynamic_values) noexcept { + return __construct_vals_from_extents( + std::integral_constant(), + std::integral_constant(), exts, dynamic_values...); + } + + MDSPAN_TEMPLATE_REQUIRES( + size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, + /* requires */ ((R == m_rank) && (DynCount == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + constexpr + vals_t __construct_vals_from_extents(std::integral_constant, + std::integral_constant, + const OtherExtents &, + DynamicValues... dynamic_values) noexcept { + return vals_t{static_cast(dynamic_values)...}; + } + +public: + + // Converting constructor from other extents specializations + MDSPAN_TEMPLATE_REQUIRES( + class OtherIndexType, size_t... OtherExtents, + /* requires */ + ( + /* multi-stage check to protect from invalid pack expansion when sizes + don't match? */ + decltype(detail::__check_compatible_extents( + // using: sizeof...(Extents) == sizeof...(OtherExtents) as the second argument fails with MSVC+NVCC with some obscure expansion error + // MSVC: 19.38.33133 NVCC: 12.0 + std::integral_constant::rank() == extents::rank()>{}, + std::integer_sequence{}, + std::integer_sequence{}))::value + ) + ) + MDSPAN_INLINE_FUNCTION + MDSPAN_CONDITIONAL_EXPLICIT((((Extents != dynamic_extent) && + (OtherExtents == dynamic_extent)) || + ...) || + (std::numeric_limits::max() < + std::numeric_limits::max())) + constexpr extents(const extents &other) noexcept + : m_vals(__construct_vals_from_extents( + std::integral_constant(), + std::integral_constant(), other)) {} + + // Comparison operator + template + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator==(const extents &lhs, + const extents &rhs) noexcept { + return + rank() == extents::rank() && + detail::rankwise_equal(detail::with_rank{}, rhs, lhs, detail::extent); + } + +#if !(MDSPAN_HAS_CXX_20) + template + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator!=(extents const &lhs, + extents const &rhs) noexcept { + return !(lhs == rhs); + } +#endif +}; + +// Recursive helper classes to implement dextents alias for extents +namespace detail { + +template > +struct __make_dextents; + +template +struct __make_dextents< + IndexType, Rank, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> +{ + using type = typename __make_dextents< + IndexType, Rank - 1, + ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents>::type; +}; + +template +struct __make_dextents< + IndexType, 0, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> +{ + using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents; +}; + +} // end namespace detail + +// [mdspan.extents.dextents], alias template +template +using dextents = typename detail::__make_dextents::type; + +// Deduction guide for extents +#if defined(_MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) +template +extents(IndexTypes...) + -> extents; +#endif + +// Helper type traits for identifying a class as extents. +namespace detail { + +template struct __is_extents : ::std::false_type {}; + +template +struct __is_extents<::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> + : ::std::true_type {}; + +template +#if MDSPAN_HAS_CXX_17 +inline +#else +static +#endif +constexpr bool __is_extents_v = __is_extents::value; + +template +MDSPAN_INLINE_FUNCTION +constexpr void +check_lower_bound(InputIndexType user_index, + ExtentsIndexType /* current_extent */, + std::true_type /* is_signed */) +{ + (void) user_index; // prevent unused variable warning +#ifdef _MDSPAN_DEBUG + assert(static_cast(user_index) >= 0); +#endif +} + +template +MDSPAN_INLINE_FUNCTION +constexpr void +check_lower_bound(InputIndexType /* user_index */, + ExtentsIndexType /* current_extent */, + std::false_type /* is_signed */) +{} + +template +MDSPAN_INLINE_FUNCTION +constexpr void +check_upper_bound(InputIndexType user_index, + ExtentsIndexType current_extent) +{ + (void) user_index; // prevent unused variable warnings + (void) current_extent; +#ifdef _MDSPAN_DEBUG + assert(static_cast(user_index) < current_extent); +#endif +} + +// Returning true to use AND fold instead of comma +// CPP14 mode doesn't like the use of void expressions +// with the way the _MDSPAN_FOLD_AND is set up +template +MDSPAN_INLINE_FUNCTION +constexpr bool +check_one_index(InputIndex user_index, + ExtentsIndexType current_extent) +{ + check_lower_bound(user_index, current_extent, + std::integral_constant::value>{}); + check_upper_bound(user_index, current_extent); + return true; +} + +template +MDSPAN_INLINE_FUNCTION +constexpr void +check_all_indices_helper(std::index_sequence, + const extents& exts, + Indices... indices) +{ + // Suppress warning about statement has no effect + (void) _MDSPAN_FOLD_AND( + (check_one_index(indices, exts.extent(RankIndices))) + ); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr void +check_all_indices(const extents& exts, + Indices... indices) +{ + check_all_indices_helper(std::make_index_sequence(), + exts, indices...); +} + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/extents.hpp +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_stride.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/compressed_pair.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/no_unique_address.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +//============================================================================== + +template +struct __no_unique_address_emulation { + using __stored_type = _T; + _T __v; + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__ref() const noexcept { + return __v; + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__ref() noexcept { + return __v; + } +}; + +// Empty case +// This doesn't work if _T is final, of course, but we're not using anything +// like that currently. That kind of thing could be added pretty easily though +template +struct __no_unique_address_emulation< + _T, _Disambiguator, + std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T) && + // If the type isn't trivially destructible, its destructor + // won't be called at the right time, so don't use this + // specialization + _MDSPAN_TRAIT(std::is_trivially_destructible, _T)>> : +#ifdef _MDSPAN_COMPILER_MSVC + // MSVC doesn't allow you to access public static member functions of a type + // when you *happen* to privately inherit from that type. + protected +#else + // But we still want this to be private if possible so that we don't accidentally + // access members of _T directly rather than calling __ref() first, which wouldn't + // work if _T happens to be stateful and thus we're using the unspecialized definition + // of __no_unique_address_emulation above. + private +#endif + _T { + using __stored_type = _T; + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__ref() const noexcept { + return *static_cast<_T const *>(this); + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__ref() noexcept { + return *static_cast<_T *>(this); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __no_unique_address_emulation() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __no_unique_address_emulation( + __no_unique_address_emulation const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __no_unique_address_emulation( + __no_unique_address_emulation &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __no_unique_address_emulation & + operator=(__no_unique_address_emulation const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __no_unique_address_emulation & + operator=(__no_unique_address_emulation &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~__no_unique_address_emulation() noexcept = default; + + // Explicitly make this not a reference so that the copy or move + // constructor still gets called. + MDSPAN_INLINE_FUNCTION + explicit constexpr __no_unique_address_emulation(_T const& __v) noexcept : _T(__v) {} + MDSPAN_INLINE_FUNCTION + explicit constexpr __no_unique_address_emulation(_T&& __v) noexcept : _T(::std::move(__v)) {} +}; + +//============================================================================== + +} // end namespace detail +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/no_unique_address.hpp +#endif + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +// For no unique address emulation, this is the case taken when neither are empty. +// For real `[[no_unique_address]]`, this case is always taken. +template struct __compressed_pair { + _MDSPAN_NO_UNIQUE_ADDRESS _T1 __t1_val{}; + _MDSPAN_NO_UNIQUE_ADDRESS _T2 __t2_val{}; + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { return __t1_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { + return __t1_val; + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { return __t2_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { + return __t2_val; + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair() = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~__compressed_pair() = default; + template + MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) + : __t1_val((_T1Like &&) __t1), __t2_val((_T2Like &&) __t2) {} +}; + +#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + +// First empty. +template +struct __compressed_pair< + _T1, _T2, + std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T1) && !_MDSPAN_TRAIT(std::is_empty, _T2)>> + : private _T1 { + _T2 __t2_val{}; + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { + return *static_cast<_T1 *>(this); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { + return *static_cast<_T1 const *>(this); + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { return __t2_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { + return __t2_val; + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair() = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~__compressed_pair() = default; + template + MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) + : _T1((_T1Like &&) __t1), __t2_val((_T2Like &&) __t2) {} +}; + +// Second empty. +template +struct __compressed_pair< + _T1, _T2, + std::enable_if_t> + : private _T2 { + _T1 __t1_val{}; + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { return __t1_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { + return __t1_val; + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { + return *static_cast<_T2 *>(this); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { + return *static_cast<_T2 const *>(this); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair() = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~__compressed_pair() = default; + + template + MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) + : _T2((_T2Like &&) __t2), __t1_val((_T1Like &&) __t1) {} +}; + +// Both empty. +template +struct __compressed_pair< + _T1, _T2, + std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T1) && _MDSPAN_TRAIT(std::is_empty, _T2)>> + // We need to use the __no_unique_address_emulation wrapper here to avoid + // base class ambiguities. +#ifdef _MDSPAN_COMPILER_MSVC +// MSVC doesn't allow you to access public static member functions of a type +// when you *happen* to privately inherit from that type. + : protected __no_unique_address_emulation<_T1, 0>, + protected __no_unique_address_emulation<_T2, 1> +#else + : private __no_unique_address_emulation<_T1, 0>, + private __no_unique_address_emulation<_T2, 1> +#endif +{ + using __first_base_t = __no_unique_address_emulation<_T1, 0>; + using __second_base_t = __no_unique_address_emulation<_T2, 1>; + + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { + return this->__first_base_t::__ref(); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { + return this->__first_base_t::__ref(); + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { + return this->__second_base_t::__ref(); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { + return this->__second_base_t::__ref(); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair() = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr __compressed_pair(__compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & + operator=(__compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~__compressed_pair() = default; + template + MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) noexcept + : __first_base_t(_T1((_T1Like &&) __t1)), + __second_base_t(_T2((_T2Like &&) __t2)) + { } +}; + +#endif // !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + +} // end namespace detail +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/compressed_pair.hpp + +#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) +#endif + +#include +#include +#include + +#ifdef __cpp_lib_span +#include +#endif +#if defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20 && defined(__cpp_lib_concepts) +# include +#endif + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +struct layout_left { + template + class mapping; +}; +struct layout_right { + template + class mapping; +}; + +namespace detail { + template + constexpr bool __is_mapping_of = + std::is_same, Mapping>::value; + +#if defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20 +# if !defined(__cpp_lib_concepts) + namespace internal { + namespace detail { + template + concept __same_as = std::is_same_v<_Tp, _Up>; + } // namespace detail + template + concept __same_as = detail::__same_as && detail::__same_as; + } // namespace internal +# endif + + template + concept __layout_mapping_alike = requires { + requires __is_extents::value; +#if defined(__cpp_lib_concepts) + { M::is_always_strided() } -> std::same_as; + { M::is_always_exhaustive() } -> std::same_as; + { M::is_always_unique() } -> std::same_as; +#else + { M::is_always_strided() } -> internal::__same_as; + { M::is_always_exhaustive() } -> internal::__same_as; + { M::is_always_unique() } -> internal::__same_as; +#endif + std::bool_constant::value; + std::bool_constant::value; + std::bool_constant::value; + }; +#endif + +} // namespace detail + +struct layout_stride { + template + class mapping +#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : private detail::__no_unique_address_emulation< + detail::__compressed_pair< + Extents, + detail::possibly_empty_array + > + > +#endif + { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_stride; + + // This could be a `requires`, but I think it's better and clearer as a `static_assert`. + static_assert(detail::__is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_stride::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + + + private: + + //---------------------------------------------------------------------------- + + using __strides_storage_t = detail::possibly_empty_array; + using __member_pair_t = detail::__compressed_pair; + +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + _MDSPAN_NO_UNIQUE_ADDRESS __member_pair_t __members; +#else + using __base_t = detail::__no_unique_address_emulation<__member_pair_t>; +#endif + + MDSPAN_FORCE_INLINE_FUNCTION constexpr __strides_storage_t const& + __strides_storage() const noexcept { +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + return __members.__second(); +#else + return this->__base_t::__ref().__second(); +#endif + } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 __strides_storage_t& + __strides_storage() noexcept { +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + return __members.__second(); +#else + return this->__base_t::__ref().__second(); +#endif + } + + template + _MDSPAN_HOST_DEVICE + constexpr index_type __get_size(::MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { + return _MDSPAN_FOLD_TIMES_RIGHT( static_cast(extents().extent(Idx)), 1 ); + } + + //---------------------------------------------------------------------------- + + template + friend class mapping; + + //---------------------------------------------------------------------------- + + // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level + template + struct __deduction_workaround; + + template + struct __deduction_workaround> + { + template + MDSPAN_INLINE_FUNCTION + static constexpr bool _eq_impl(mapping const& self, mapping const& other) noexcept { + using common_t = std::common_type_t; + return _MDSPAN_FOLD_AND((static_cast(self.stride(Idxs)) == static_cast(other.stride(Idxs))) /* && ... */) + && _MDSPAN_FOLD_AND((static_cast(self.extents().extent(Idxs)) == static_cast(other.extents().extent(Idxs))) /* || ... */); + } + template + MDSPAN_INLINE_FUNCTION + static constexpr bool _not_eq_impl(mapping const& self, mapping const& other) noexcept { + using common_t = std::common_type_t; + return _MDSPAN_FOLD_OR((static_cast(self.stride(Idxs)) != static_cast(other.stride(Idxs))) /* || ... */) + || _MDSPAN_FOLD_OR((static_cast(self.extents().extent(Idxs)) != static_cast(other.extents().extent(Idxs))) /* || ... */); + } + + template + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr size_t _call_op_impl(mapping const& self, Integral... idxs) noexcept { + return _MDSPAN_FOLD_PLUS_RIGHT((idxs * self.stride(Idxs)), /* + ... + */ 0); + } + + MDSPAN_INLINE_FUNCTION + static constexpr size_t _req_span_size_impl(mapping const& self) noexcept { + // assumes no negative strides; not sure if I'm allowed to assume that or not + return __impl::_call_op_impl(self, (self.extents().template __extent() - 1)...) + 1; + } + + template + MDSPAN_INLINE_FUNCTION + static constexpr const __strides_storage_t fill_strides(const OtherMapping& map) { + return __strides_storage_t{static_cast(map.stride(Idxs))...}; + } + + MDSPAN_INLINE_FUNCTION + static constexpr const __strides_storage_t& fill_strides(const __strides_storage_t& s) { + return s; + } + + template + MDSPAN_INLINE_FUNCTION + static constexpr const __strides_storage_t fill_strides(const std::array& s) { + return __strides_storage_t{static_cast(s[Idxs])...}; + } + + template + MDSPAN_INLINE_FUNCTION + static constexpr const __strides_storage_t fill_strides(mdspan_non_standard_tag, const IntegralType (&s)[extents_type::rank()]) { + return __strides_storage_t{static_cast(s[Idxs])...}; + } + +#ifdef __cpp_lib_span + template + MDSPAN_INLINE_FUNCTION + static constexpr const __strides_storage_t fill_strides(const std::span& s) { + return __strides_storage_t{static_cast(s[Idxs])...}; + } +#endif + + MDSPAN_INLINE_FUNCTION + static constexpr std::array return_strides(const __strides_storage_t& s) { + return std::array{s[Idxs]...}; + } + + template + MDSPAN_INLINE_FUNCTION + static constexpr size_t __return_zero() { return 0; } + + template + MDSPAN_INLINE_FUNCTION + static constexpr typename Mapping::index_type + __OFFSET(const Mapping& m) { return m(__return_zero()...); } + }; + + // Can't use defaulted parameter in the __deduction_workaround template because of a bug in MSVC warning C4348. + using __impl = __deduction_workaround>; + + static constexpr __strides_storage_t strides_storage(detail::with_rank<0>) { + return {}; + } + template + static constexpr __strides_storage_t strides_storage(detail::with_rank) { + __strides_storage_t s{}; + + extents_type e; + index_type stride = 1; + for(int r = static_cast(extents_type::rank() - 1); r >= 0; r--) { + s[r] = stride; + stride *= e.extent(r); + } + + return s; + } + + //---------------------------------------------------------------------------- + +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + MDSPAN_INLINE_FUNCTION constexpr explicit + mapping(__member_pair_t&& __m) : __members(::std::move(__m)) {} +#else + MDSPAN_INLINE_FUNCTION constexpr explicit + mapping(__base_t&& __b) : __base_t(::std::move(__b)) {} +#endif + + public: + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : __members{ +#else + : __base_t(__base_t{__member_pair_t( +#endif + extents_type(), + __strides_storage_t(strides_storage(detail::with_rank{})) +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + {} + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; + + MDSPAN_TEMPLATE_REQUIRES( + class IntegralTypes, + /* requires */ ( + // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type + // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' + _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr + mapping( + extents_type const& e, + std::array const& s + ) noexcept +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : __members{ +#else + : __base_t(__base_t{__member_pair_t( +#endif + e, __strides_storage_t(__impl::fill_strides(s)) +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + { + /* + * TODO: check preconditions + * - s[i] > 0 is true for all i in the range [0, rank_ ). + * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). + * - If rank_ is greater than 0, then there exists a permutation P of the integers in the + * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for + * all i in the range [1, rank_ ), where pi is the ith element of P. + */ + } + + MDSPAN_TEMPLATE_REQUIRES( + class IntegralTypes, + /* requires */ ( + // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type + // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' + _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr + mapping( + mdspan_non_standard_tag, + extents_type const& e, + IntegralTypes (&s)[extents_type::rank()] + ) noexcept +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : __members{ +#else + : __base_t(__base_t{__member_pair_t( +#endif + e, __strides_storage_t(__impl::fill_strides(mdspan_non_standard, s)) +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + { + /* + * TODO: check preconditions + * - s[i] > 0 is true for all i in the range [0, rank_ ). + * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). + * - If rank_ is greater than 0, then there exists a permutation P of the integers in the + * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for + * all i in the range [1, rank_ ), where pi is the ith element of P. + */ + } + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class IntegralTypes, + /* requires */ ( + // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type + // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' + _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr + mapping( + extents_type const& e, + std::span const& s + ) noexcept +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : __members{ +#else + : __base_t(__base_t{__member_pair_t( +#endif + e, __strides_storage_t(__impl::fill_strides(s)) +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + { + /* + * TODO: check preconditions + * - s[i] > 0 is true for all i in the range [0, rank_ ). + * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). + * - If rank_ is greater than 0, then there exists a permutation P of the integers in the + * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for + * all i in the range [1, rank_ ), where pi is the ith element of P. + */ + } +#endif // __cpp_lib_span + +#if !(defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20) + MDSPAN_TEMPLATE_REQUIRES( + class StridedLayoutMapping, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) && + detail::__is_mapping_of && + StridedLayoutMapping::is_always_unique() && + StridedLayoutMapping::is_always_strided() + ) + ) +#else + template + requires( + detail::__layout_mapping_alike && + _MDSPAN_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) && + StridedLayoutMapping::is_always_unique() && + StridedLayoutMapping::is_always_strided() + ) +#endif + MDSPAN_CONDITIONAL_EXPLICIT( + !(std::is_convertible::value && + (detail::__is_mapping_of || + detail::__is_mapping_of || + detail::__is_mapping_of)) + ) // needs two () due to comma + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(StridedLayoutMapping const& other) noexcept // NOLINT(google-explicit-constructor) +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : __members{ +#else + : __base_t(__base_t{__member_pair_t( +#endif + other.extents(), __strides_storage_t(__impl::fill_strides(other)) +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + { + /* + * TODO: check preconditions + * - other.stride(i) > 0 is true for all i in the range [0, rank_ ). + * - other.required_span_size() is a representable value of type index_type ([basic.fundamental]). + * - OFFSET(other) == 0 + */ + } + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED + mapping& operator=(mapping const&) noexcept = default; + + MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { +#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + return __members.__first(); +#else + return this->__base_t::__ref().__first(); +#endif + }; + + MDSPAN_INLINE_FUNCTION + constexpr std::array< index_type, extents_type::rank() > strides() const noexcept { + return __impl::return_strides(__strides_storage()); + } + + MDSPAN_INLINE_FUNCTION + constexpr index_type required_span_size() const noexcept { + index_type span_size = 1; + for(unsigned r = 0; r < extents_type::rank(); r++) { + // Return early if any of the extents are zero + if(extents().extent(r)==0) return 0; + span_size += ( static_cast(extents().extent(r) - 1 ) * __strides_storage()[r]); + } + return span_size; + } + + + MDSPAN_TEMPLATE_REQUIRES( + class... Indices, + /* requires */ ( + sizeof...(Indices) == Extents::rank() && + (detail::are_valid_indices()) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr index_type operator()(Indices... idxs) const noexcept { +#if ! defined(NDEBUG) + detail::check_all_indices(this->extents(), idxs...); +#endif // ! NDEBUG + return static_cast(__impl::_call_op_impl(*this, static_cast(idxs)...)); + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { + return false; + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } + + private: + constexpr bool exhaustive_for_nonzero_span_size() const + { + return required_span_size() == __get_size(extents(), std::make_index_sequence()); + } + + constexpr bool is_exhaustive_impl(detail::with_rank<0>) const + { + return true; + } + constexpr bool is_exhaustive_impl(detail::with_rank<1>) const + { + if (required_span_size() != static_cast(0)) { + return exhaustive_for_nonzero_span_size(); + } + return stride(0) == 1; + } + template + constexpr bool is_exhaustive_impl(detail::with_rank) const + { + if (required_span_size() != static_cast(0)) { + return exhaustive_for_nonzero_span_size(); + } + + rank_type r_largest = 0; + for (rank_type r = 1; r < extents_type::rank(); r++) { + if (stride(r) > stride(r_largest)) { + r_largest = r; + } + } + for (rank_type r = 0; r < extents_type::rank(); r++) { + if (extents().extent(r) == 0 && r != r_largest) { + return false; + } + } + return true; + } + + public: + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 bool is_exhaustive() const noexcept { + return is_exhaustive_impl(detail::with_rank{}); + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } + + + MDSPAN_INLINE_FUNCTION + constexpr index_type stride(rank_type r) const noexcept { + return __strides_storage()[r]; + } + +#if !(defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20) + MDSPAN_TEMPLATE_REQUIRES( + class StridedLayoutMapping, + /* requires */ ( + detail::__is_mapping_of && + (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && + StridedLayoutMapping::is_always_strided() + ) + ) +#else + template + requires( + detail::__layout_mapping_alike && + (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && + StridedLayoutMapping::is_always_strided() + ) +#endif + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator==(const mapping& x, const StridedLayoutMapping& y) noexcept { + return (x.extents() == y.extents()) && + (__impl::__OFFSET(y) == static_cast(0)) && + detail::rankwise_equal(detail::with_rank{}, x, y, detail::stride); + } + + // This one is not technically part of the proposal. Just here to make implementation a bit more optimal hopefully + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + (extents_type::rank() == OtherExtents::rank()) + ) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator==(mapping const& lhs, mapping const& rhs) noexcept { + return __impl::_eq_impl(lhs, rhs); + } + +#if !MDSPAN_HAS_CXX_20 + MDSPAN_TEMPLATE_REQUIRES( + class StridedLayoutMapping, + /* requires */ ( + detail::__is_mapping_of && + (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && + StridedLayoutMapping::is_always_strided() + ) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(const mapping& x, const StridedLayoutMapping& y) noexcept { + return not (x == y); + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + (extents_type::rank() == OtherExtents::rank()) + ) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { + return __impl::_not_eq_impl(lhs, rhs); + } +#endif + + // [mdspan.submdspan.mapping], submdspan mapping specialization + template + MDSPAN_INLINE_FUNCTION + constexpr auto submdspan_mapping_impl( + SliceSpecifiers... slices) const; + + template + friend constexpr auto submdspan_mapping( + const mapping& src, SliceSpecifiers... slices) { + return src.submdspan_mapping_impl(slices...); + } + }; +}; + +namespace detail { + +template +constexpr void validate_strides(with_rank<0>, Layout, const Extents&, const Mapping&) +{} + +template +constexpr void validate_strides(with_rank, Layout, const Extents& ext, const Mapping& other) +{ + static_assert(std::is_same::value and + (std::is_same::value or + std::is_same::value) + , "This function is only intended to validate construction of " + "a layout_left or layout_right mapping from a layout_stride mapping."); + + constexpr auto is_left = std::is_same::value; + + typename Extents::index_type stride = 1; + + for (std::size_t r = 0; r < N; r++) { + const std::size_t s = is_left ? r : N - 1 - r; + + MDSPAN_IMPL_PRECONDITION(common_integral_compare(stride, other.stride(s)) + and "invalid strides for layout_{left,right}"); + + stride *= ext.extent(s); + } +} + +} // namespace detail +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_stride.hpp +#if MDSPAN_HAS_CXX_17 +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded_fwd.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { + +template +struct layout_left_padded { + template + class mapping; +}; + +template +struct layout_right_padded { + template + class mapping; +}; + +namespace detail { +// The layout_padded_constants structs are only useful if rank > 1, otherwise they may wrap +template +struct layout_padded_constants; + +template +struct layout_padded_constants, _ExtentsType> +{ + using rank_type = typename _ExtentsType::rank_type; + static constexpr rank_type padded_stride_idx = 1; + static constexpr rank_type extent_to_pad_idx = 0; +}; + +template +struct layout_padded_constants, _ExtentsType> +{ + using rank_type = typename _ExtentsType::rank_type; + static constexpr rank_type padded_stride_idx = _ExtentsType::rank() - 2; + static constexpr rank_type extent_to_pad_idx = _ExtentsType::rank() - 1; +}; + +template +struct is_layout_left_padded : std::false_type {}; + +template +struct is_layout_left_padded> : std::true_type {}; + +template +struct is_layout_left_padded_mapping : std::false_type {}; + +template +struct is_layout_left_padded_mapping<_Mapping, + std::enable_if_t::template mapping>::value>> + : std::true_type {}; + +template +struct is_layout_right_padded : std::false_type {}; + +template +struct is_layout_right_padded> : std::true_type {}; + +template +struct is_layout_right_padded_mapping : std::false_type {}; + +template +struct is_layout_right_padded_mapping<_Mapping, + std::enable_if_t::template mapping>::value>> + : std::true_type {}; + + +template +constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<0>) {} + +template +constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<1>) {} + +template +constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank) +{ + using extents_type = typename _PaddedLayoutMappingType::extents_type; + constexpr auto padding_value = _PaddedLayoutMappingType::padding_value; + constexpr auto idx = layout_padded_constants::extent_to_pad_idx; + + constexpr auto statically_determinable = + (_LayoutExtentsType::static_extent(idx) != dynamic_extent) && + (extents_type::static_extent(idx) != dynamic_extent) && + (padding_value != dynamic_extent); + + static_assert(not statically_determinable or + (padding_value == 0 + ? _LayoutExtentsType::static_extent(idx) == 0 + : _LayoutExtentsType::static_extent(idx) % padding_value == 0), + ""); +} + +template +constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<0>, + const _OtherMapping&) {} +template +constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<1>, + const _OtherMapping&) {} +template +constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank, + const _OtherMapping &other_mapping) { + constexpr auto padded_stride_idx = + layout_padded_constants::padded_stride_idx; + constexpr auto extent_to_pad_idx = layout_padded_constants::extent_to_pad_idx; + MDSPAN_IMPL_PRECONDITION(other_mapping.stride(padded_stride_idx) == other_mapping.extents().extent(extent_to_pad_idx)); +} + + +} +} +} +//END_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded_fwd.hpp +#endif + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +//============================================================================== +template +class layout_right::mapping { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_right; + private: + + static_assert(detail::__is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_right::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + + template + friend class mapping; + + // i0+(i1 + E(1)*(i2 + E(2)*i3)) + template + struct __rank_count {}; + + template + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset( + index_type offset, __rank_count, const I& i, Indices... idx) const { + return __compute_offset(offset * __extents.extent(r) + i,__rank_count(), idx...); + } + + template + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset( + __rank_count<0,extents_type::rank()>, const I& i, Indices... idx) const { + return __compute_offset(i,__rank_count<1,extents_type::rank()>(),idx...); + } + + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset(size_t offset, __rank_count) const { + return static_cast(offset); + } + + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset(__rank_count<0,0>) const { return 0; } + + public: + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; + + _MDSPAN_HOST_DEVICE + constexpr mapping(extents_type const& __exts) noexcept + :__extents(__exts) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) && + (extents_type::rank() <= 1) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(layout_left::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + + /** + * Converting constructor from `layout_right_padded::mapping`. + * + * This overload participates in overload resolution only if _Mapping is a layout_right_padded mapping and + * extents_type is constructible from _Mapping::extents_type. + * + * \note There is currently a difference from p2642r2, where this function is specified as taking + * `layout_right_padded< padding_value >::mapping< Extents>`. However, this makes `padding_value` non-deducible. + */ +#if MDSPAN_HAS_CXX_17 + MDSPAN_TEMPLATE_REQUIRES( + class _Mapping, + /* requires */ ( + MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::is_layout_right_padded_mapping<_Mapping>::value + && std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible_v)) + mapping(const _Mapping &__other) noexcept + : __extents(__other.extents()) + { + MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: + check_padded_layout_converting_constructor_mandates< + extents_type, _Mapping>(detail::with_rank{}); + MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: + check_padded_layout_converting_constructor_preconditions< + extents_type>(detail::with_rank{}, __other); + } +#endif + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(layout_stride::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + detail::validate_strides(detail::with_rank{}, layout_right{}, __extents, other); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default; + + MDSPAN_INLINE_FUNCTION + constexpr const extents_type& extents() const noexcept { + return __extents; + } + + MDSPAN_INLINE_FUNCTION + constexpr index_type required_span_size() const noexcept { + index_type value = 1; + for(rank_type r=0; r != extents_type::rank(); ++r) value*=__extents.extent(r); + return value; + } + + //-------------------------------------------------------------------------------- + + MDSPAN_TEMPLATE_REQUIRES( + class ... Indices, + /* requires */ ( + (sizeof...(Indices) == extents_type::rank()) && + (detail::are_valid_indices()) + ) + ) + _MDSPAN_HOST_DEVICE + constexpr index_type operator()(Indices... idxs) const noexcept { +#if ! defined(NDEBUG) + detail::check_all_indices(this->extents(), idxs...); +#endif // ! NDEBUG + return __compute_offset(__rank_count<0, extents_type::rank()>(), static_cast(idxs)...); + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_exhaustive() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } + + MDSPAN_INLINE_FUNCTION + constexpr index_type stride(rank_type i) const noexcept +#if MDSPAN_HAS_CXX_20 + requires ( Extents::rank() > 0 ) +#endif + { + index_type value = 1; + for(rank_type r=extents_type::rank()-1; r>i; r--) value*=__extents.extent(r); + return value; + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( Extents::rank() == OtherExtents::rank()) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator==(mapping const& lhs, mapping const& rhs) noexcept { + return lhs.extents() == rhs.extents(); + } + + // In C++ 20 the not equal exists if equal is found +#if !(MDSPAN_HAS_CXX_20) + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ (Extents::rank() == OtherExtents::rank()) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { + return lhs.extents() != rhs.extents(); + } +#endif + + // Not really public, but currently needed to implement fully constexpr useable submdspan: + template + constexpr index_type __get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { + return _MDSPAN_FOLD_TIMES_RIGHT((Idx>N? __extents.template __extent():1),1); + } + template + constexpr index_type __stride() const noexcept { + return __get_stride(__extents, std::make_index_sequence()); + } + +private: + _MDSPAN_NO_UNIQUE_ADDRESS extents_type __extents{}; + + // [mdspan.submdspan.mapping], submdspan mapping specialization + template + MDSPAN_INLINE_FUNCTION + constexpr auto submdspan_mapping_impl( + SliceSpecifiers... slices) const; + + template + friend constexpr auto submdspan_mapping( + const mapping& src, SliceSpecifiers... slices) { + return src.submdspan_mapping_impl(slices...); + } +}; + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_right.hpp + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +template < + class ElementType, + class Extents, + class LayoutPolicy = layout_right, + class AccessorPolicy = default_accessor +> +class mdspan +{ +private: + static_assert(detail::__is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::mdspan's Extents template parameter must be a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + static_assert(std::is_same::value, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::mdspan's ElementType template parameter must be the same as its AccessorPolicy::element_type."); + + // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level + template + struct __deduction_workaround; + + template + struct __deduction_workaround> + { + MDSPAN_FORCE_INLINE_FUNCTION static constexpr + size_t __size(mdspan const& __self) noexcept { + return _MDSPAN_FOLD_TIMES_RIGHT((__self.__mapping_ref().extents().extent(Idxs)), /* * ... * */ size_t(1)); + } + MDSPAN_FORCE_INLINE_FUNCTION static constexpr + bool __empty(mdspan const& __self) noexcept { + return (__self.rank()>0) && _MDSPAN_FOLD_OR((__self.__mapping_ref().extents().extent(Idxs)==index_type(0))); + } + template + MDSPAN_FORCE_INLINE_FUNCTION static constexpr + ReferenceType __callop(mdspan const& __self, const std::array& indices) noexcept { + return __self.__accessor_ref().access(__self.__ptr_ref(), __self.__mapping_ref()(indices[Idxs]...)); + } +#ifdef __cpp_lib_span + template + MDSPAN_FORCE_INLINE_FUNCTION static constexpr + ReferenceType __callop(mdspan const& __self, const std::span& indices) noexcept { + return __self.__accessor_ref().access(__self.__ptr_ref(), __self.__mapping_ref()(indices[Idxs]...)); + } +#endif + }; + +public: + + //-------------------------------------------------------------------------------- + // Domain and codomain types + + using extents_type = Extents; + using layout_type = LayoutPolicy; + using accessor_type = AccessorPolicy; + using mapping_type = typename layout_type::template mapping; + using element_type = ElementType; + using value_type = std::remove_cv_t; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using data_handle_type = typename accessor_type::data_handle_type; + using reference = typename accessor_type::reference; + + MDSPAN_INLINE_FUNCTION static constexpr size_t rank() noexcept { return extents_type::rank(); } + MDSPAN_INLINE_FUNCTION static constexpr size_t rank_dynamic() noexcept { return extents_type::rank_dynamic(); } + MDSPAN_INLINE_FUNCTION static constexpr size_t static_extent(size_t r) noexcept { return extents_type::static_extent(r); } + MDSPAN_INLINE_FUNCTION constexpr index_type extent(size_t r) const noexcept { return __mapping_ref().extents().extent(r); }; + +private: + + // Can't use defaulted parameter in the __deduction_workaround template because of a bug in MSVC warning C4348. + using __impl = __deduction_workaround>; + + using __map_acc_pair_t = detail::__compressed_pair; + +public: + + //-------------------------------------------------------------------------------- + // [mdspan.basic.cons], mdspan constructors, assignment, and destructor + +#if !MDSPAN_HAS_CXX_20 + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan() = default; +#else + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan() + requires( + // nvhpc has a bug where using just rank_dynamic() here doesn't work ... + (extents_type::rank_dynamic() > 0) && + _MDSPAN_TRAIT(std::is_default_constructible, data_handle_type) && + _MDSPAN_TRAIT(std::is_default_constructible, mapping_type) && + _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) + ) = default; +#endif + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(const mdspan&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(mdspan&&) = default; + + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + ((sizeof...(SizeTypes) == rank()) || (sizeof...(SizeTypes) == rank_dynamic())) && + (detail::are_valid_indices()) && + _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && + _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) + ) + ) + MDSPAN_INLINE_FUNCTION + explicit constexpr mdspan(data_handle_type p, SizeTypes... dynamic_extents) + // TODO @proposal-bug shouldn't I be allowed to do `move(p)` here? + : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(static_cast(std::move(dynamic_extents))...)), accessor_type())) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) && + ((N == rank()) || (N == rank_dynamic())) && + _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && + _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic()) + MDSPAN_INLINE_FUNCTION + constexpr mdspan(data_handle_type p, const std::array& dynamic_extents) + : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(dynamic_extents)), accessor_type())) + { } + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) && + ((N == rank()) || (N == rank_dynamic())) && + _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && + _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic()) + MDSPAN_INLINE_FUNCTION + constexpr mdspan(data_handle_type p, std::span dynamic_extents) + : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(as_const(dynamic_extents))), accessor_type())) + { } +#endif + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdspan, (data_handle_type p, const extents_type& exts), , + /* requires */ (_MDSPAN_TRAIT(std::is_default_constructible, accessor_type) && + _MDSPAN_TRAIT(std::is_constructible, mapping_type, const extents_type&)) + ) : __members(std::move(p), __map_acc_pair_t(mapping_type(exts), accessor_type())) + { } + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdspan, (data_handle_type p, const mapping_type& m), , + /* requires */ (_MDSPAN_TRAIT(std::is_default_constructible, accessor_type)) + ) : __members(std::move(p), __map_acc_pair_t(m, accessor_type())) + { } + + MDSPAN_INLINE_FUNCTION + constexpr mdspan(data_handle_type p, const mapping_type& m, const accessor_type& a) + : __members(std::move(p), __map_acc_pair_t(m, a)) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherAccessor, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, mapping_type, const typename OtherLayoutPolicy::template mapping&) && + _MDSPAN_TRAIT(std::is_constructible, accessor_type, const OtherAccessor&) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT( + !_MDSPAN_TRAIT(std::is_convertible, const typename OtherLayoutPolicy::template mapping&, mapping_type) || + !_MDSPAN_TRAIT(std::is_convertible, const OtherAccessor&, accessor_type) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdspan(const mdspan& other) + : __members(other.__ptr_ref(), __map_acc_pair_t(other.__mapping_ref(), other.__accessor_ref())) + { + static_assert(_MDSPAN_TRAIT(std::is_constructible, data_handle_type, typename OtherAccessor::data_handle_type),"Incompatible data_handle_type for mdspan construction"); + static_assert(_MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents),"Incompatible extents for mdspan construction"); + /* + * TODO: Check precondition + * For each rank index r of extents_type, static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r) is true. + */ + } + + /* Might need this on NVIDIA? + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~mdspan() = default; + */ + + MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mdspan& operator=(const mdspan&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mdspan& operator=(mdspan&&) = default; + + + //-------------------------------------------------------------------------------- + // [mdspan.basic.mapping], mdspan mapping domain multidimensional index to access codomain element + + #if MDSPAN_USE_BRACKET_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, SizeTypes, index_type) /* && ... */) && + _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeTypes) /* && ... */) && + (rank() == sizeof...(SizeTypes)) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](SizeTypes... indices) const + { + return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(indices))...)); + } + #endif + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](const std::array< SizeType, rank()>& indices) const + { + return __impl::template __callop(*this, indices); + } + + #ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](std::span indices) const + { + return __impl::template __callop(*this, indices); + } + #endif // __cpp_lib_span + + #if !MDSPAN_USE_BRACKET_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class Index, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, Index, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, Index) && + extents_type::rank() == 1 + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](Index idx) const + { + return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(idx)))); + } + #endif + + #if MDSPAN_USE_PAREN_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + extents_type::rank() == sizeof...(SizeTypes) && + (detail::are_valid_indices()) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(SizeTypes... indices) const + { + return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(indices))...)); + } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(const std::array& indices) const + { + return __impl::template __callop(*this, indices); + } + + #ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && + _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(std::span indices) const + { + return __impl::template __callop(*this, indices); + } + #endif // __cpp_lib_span + #endif // MDSPAN_USE_PAREN_OPERATOR + + MDSPAN_INLINE_FUNCTION constexpr size_type size() const noexcept { + return __impl::__size(*this); + }; + + MDSPAN_INLINE_FUNCTION constexpr bool empty() const noexcept { + return __impl::__empty(*this); + }; + + MDSPAN_INLINE_FUNCTION + friend constexpr void swap(mdspan& x, mdspan& y) noexcept { + // can't call the std::swap inside on HIP + #if !defined(_MDSPAN_HAS_HIP) && !defined(_MDSPAN_HAS_CUDA) + using std::swap; + swap(x.__ptr_ref(), y.__ptr_ref()); + swap(x.__mapping_ref(), y.__mapping_ref()); + swap(x.__accessor_ref(), y.__accessor_ref()); + #else + mdspan tmp = y; + y = x; + x = tmp; + #endif + } + + //-------------------------------------------------------------------------------- + // [mdspan.basic.domobs], mdspan observers of the domain multidimensional index space + + + MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { return __mapping_ref().extents(); }; + MDSPAN_INLINE_FUNCTION constexpr const data_handle_type& data_handle() const noexcept { return __ptr_ref(); }; + MDSPAN_INLINE_FUNCTION constexpr const mapping_type& mapping() const noexcept { return __mapping_ref(); }; + MDSPAN_INLINE_FUNCTION constexpr const accessor_type& accessor() const noexcept { return __accessor_ref(); }; + + //-------------------------------------------------------------------------------- + // [mdspan.basic.obs], mdspan observers of the mapping + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() { return mapping_type::is_always_unique(); }; + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() { return mapping_type::is_always_exhaustive(); }; + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() { return mapping_type::is_always_strided(); }; + + MDSPAN_INLINE_FUNCTION constexpr bool is_unique() const { return __mapping_ref().is_unique(); }; + MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const { return __mapping_ref().is_exhaustive(); }; + MDSPAN_INLINE_FUNCTION constexpr bool is_strided() const { return __mapping_ref().is_strided(); }; + MDSPAN_INLINE_FUNCTION constexpr index_type stride(size_t r) const { return __mapping_ref().stride(r); }; + +private: + + detail::__compressed_pair __members{}; + + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 data_handle_type& __ptr_ref() noexcept { return __members.__first(); } + MDSPAN_FORCE_INLINE_FUNCTION constexpr data_handle_type const& __ptr_ref() const noexcept { return __members.__first(); } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 mapping_type& __mapping_ref() noexcept { return __members.__second().__first(); } + MDSPAN_FORCE_INLINE_FUNCTION constexpr mapping_type const& __mapping_ref() const noexcept { return __members.__second().__first(); } + MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 accessor_type& __accessor_ref() noexcept { return __members.__second().__second(); } + MDSPAN_FORCE_INLINE_FUNCTION constexpr accessor_type const& __accessor_ref() const noexcept { return __members.__second().__second(); } + + template + friend class mdspan; + +}; + +#if defined(_MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) +MDSPAN_TEMPLATE_REQUIRES( + class ElementType, class... SizeTypes, + /* requires */ _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, SizeTypes, size_t) /* && ... */) && + (sizeof...(SizeTypes) > 0) +) +MDSPAN_DEDUCTION_GUIDE explicit mdspan(ElementType*, SizeTypes...) + -> mdspan>; + +MDSPAN_TEMPLATE_REQUIRES( + class Pointer, + (_MDSPAN_TRAIT(std::is_pointer, std::remove_reference_t)) +) +MDSPAN_DEDUCTION_GUIDE mdspan(Pointer&&) -> mdspan>, extents>; + +MDSPAN_TEMPLATE_REQUIRES( + class CArray, + (_MDSPAN_TRAIT(std::is_array, CArray) && (std::rank_v == 1)) +) +MDSPAN_DEDUCTION_GUIDE mdspan(CArray&) -> mdspan, extents>>; + +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const ::std::array&) + -> mdspan>; + +#ifdef __cpp_lib_span +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, ::std::span) + -> mdspan>; +#endif + +// This one is necessary because all the constructors take `data_handle_type`s, not +// `ElementType*`s, and `data_handle_type` is taken from `accessor_type::data_handle_type`, which +// seems to throw off automatic deduction guides. +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const extents&) + -> mdspan>; + +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const MappingType&) + -> mdspan; + +template +MDSPAN_DEDUCTION_GUIDE mdspan(const typename AccessorType::data_handle_type, const MappingType&, const AccessorType&) + -> mdspan; +#endif + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/mdspan.hpp +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_left.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +#if MDSPAN_HAS_CXX_17 +#endif +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +//============================================================================== + +template +class layout_left::mapping { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_left; + private: + + static_assert(detail::__is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_left::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + + template + friend class mapping; + + // i0+(i1 + E(1)*(i2 + E(2)*i3)) + template + struct __rank_count {}; + + template + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset( + __rank_count, const I& i, Indices... idx) const { + return __compute_offset(__rank_count(), idx...) * + __extents.extent(r) + i; + } + + template + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset( + __rank_count, const I& i) const { + return i; + } + + _MDSPAN_HOST_DEVICE + constexpr index_type __compute_offset(__rank_count<0,0>) const { return 0; } + + public: + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; + + _MDSPAN_HOST_DEVICE + constexpr mapping(extents_type const& __exts) noexcept + :__extents(__exts) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) && + (extents_type::rank() <= 1) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(layout_right::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + +#if MDSPAN_HAS_CXX_17 + /** + * Converting constructor from `layout_left_padded::mapping`. + * + * This overload participates in overload resolution only if _Mapping is a layout_left_padded mapping and + * extents_type is constructible from _Mapping::extents_type. + * + * \note There is currently a difference from p2642r2, where this function is specified as taking + * `layout_left_padded< padding_value >::mapping< Extents>`. However, this makes `padding_value` non-deducible. + */ + MDSPAN_TEMPLATE_REQUIRES( + class _Mapping, + /* requires */ ( + MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::is_layout_left_padded_mapping<_Mapping>::value + && std::is_constructible_v + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible_v)) + mapping(const _Mapping& __other) noexcept + : __extents(__other.extents()) + { + MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: + check_padded_layout_converting_constructor_mandates< + extents_type, _Mapping>(detail::with_rank{}); + MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: + check_padded_layout_converting_constructor_preconditions< + extents_type>(detail::with_rank{}, __other); + } +#endif + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) + MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 + mapping(layout_stride::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :__extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + detail::validate_strides(detail::with_rank{}, layout_left{}, __extents, other); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default; + + MDSPAN_INLINE_FUNCTION + constexpr const extents_type& extents() const noexcept { + return __extents; + } + + MDSPAN_INLINE_FUNCTION + constexpr index_type required_span_size() const noexcept { + index_type value = 1; + for(rank_type r=0; r()) + ) + ) + _MDSPAN_HOST_DEVICE + constexpr index_type operator()(Indices... idxs) const noexcept { +#if ! defined(NDEBUG) + detail::check_all_indices(this->extents(), idxs...); +#endif // ! NDEBUG + return __compute_offset(__rank_count<0, extents_type::rank()>(), static_cast(idxs)...); + } + + + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_exhaustive() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } + + MDSPAN_INLINE_FUNCTION + constexpr index_type stride(rank_type i) const noexcept +#if MDSPAN_HAS_CXX_20 + requires ( Extents::rank() > 0 ) +#endif + { + index_type value = 1; + for(rank_type r=0; r const& rhs) noexcept { + return lhs.extents() == rhs.extents(); + } + + // In C++ 20 the not equal exists if equal is found +#if !(MDSPAN_HAS_CXX_20) + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( Extents::rank() == OtherExtents::rank()) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { + return lhs.extents() != rhs.extents(); + } +#endif + + // Not really public, but currently needed to implement fully constexpr useable submdspan: + template + constexpr index_type __get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { + return _MDSPAN_FOLD_TIMES_RIGHT((Idx():1),1); + } + template + constexpr index_type __stride() const noexcept { + return __get_stride(__extents, std::make_index_sequence()); + } + +private: + _MDSPAN_NO_UNIQUE_ADDRESS extents_type __extents{}; + + // [mdspan.submdspan.mapping], submdspan mapping specialization + template + MDSPAN_INLINE_FUNCTION + constexpr auto submdspan_mapping_impl( + SliceSpecifiers... slices) const; + + template + friend constexpr auto submdspan_mapping( + const mapping& src, SliceSpecifiers... slices) { + return src.submdspan_mapping_impl(slices...); + } +}; + + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_left.hpp +#if MDSPAN_HAS_CXX_17 +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { + +namespace detail { +template +MDSPAN_INLINE_FUNCTION +constexpr _T +find_next_multiple(_T alignment, _T offset) +{ + if ( alignment == 0 ) { + return _T(0); + } else { + return ( ( offset + alignment - 1 ) / alignment) * alignment; + } +} + +template +MDSPAN_INLINE_FUNCTION constexpr size_t get_actual_static_padding_value() { + constexpr auto rank = _ExtentsType::rank(); + + if constexpr (rank <= typename _ExtentsType::rank_type(1)) { + return 0; + } else if constexpr (_PaddingValue != dynamic_extent && + _ExtentsType::static_extent(_ExtentToPadIdx) != + dynamic_extent) { + static_assert( + (_PaddingValue != 0) || + (_ExtentsType::static_extent(_ExtentToPadIdx) == 0), + "padding stride can be 0 only if " + "extents_type::static_extent(extent-to-pad) is 0 or dynamic_extent"); + return find_next_multiple(_PaddingValue, + _ExtentsType::static_extent(_ExtentToPadIdx)); + } else { + return dynamic_extent; + } + // Missing return statement warning from NVCC +#ifdef __NVCC__ + return 0; +#endif +} + +template +struct static_array_type_for_padded_extent +{ + static constexpr size_t padding_value = _PaddingValue; + using index_type = typename _Extents::index_type; + using extents_type = _Extents; + using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::maybe_static_array< + index_type, size_t, dynamic_extent, + detail::get_actual_static_padding_value()>; +}; + +template +struct static_array_type_for_padded_extent<_PaddingValue, _Extents, + _ExtentToPadIdx, Rank, std::enable_if_t> { + using index_type = typename _Extents::index_type; + using extents_type = _Extents; + using type = + ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::maybe_static_array< + index_type, size_t, dynamic_extent, 0>; +}; + +template +struct padded_extent { + static constexpr size_t padding_value = _PaddingValue; + using index_type = typename _Extents::index_type; + using extents_type = _Extents; + using static_array_type = typename static_array_type_for_padded_extent< + padding_value, _Extents, _ExtentToPadIdx, _Extents::rank()>::type; + + static constexpr auto static_value() { return static_array_type::static_value(0); } + + MDSPAN_INLINE_FUNCTION + static constexpr static_array_type + init_padding(const _Extents &exts) { + if constexpr ((_Extents::rank() > 1) && (padding_value == dynamic_extent)) { + return {exts.extent(_ExtentToPadIdx)}; + } else { + return init_padding(exts, padding_value); + } + // Missing return statement warning from NVCC +#ifdef __NVCC__ + return {}; +#endif + } + + MDSPAN_INLINE_FUNCTION static constexpr static_array_type + init_padding([[maybe_unused]] const _Extents &exts, + [[maybe_unused]] index_type pv) { + if constexpr (_Extents::rank() > 1) { + return {find_next_multiple(pv, + exts.extent(_ExtentToPadIdx))}; + } else { + return {}; + } + // Missing return statement warning from NVCC +#ifdef __NVCC__ + return {}; +#endif + } + + template + MDSPAN_INLINE_FUNCTION static constexpr static_array_type + init_padding([[maybe_unused]] const _Mapping &other_mapping, + std::integral_constant) { + if constexpr (_Extents::rank() > 1) { + return {other_mapping.stride(_PaddingStrideIdx)}; + } else { + return {}; + } + // Missing return statement warning from NVCC +#ifdef __NVCC__ + return {}; +#endif + } +}; +} // namespace detail + +template +template +class layout_left_padded::mapping { +public: + static constexpr size_t padding_value = PaddingValue; + + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_left_padded; + +#ifndef MDSPAN_INTERNAL_TEST +private: +#endif // MDSPAN_INTERNAL_TEST + + static constexpr rank_type padded_stride_idx = detail::layout_padded_constants::padded_stride_idx; + static constexpr rank_type extent_to_pad_idx = detail::layout_padded_constants::extent_to_pad_idx; + + static_assert((padding_value != 0) + || (extents_type::static_extent(extent_to_pad_idx) == 0) + || (extents_type::static_extent(extent_to_pad_idx) == dynamic_extent), + "out of bounds access for rank 0"); + + using padded_stride_type = detail::padded_extent< padding_value, extents_type, extent_to_pad_idx >; + + static constexpr size_t static_padding_stride = padded_stride_type::static_value(); + + typename padded_stride_type::static_array_type padded_stride = {}; + extents_type exts = {}; + + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence<>) const { + return 0; + } + + template + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence, IndexOffset index_offset) const { + return index_offset; + } + + template + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence, + IndexOffsets... index_offsets) const { + index_type indices[] = {static_cast(index_offsets)...}; + // self-recursive fold trick from + // https://github.com/llvm/llvm-project/blob/96e1914aa2e6d8966acbfbe2f4d184201f1aa318/libcxx/include/mdspan/layout_left.h#L144 + index_type res = 0; + ((res = indices[extents_type::rank() - 1 - Ranks] + + ((extents_type::rank() - 1 - Ranks) == extent_to_pad_idx + ? padded_stride.value(0) + : exts.extent(extents_type::rank() - 1 - Ranks)) * + res), + ...); + return res; + } + +public: +#if !MDSPAN_HAS_CXX_20 + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr mapping() + : mapping(extents_type{}) + {} +#else + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr mapping() + requires(static_padding_stride != dynamic_extent) = default; + + MDSPAN_INLINE_FUNCTION + constexpr mapping() + requires(static_padding_stride == dynamic_extent) + : mapping(extents_type{}) + {} +#endif + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(const mapping&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED mapping& operator=(const mapping&) noexcept = default; + + /** + * Initializes the mapping with the given extents. + * + * \param ext the given extents + */ + MDSPAN_INLINE_FUNCTION + constexpr mapping(const extents_type& ext) + : padded_stride(padded_stride_type::init_padding(ext)), exts(ext) + {} + + /** + * Initializes the mapping with the given extents and the specified padding value. + * + * This overload participates in overload resolution only if `is_convertible_v` + * is `true` and `is_nothrow_constructible_v` is `true` + * + * \param ext the given extents + * \param padding_value the padding value + */ + MDSPAN_TEMPLATE_REQUIRES( + class _Size, + /* requires */ ( + std::is_convertible_v<_Size, index_type> + && std::is_nothrow_constructible_v + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const extents_type &ext, _Size dynamic_padding_value) + : padded_stride(padded_stride_type::init_padding(ext, dynamic_padding_value)), exts(ext) + { + assert((padding_value == dynamic_extent) || (static_cast(padding_value) == static_cast(dynamic_padding_value))); + } + + /** + * Converting constructor from `layout_left::mapping`. + * + * This overload participates in overload resolution only if + * `is_constructible_v` is true. If + * `OtherExtents::rank() > 1` then one of `padding_value`, `static_extent(0)`, + * or `OtherExtents::static_extent(0)` must be `dynamic_extent`; otherwise, + * `OtherExtents::static_extent(0)` must be equal to the least multiple of + * `padding_value` greater than or equal to `extents_type::static_extent(0)` + */ + MDSPAN_TEMPLATE_REQUIRES( + class _OtherExtents, + /* requires */ (std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT( + (!std::is_convertible_v<_OtherExtents, extents_type>)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const layout_left::mapping<_OtherExtents> &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) { + static_assert( + (_OtherExtents::rank() > 1) || + (static_padding_stride != dynamic_extent) || + (_OtherExtents::static_extent(extent_to_pad_idx) != dynamic_extent) || + (static_padding_stride == + _OtherExtents::static_extent(extent_to_pad_idx))); + } + + /** + * Converting constructor from `layout_stride::mapping`. + * + * This overload participates in overload resolution only if + * `is_constructible_v` is true + */ + MDSPAN_TEMPLATE_REQUIRES( + class _OtherExtents, + /* requires */ (std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const layout_stride::mapping<_OtherExtents> &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) {} + + /** + * Converting constructor from `layout_left_padded::mapping`. + * + * This overload participates in overload resolution only if + * `is_constructible_v` is true. Either + * `padding_value` or `OtherPaddingStride` must be `std::dynamic_extent`, or + * `padding_value == OtherPaddingStride`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class _Mapping, + /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value + &&std::is_constructible_v< + extents_type, typename _Mapping::extents_type>)) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 1 && + (padding_value == dynamic_extent || + _Mapping::padding_value == dynamic_extent))) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const _Mapping &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) { + static_assert(padding_value == dynamic_extent || + _Mapping::padding_value == dynamic_extent || + padding_value == _Mapping::padding_value); + } + + /** + * Converting constructor from `layout_right_padded::mapping`. + * + * This overload participates in overload resolution only if + * `extents_type::rank()` is 0 or 1 and `is_constructible_v` is `true`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class _Mapping, + /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value + &&extents_type::rank() <= 1 && + std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT( + (!std::is_convertible_v)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const _Mapping &other_mapping) noexcept + : padded_stride(padded_stride_type::init_padding( + other_mapping.extents(), + other_mapping.extents().extent(extent_to_pad_idx))), + exts(other_mapping.extents()) {} + + MDSPAN_INLINE_FUNCTION constexpr const extents_type & + extents() const noexcept { + return exts; + } + + MDSPAN_INLINE_FUNCTION constexpr std::array + strides() const noexcept { + if constexpr (extents_type::rank() == 0) { + return {}; + } else if constexpr (extents_type::rank() == 1) { + return {1}; + } else { + index_type value = 1; + std::array s{}; + s[extent_to_pad_idx] = value; + value *= padded_stride.value(0); + for (rank_type r = extent_to_pad_idx + 1; r < extents_type::rank() - 1; + ++r) { + s[r] = value; + value *= exts.extent(r); + } + s[extents_type::rank() - 1] = value; + return s; + } + } + + MDSPAN_INLINE_FUNCTION constexpr index_type + required_span_size() const noexcept { + if constexpr (extents_type::rank() == 0) { + return 1; + } else if constexpr (extents_type::rank() == 1) { + return exts.extent(0); + } else { + index_type value = padded_stride.value(0); + for (rank_type r = 1; r < extents_type::rank(); ++r) { + value *= exts.extent(r); + } + return value; + } + } + + /** + * Return the mapping given the provided indices per rank. + * + * This overload participates in overload resolution only if: + * - `sizeof...(Indices) == extents_type::rank()`, + * - `(is_convertible_v && ...) is true`, and + * - (is_nothrow_constructible_v && ...) is true. + */ + MDSPAN_TEMPLATE_REQUIRES( + class... _Indices, + /* requires */ (sizeof...(_Indices) == extents_type::rank() && + (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail:: + are_valid_indices()))) + MDSPAN_INLINE_FUNCTION constexpr size_t + operator()(_Indices... idxs) const noexcept { +#if !defined(NDEBUG) + ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::check_all_indices(this->extents(), + idxs...); +#endif // ! NDEBUG + return compute_offset(std::index_sequence_for<_Indices...>{}, idxs...); + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { + return true; + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { + return (extents_type::rank() <= rank_type(1)) || + (extents_type::static_extent(extent_to_pad_idx) != dynamic_extent && + extents_type::static_extent(extent_to_pad_idx) == + padded_stride_type::static_value()); + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { + return true; + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { + return true; + } + MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { + return (extents_type::rank() < 2) || + (exts.extent(extent_to_pad_idx) == padded_stride.value(0)); + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { + return true; + } + + MDSPAN_INLINE_FUNCTION + constexpr index_type stride(rank_type r) const noexcept { + assert(r < extents_type::rank()); + if (r == 0) + return index_type(1); + + index_type value = padded_stride.value(0); + for (rank_type k = 1; k < r; k++) + value *= exts.extent(k); + + return value; + } + + /** + * Equality operator between `layout_left_padded`s + * + * This overload only participates in overload resolution if + * `OtherExtents::rank() == extents_type::rank()`. + * + * \note There is currently a difference from p2642r2, where this function is + * specified as taking `layout_left_padded< padding_value >::mapping< + * Extents>`. However, this makes `padding_value` non-deducible. + */ + MDSPAN_TEMPLATE_REQUIRES( + class _Mapping, + /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value && + (_Mapping::extents_type::rank() == extents_type::rank()))) + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator==(const mapping &left, const _Mapping &right) noexcept { + // Workaround for some compilers not short-circuiting properly with + // compile-time checks i.e. we can't access stride(_padding_stride_idx) of a + // rank 0 mapping + bool strides_equal = true; + if constexpr (extents_type::rank() > rank_type(1)) { + strides_equal = + left.stride(padded_stride_idx) == right.stride(padded_stride_idx); + } + return (left.extents() == right.extents()) && strides_equal; + } + +#if !MDSPAN_HAS_CXX_20 + /** + * Inequality operator between `layout_left_padded`s + * + * This overload only participates in overload resolution if + * `OtherExtents::rank() == extents_type::rank()`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class _Mapping, + /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value && + (_Mapping::extents_type::rank() == extents_type::rank()))) + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator!=(const mapping &left, const _Mapping &right) noexcept { + return !(left == right); + } +#endif +}; + +template +template +class layout_right_padded::mapping { +public: + static constexpr size_t padding_value = PaddingValue; + + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_right_padded; + +#ifndef MDSPAN_INTERNAL_TEST + private: +#endif // MDSPAN_INTERNAL_TEST + + static constexpr rank_type padded_stride_idx = detail::layout_padded_constants::padded_stride_idx; + static constexpr rank_type extent_to_pad_idx = detail::layout_padded_constants::extent_to_pad_idx; + + static_assert((padding_value != 0) + || (extents_type::static_extent(extent_to_pad_idx) == 0) + || (extents_type::static_extent(extent_to_pad_idx) == dynamic_extent), + "if padding stride is 0, static_extent(extent-to-pad-rank) must also be 0 or dynamic_extent"); + + using padded_stride_type = detail::padded_extent< padding_value, extents_type, extent_to_pad_idx >; + static constexpr size_t static_padding_stride = padded_stride_type::static_value(); + + typename padded_stride_type::static_array_type padded_stride = {}; + extents_type exts = {}; + + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence<>) const { + return 0; + } + + template + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence, IndexOffset index_offset) const { + return index_offset; + } + + template + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence, + IndexOffsets... index_offsets) const { + // self-recursive fold trick from + // https://github.com/llvm/llvm-project/blob/4d9771741d40cc9cfcccb6b033f43689d36b705a/libcxx/include/mdspan/layout_right.h#L141 + index_type res = 0; + ((res = static_cast(index_offsets) + + (Ranks == extent_to_pad_idx ? padded_stride.value(0) + : exts.extent(Ranks)) * + res), + ...); + return res; + } + +public: +#if !MDSPAN_HAS_CXX_20 + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr mapping() + : mapping(extents_type{}) + {} +#else + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr mapping() + requires(static_padding_stride != dynamic_extent) = default; + + MDSPAN_INLINE_FUNCTION + constexpr mapping() + requires(static_padding_stride == dynamic_extent) + : mapping(extents_type{}) + {} +#endif + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(const mapping&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED mapping& operator=(const mapping&) noexcept = default; + + /** + * Initializes the mapping with the given extents. + * + * \param ext the given extents + */ + MDSPAN_INLINE_FUNCTION + constexpr mapping(const extents_type &ext) + : padded_stride(padded_stride_type::init_padding(ext)), exts(ext) {} + + /** + * Initializes the mapping with the given extents and the specified padding value. + * + * This overload participates in overload resolution only if `is_convertible_v` + * is `true` and `is_nothrow_constructible_v` is `true` + * + * \param ext the given extents + * \param padding_value the padding value + */ + MDSPAN_TEMPLATE_REQUIRES( + class _Size, + /* requires */ ( + std::is_convertible_v<_Size, index_type> + && std::is_nothrow_constructible_v + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const extents_type &ext, _Size dynamic_padding_value) + : padded_stride(padded_stride_type::init_padding(ext, static_cast(dynamic_padding_value))), + exts(ext) { + assert((padding_value == dynamic_extent) || + (static_cast(padding_value) == static_cast(dynamic_padding_value))); + } + + /** + * Converting constructor from `layout_right::mapping`. + * + * This overload participates in overload resolution only if `is_constructible_v` is true. + * If `OtherExtents::rank() > 1` then one of `padding_value`, `static_extent(0)`, or `OtherExtents::static_extent(0)` must be `dynamic_extent`; + * otherwise, `OtherExtents::static_extent(0)` must be equal to the least multiple of `padding_value` greater than or equal to `extents_type::static_extent(0)` + */ + MDSPAN_TEMPLATE_REQUIRES( + class _OtherExtents, + /* requires */ (std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT( + (!std::is_convertible_v<_OtherExtents, extents_type>)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const layout_right::mapping<_OtherExtents> &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) { + static_assert( + (_OtherExtents::rank() > 1) || + (padded_stride_type::static_value() != dynamic_extent) || + (_OtherExtents::static_extent(extent_to_pad_idx) != dynamic_extent) || + (padded_stride_type::static_value() == + _OtherExtents::static_extent(extent_to_pad_idx))); + } + + /** + * Converting constructor from `layout_stride::mapping`. + * + * This overload participates in overload resolution only if + * `is_constructible_v` is true + */ + MDSPAN_TEMPLATE_REQUIRES( + class _OtherExtents, + /* requires */ (std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const layout_stride::mapping<_OtherExtents> &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) {} + + /** + * Converting constructor from `layout_right_padded::mapping`. + * + * This overload participates in overload resolution only if + * `is_constructible_v` is true. Either + * `padding_value` or `OtherPaddingStride` must be `std::dynamic_extent`, or + * `padding_value == OtherPaddingStride`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class _Mapping, + /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value + &&std::is_constructible_v< + extents_type, typename _Mapping::extents_type>)) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 1 && + (padding_value == dynamic_extent || + _Mapping::padding_value == dynamic_extent))) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const _Mapping &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) { + static_assert(padding_value == dynamic_extent || + _Mapping::padding_value == dynamic_extent || + padding_value == _Mapping::padding_value); + } + + /** + * Converting constructor from `layout_left_padded::mapping`. + * + * This overload participates in overload resolution only if + * `extents_type::rank()` is 0 or 1 and `is_constructible_v` is `true`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class _Mapping, + /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value + &&extents_type::rank() <= 1 && + std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT( + (!std::is_convertible_v)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const _Mapping &other_mapping) noexcept + : padded_stride(padded_stride_type::init_padding( + other_mapping.extents(), + other_mapping.extents().extent(extent_to_pad_idx))), + exts(other_mapping.extents()) {} + + MDSPAN_INLINE_FUNCTION constexpr const extents_type & + extents() const noexcept { + return exts; + } + + MDSPAN_INLINE_FUNCTION constexpr std::array + strides() const noexcept { + if constexpr (extents_type::rank() == 0) { + return {}; + } else if constexpr (extents_type::rank() == 1) { + return {1}; + } else { + index_type value = 1; + std::array s{}; + s[extent_to_pad_idx] = value; + value *= padded_stride.value(0); + for (rank_type r = extent_to_pad_idx - 1; r > 0; --r) { + s[r] = value; + value *= exts.extent(r); + } + s[0] = value; + return s; + } + } + + MDSPAN_INLINE_FUNCTION constexpr index_type + required_span_size() const noexcept { + if constexpr (extents_type::rank() == 0) { + return 1; + } else if constexpr (extents_type::rank() == 1) { + return exts.extent(0); + } else { + index_type value = 1; + for (rank_type r = 0; r < extent_to_pad_idx; ++r) { + value *= exts.extent(r); + } + return value * padded_stride.value(0); + } + } + + /** + * Return the mapping given the provided indices per rank. + * + * This overload participates in overload resolution only if: + * - `sizeof...(Indices) == extents_type::rank()`, + * - `(is_convertible_v && ...) is true`, and + * - (is_nothrow_constructible_v && ...) is true. + */ + MDSPAN_TEMPLATE_REQUIRES( + class... _Indices, + /* requires */ (sizeof...(_Indices) == extents_type::rank() && + (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail:: + are_valid_indices()))) + MDSPAN_INLINE_FUNCTION constexpr size_t + operator()(_Indices... idxs) const noexcept { + return compute_offset(std::index_sequence_for<_Indices...>{}, idxs...); + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { + return true; + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { + return (extents_type::rank() <= rank_type(1)) || + (extents_type::static_extent(extent_to_pad_idx) != dynamic_extent && + extents_type::static_extent(extent_to_pad_idx) == + padded_stride_type::static_value()); + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { + return true; + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { + return true; + } + MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { + return (extents_type::rank() < 2) || + (exts.extent(extent_to_pad_idx) == padded_stride.value(0)); + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { + return true; + } + + MDSPAN_INLINE_FUNCTION constexpr index_type + stride(rank_type r) const noexcept { + assert(r < extents_type::rank()); + if (r == extents_type::rank() - 1) + return index_type(1); + + index_type value = padded_stride.value(0); + for (rank_type k = extents_type::rank() - 2; k > r; k--) + value *= exts.extent(k); + + return value; + } + + /** + * Equality operator between `layout_right_padded`s + * + * This overload only participates in overload resolution if + * `OtherExtents::rank() == extents_type::rank()`. + * + * \note There is currently a difference from p2642r2, where this function is + * specified as taking `layout_right_padded< padding_value >::mapping< + * Extents>`. However, this makes `padding_value` non-deducible. + */ + MDSPAN_TEMPLATE_REQUIRES( + class _Mapping, + /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value && + (_Mapping::extents_type::rank() == extents_type::rank()))) + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator==(const mapping &left, const _Mapping &right) noexcept { + // Workaround for some compilers not short-circuiting properly with + // compile-time checks i.e. we can't access stride(_padding_stride_idx) of a + // rank 0 mapping + bool strides_equal = true; + if constexpr (extents_type::rank() > rank_type(1)) { + strides_equal = + left.stride(padded_stride_idx) == right.stride(padded_stride_idx); + } + return (left.extents() == right.extents()) && strides_equal; + } + +#if !MDSPAN_HAS_CXX_20 + /** + * Inequality operator between `layout_right_padded`s + * + * This overload only participates in overload resolution if + * `OtherExtents::rank() == extents_type::rank()`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class _Mapping, + /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value && + (_Mapping::extents_type::rank() == extents_type::rank()))) + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator!=(const mapping &left, const _Mapping &right) noexcept { + return !(left == right); + } +#endif +}; +} +} +//END_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded.hpp +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_extents.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#include + +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/strided_slice.hpp + +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +namespace { + template + struct __mdspan_is_integral_constant: std::false_type {}; + + template + struct __mdspan_is_integral_constant>: std::true_type {}; +} + +// Slice Specifier allowing for strides and compile time extent +template +struct strided_slice { + using offset_type = OffsetType; + using extent_type = ExtentType; + using stride_type = StrideType; + + _MDSPAN_NO_UNIQUE_ADDRESS OffsetType offset{}; + _MDSPAN_NO_UNIQUE_ADDRESS ExtentType extent{}; + _MDSPAN_NO_UNIQUE_ADDRESS StrideType stride{}; + + static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); + static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); + static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); +}; + +} // MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/strided_slice.hpp +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +// Mapping from submapping ranks to srcmapping ranks +// InvMapRank is an index_sequence, which we build recursively +// to contain the mapped indices. +// end of recursion specialization containing the final index_sequence +template +MDSPAN_INLINE_FUNCTION +constexpr auto inv_map_rank(std::integral_constant, std::index_sequence) { + return std::index_sequence(); +} + +// specialization reducing rank by one (i.e., integral slice specifier) +template +MDSPAN_INLINE_FUNCTION +constexpr auto inv_map_rank(std::integral_constant, std::index_sequence, Slice, + SliceSpecifiers... slices) { + using next_idx_seq_t = std::conditional_t, + std::index_sequence, + std::index_sequence>; + + return inv_map_rank(std::integral_constant(), next_idx_seq_t(), + slices...); +} + +// Helper for identifying strided_slice +template struct is_strided_slice : std::false_type {}; + +template +struct is_strided_slice< + strided_slice> : std::true_type {}; + +// first_of(slice): getting begin of slice specifier range +MDSPAN_TEMPLATE_REQUIRES( + class Integral, + /* requires */(std::is_convertible_v) +) +MDSPAN_INLINE_FUNCTION +constexpr Integral first_of(const Integral &i) { + return i; +} + +MDSPAN_INLINE_FUNCTION +constexpr std::integral_constant +first_of(const ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t &) { + return std::integral_constant(); +} + +MDSPAN_TEMPLATE_REQUIRES( + class Slice, + /* requires */(std::is_convertible_v>) +) +MDSPAN_INLINE_FUNCTION +constexpr auto first_of(const Slice &i) { + return std::get<0>(i); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr OffsetType +first_of(const strided_slice &r) { + return r.offset; +} + +// last_of(slice): getting end of slice specifier range +// We need however not just the slice but also the extents +// of the original view and which rank from the extents. +// This is needed in the case of slice being full_extent_t. +MDSPAN_TEMPLATE_REQUIRES( + size_t k, class Extents, class Integral, + /* requires */(std::is_convertible_v) +) +MDSPAN_INLINE_FUNCTION +constexpr Integral + last_of(std::integral_constant, const Extents &, const Integral &i) { + return i; +} + +MDSPAN_TEMPLATE_REQUIRES( + size_t k, class Extents, class Slice, + /* requires */(std::is_convertible_v>) +) +MDSPAN_INLINE_FUNCTION +constexpr auto last_of(std::integral_constant, const Extents &, + const Slice &i) { + return std::get<1>(i); +} + +// Suppress spurious warning with NVCC about no return statement. +// This is a known issue in NVCC and NVC++ +// Depending on the CUDA and GCC version we need both the builtin +// and the diagnostic push. I tried really hard to find something shorter +// but no luck ... +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic push + #pragma nv_diag_suppress = implicit_return_from_non_void_function + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic push + #pragma diag_suppress implicit_return_from_non_void_function + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic push + #pragma diag_suppress = implicit_return_from_non_void_function +#endif +template +MDSPAN_INLINE_FUNCTION +constexpr auto last_of(std::integral_constant, const Extents &ext, + ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t) { + if constexpr (Extents::static_extent(k) == dynamic_extent) { + return ext.extent(k); + } else { + return std::integral_constant(); + } +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + // Even with CUDA_ARCH protection this thing warns about calling host function + __builtin_unreachable(); +#endif +} +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic pop + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic pop + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic pop +#endif + +template +MDSPAN_INLINE_FUNCTION +constexpr OffsetType +last_of(std::integral_constant, const Extents &, + const strided_slice &r) { + return r.extent; +} + +// get stride of slices +template +MDSPAN_INLINE_FUNCTION +constexpr auto stride_of(const T &) { + return std::integral_constant(); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto +stride_of(const strided_slice &r) { + return r.stride; +} + +// divide which can deal with integral constant preservation +template +MDSPAN_INLINE_FUNCTION +constexpr auto divide(const T0 &v0, const T1 &v1) { + return IndexT(v0) / IndexT(v1); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto divide(const std::integral_constant &, + const std::integral_constant &) { + // cutting short division by zero + // this is used for strided_slice with zero extent/stride + return std::integral_constant(); +} + +// multiply which can deal with integral constant preservation +template +MDSPAN_INLINE_FUNCTION +constexpr auto multiply(const T0 &v0, const T1 &v1) { + return IndexT(v0) * IndexT(v1); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto multiply(const std::integral_constant &, + const std::integral_constant &) { + return std::integral_constant(); +} + +// compute new static extent from range, preserving static knowledge +template struct StaticExtentFromRange { + constexpr static size_t value = dynamic_extent; +}; + +template +struct StaticExtentFromRange, + std::integral_constant> { + constexpr static size_t value = val1 - val0; +}; + +// compute new static extent from strided_slice, preserving static +// knowledge +template struct StaticExtentFromStridedRange { + constexpr static size_t value = dynamic_extent; +}; + +template +struct StaticExtentFromStridedRange, + std::integral_constant> { + constexpr static size_t value = val0 > 0 ? 1 + (val0 - 1) / val1 : 0; +}; + +// creates new extents through recursive calls to next_extent member function +// next_extent has different overloads for different types of stride specifiers +template +struct extents_constructor { + MDSPAN_TEMPLATE_REQUIRES( + class Slice, class... SlicesAndExtents, + /* requires */(!std::is_convertible_v && + !is_strided_slice::value) + ) + MDSPAN_INLINE_FUNCTION + constexpr static auto next_extent(const Extents &ext, const Slice &sl, + SlicesAndExtents... slices_and_extents) { + constexpr size_t new_static_extent = StaticExtentFromRange< + decltype(first_of(std::declval())), + decltype(last_of(std::integral_constant(), + std::declval(), + std::declval()))>::value; + + using next_t = + extents_constructor; + using index_t = typename Extents::index_type; + return next_t::next_extent( + ext, slices_and_extents..., + index_t(last_of(std::integral_constant(), ext, + sl)) - + index_t(first_of(sl))); + } + + MDSPAN_TEMPLATE_REQUIRES( + class Slice, class... SlicesAndExtents, + /* requires */ (std::is_convertible_v) + ) + MDSPAN_INLINE_FUNCTION + constexpr static auto next_extent(const Extents &ext, const Slice &, + SlicesAndExtents... slices_and_extents) { + using next_t = extents_constructor; + return next_t::next_extent(ext, slices_and_extents...); + } + + template + MDSPAN_INLINE_FUNCTION + constexpr static auto + next_extent(const Extents &ext, + const strided_slice &r, + SlicesAndExtents... slices_and_extents) { + using index_t = typename Extents::index_type; + using new_static_extent_t = + StaticExtentFromStridedRange; + if constexpr (new_static_extent_t::value == dynamic_extent) { + using next_t = + extents_constructor; + return next_t::next_extent( + ext, slices_and_extents..., + r.extent > 0 ? 1 + divide(r.extent - 1, r.stride) : 0); + } else { + constexpr size_t new_static_extent = new_static_extent_t::value; + using next_t = + extents_constructor; + return next_t::next_extent( + ext, slices_and_extents..., index_t(divide(ExtentType(), StrideType()))); + } + } +}; + +template +struct extents_constructor<0, Extents, NewStaticExtents...> { + + template + MDSPAN_INLINE_FUNCTION + constexpr static auto next_extent(const Extents &, NewExtents... new_exts) { + return extents( + new_exts...); + } +}; + +} // namespace detail + +// submdspan_extents creates new extents given src extents and submdspan slice +// specifiers +template +MDSPAN_INLINE_FUNCTION +constexpr auto submdspan_extents(const extents &src_exts, + SliceSpecifiers... slices) { + + using ext_t = extents; + return detail::extents_constructor::next_extent( + src_exts, slices...); +} +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_extents.hpp +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_mapping.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +#include +#include +#include +#include // index_sequence + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +//****************************************** +// Return type of submdspan_mapping overloads +//****************************************** +template struct submdspan_mapping_result { + _MDSPAN_NO_UNIQUE_ADDRESS LayoutMapping mapping{}; + size_t offset; +}; + +namespace detail { + +// We use const Slice& and not Slice&& because the various +// submdspan_mapping_impl overloads use their slices arguments +// multiple times. This makes perfect forwarding not useful, but we +// still don't want to pass those (possibly of size 64 x 3 bits) +// objects by value. +template +MDSPAN_INLINE_FUNCTION +constexpr bool +one_slice_out_of_bounds(const IndexType& extent, const Slice& slice) +{ + using common_t = std::common_type_t; + return static_cast(detail::first_of(slice)) == static_cast(extent); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr bool +any_slice_out_of_bounds_helper(std::index_sequence, + const extents& exts, + const Slices& ... slices) +{ + return _MDSPAN_FOLD_OR( + (one_slice_out_of_bounds(exts.extent(RankIndices), slices)) + ); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr bool +any_slice_out_of_bounds(const extents& exts, + const Slices& ... slices) +{ + return any_slice_out_of_bounds_helper( + std::make_index_sequence(), + exts, slices...); +} + +// constructs sub strides +template +MDSPAN_INLINE_FUNCTION +constexpr auto +construct_sub_strides(const SrcMapping &src_mapping, + std::index_sequence, + const std::tuple &slices_stride_factor) { + using index_type = typename SrcMapping::index_type; + return std::array{ + (static_cast(src_mapping.stride(InvMapIdxs)) * + static_cast(std::get(slices_stride_factor)))...}; +} +} // namespace detail + +//********************************** +// layout_left submdspan_mapping +//********************************* +namespace detail { + +// Figure out whether to preserve layout_left +template +struct preserve_layout_left_mapping; + +template +struct preserve_layout_left_mapping, SubRank, + SliceSpecifiers...> { + constexpr static bool value = + // Preserve layout for rank 0 + (SubRank == 0) || + ( + // Slice specifiers up to subrank need to be full_extent_t - except + // for the last one which could also be tuple but not a strided index + // range slice specifiers after subrank are integrals + ((Idx > SubRank - 1) || // these are only integral slice specifiers + (std::is_same_v) || + ((Idx == SubRank - 1) && + std::is_convertible_v>)) && + ...); +}; +} // namespace detail + +// Suppress spurious warning with NVCC about no return statement. +// This is a known issue in NVCC and NVC++ +// Depending on the CUDA and GCC version we need both the builtin +// and the diagnostic push. I tried really hard to find something shorter +// but no luck ... +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic push + #pragma nv_diag_suppress = implicit_return_from_non_void_function + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic push + #pragma diag_suppress implicit_return_from_non_void_function + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic push + #pragma diag_suppress = implicit_return_from_non_void_function +#endif +// Actual submdspan mapping call +template +template +MDSPAN_INLINE_FUNCTION +constexpr auto +layout_left::mapping::submdspan_mapping_impl(SliceSpecifiers... slices) const { + + // compute sub extents + using src_ext_t = Extents; + auto dst_ext = submdspan_extents(extents(), slices...); + using dst_ext_t = decltype(dst_ext); + + // figure out sub layout type + constexpr bool preserve_layout = detail::preserve_layout_left_mapping< + decltype(std::make_index_sequence()), dst_ext_t::rank(), + SliceSpecifiers...>::value; + using dst_layout_t = + std::conditional_t; + using dst_mapping_t = typename dst_layout_t::template mapping; + + // Figure out if any slice's lower bound equals the corresponding extent. + // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. + const bool out_of_bounds = + detail::any_slice_out_of_bounds(this->extents(), slices...); + auto offset = static_cast( + out_of_bounds ? + this->required_span_size() : + this->operator()(detail::first_of(slices)...) + ); + + if constexpr (std::is_same_v) { + // layout_left case + return submdspan_mapping_result{dst_mapping_t(dst_ext), offset}; + } else { + // layout_stride case + auto inv_map = detail::inv_map_rank( + std::integral_constant(), + std::index_sequence<>(), + slices...); + return submdspan_mapping_result{ + dst_mapping_t(dst_ext, detail::construct_sub_strides( + *this, inv_map, + // HIP needs deduction guides to have markups so we need to be explicit + // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue + // But Clang-CUDA also doesn't accept the use of deduction guide so disable it for CUDA alltogether + #if defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_CUDA) + std::tuple{detail::stride_of(slices)...})), + #else + std::tuple{detail::stride_of(slices)...})), + #endif + offset}; + } +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + __builtin_unreachable(); +#endif +} +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic pop + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic pop + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic pop +#endif + +//********************************** +// layout_right submdspan_mapping +//********************************* +namespace detail { + +// Figure out whether to preserve layout_right +template +struct preserve_layout_right_mapping; + +template +struct preserve_layout_right_mapping, SubRank, + SliceSpecifiers...> { + constexpr static size_t SrcRank = sizeof...(SliceSpecifiers); + constexpr static bool value = + // Preserve layout for rank 0 + (SubRank == 0) || + ( + // The last subrank slice specifiers need to be full_extent_t - except + // for the srcrank-subrank one which could also be tuple but not a + // strided index range slice specifiers before srcrank-subrank are + // integrals + ((Idx < + SrcRank - SubRank) || // these are only integral slice specifiers + (std::is_same_v) || + ((Idx == SrcRank - SubRank) && + std::is_convertible_v>)) && + ...); +}; +} // namespace detail + +// Suppress spurious warning with NVCC about no return statement. +// This is a known issue in NVCC and NVC++ +// Depending on the CUDA and GCC version we need both the builtin +// and the diagnostic push. I tried really hard to find something shorter +// but no luck ... +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic push + #pragma nv_diag_suppress = implicit_return_from_non_void_function + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic push + #pragma diag_suppress implicit_return_from_non_void_function + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic push + #pragma diag_suppress = implicit_return_from_non_void_function +#endif +template +template +MDSPAN_INLINE_FUNCTION +constexpr auto +layout_right::mapping::submdspan_mapping_impl( + SliceSpecifiers... slices) const { + // get sub extents + using src_ext_t = Extents; + auto dst_ext = submdspan_extents(extents(), slices...); + using dst_ext_t = decltype(dst_ext); + + // determine new layout type + constexpr bool preserve_layout = detail::preserve_layout_right_mapping< + decltype(std::make_index_sequence()), dst_ext_t::rank(), + SliceSpecifiers...>::value; + using dst_layout_t = + std::conditional_t; + using dst_mapping_t = typename dst_layout_t::template mapping; + + // Figure out if any slice's lower bound equals the corresponding extent. + // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. + const bool out_of_bounds = + detail::any_slice_out_of_bounds(this->extents(), slices...); + auto offset = static_cast( + out_of_bounds ? + this->required_span_size() : + this->operator()(detail::first_of(slices)...) + ); + + if constexpr (std::is_same_v) { + // layout_right case + return submdspan_mapping_result{dst_mapping_t(dst_ext), offset}; + } else { + // layout_stride case + auto inv_map = detail::inv_map_rank( + std::integral_constant(), + std::index_sequence<>(), + slices...); + return submdspan_mapping_result{ + dst_mapping_t(dst_ext, detail::construct_sub_strides( + *this, inv_map, + // HIP needs deduction guides to have markups so we need to be explicit + // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue + // But Clang-CUDA also doesn't accept the use of deduction guide so disable it for CUDA alltogether + #if defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_CUDA) + std::tuple{detail::stride_of(slices)...})), + #else + std::tuple{detail::stride_of(slices)...})), + #endif + offset}; + } +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + __builtin_unreachable(); +#endif +} +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic pop + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic pop + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic pop +#endif + +//********************************** +// layout_stride submdspan_mapping +//********************************* +template +template +MDSPAN_INLINE_FUNCTION +constexpr auto +layout_stride::mapping::submdspan_mapping_impl( + SliceSpecifiers... slices) const { + auto dst_ext = submdspan_extents(extents(), slices...); + using dst_ext_t = decltype(dst_ext); + auto inv_map = detail::inv_map_rank( + std::integral_constant(), + std::index_sequence<>(), + slices...); + using dst_mapping_t = typename layout_stride::template mapping; + + // Figure out if any slice's lower bound equals the corresponding extent. + // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. + const bool out_of_bounds = + detail::any_slice_out_of_bounds(this->extents(), slices...); + auto offset = static_cast( + out_of_bounds ? + this->required_span_size() : + this->operator()(detail::first_of(slices)...) + ); + + return submdspan_mapping_result{ + dst_mapping_t(dst_ext, detail::construct_sub_strides( + *this, inv_map, + // HIP needs deduction guides to have markups so we need to be explicit + // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue + #if defined(_MDSPAN_HAS_HIP) || (defined(__NVCC__) && (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10) < 1120) + std::tuple(detail::stride_of(slices)...))), +#else + std::tuple(detail::stride_of(slices)...))), +#endif + offset}; +} + +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_mapping.hpp + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +template +MDSPAN_INLINE_FUNCTION +constexpr auto +submdspan(const mdspan &src, + SliceSpecifiers... slices) { + const auto sub_submdspan_mapping_result = submdspan_mapping(src.mapping(), slices...); + // NVCC has a problem with the deduction so lets figure out the type + using sub_mapping_t = std::remove_cv_t; + using sub_extents_t = typename sub_mapping_t::extents_type; + using sub_layout_t = typename sub_mapping_t::layout_type; + using sub_accessor_t = typename AccessorPolicy::offset_policy; + return mdspan( + src.accessor().offset(src.data_handle(), sub_submdspan_mapping_result.offset), + sub_submdspan_mapping_result.mapping, + sub_accessor_t(src.accessor())); +} +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan.hpp +#endif +//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2389_bits/dims.hpp +//@HEADER +// ************************************************************************ +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. +// See https://kokkos.org/LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//@HEADER + + +// backward compatibility import into experimental +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { + +template< ::std::size_t Rank, class IndexType = std::size_t> +using dims = + :: MDSPAN_IMPL_STANDARD_NAMESPACE :: dextents; + +} // namespace MDSPAN_IMPL_PROPOSED_NAMESPACE +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE +//END_FILE_INCLUDE: mdspan/include/experimental/__p2389_bits/dims.hpp + +#endif // MDSPAN_HPP_ +//END_FILE_INCLUDE: mdspan/include/mdspan/mdspan.hpp +#endif // _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ + diff --git a/scipy/special/ufunc.h b/scipy/special/ufunc.h index 86e3dd13a75c..c097a18197a0 100644 --- a/scipy/special/ufunc.h +++ b/scipy/special/ufunc.h @@ -15,7 +15,7 @@ #include #include "sf_error.h" -#include "special/mdspan.h" +#include "special/third_party/kokkos/mdspan.hpp" // This is std::accumulate, but that is not constexpr until C++20 From 9e50b784316a029e02125be4c67f4e301f7c6495 Mon Sep 17 00:00:00 2001 From: Tupui <23188539+tupui@users.noreply.github.com> Date: Mon, 3 Jun 2024 10:48:25 -0700 Subject: [PATCH 345/500] DOC: use intersphinx_registry for easier intersphinx mapping maintenance. [docs only] --- doc/source/conf.py | 13 ++++--------- doc/source/dev/toolchain.rst | 1 + environment.yml | 1 + pyproject.toml | 1 + requirements/doc.txt | 1 + 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 51c18bf6babc..5753687b2562 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -8,6 +8,7 @@ from docutils import nodes from docutils.parsers.rst import Directive +from intersphinx_registry import get_intersphinx_mapping import matplotlib import matplotlib.pyplot as plt from numpydoc.docscrape_sphinx import SphinxDocString @@ -273,15 +274,9 @@ # ----------------------------------------------------------------------------- # Intersphinx configuration # ----------------------------------------------------------------------------- -intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), - 'numpy': ('https://numpy.org/devdocs', None), - 'neps': ('https://numpy.org/neps', None), - 'matplotlib': ('https://matplotlib.org/stable', None), - 'asv': ('https://asv.readthedocs.io/en/stable/', None), - 'statsmodels': ('https://www.statsmodels.org/stable', None), -} - +intersphinx_mapping = get_intersphinx_mapping( + packages={"python", "numpy", "neps", "matplotlib", "asv", "statsmodels", "mpmath"} +) # ----------------------------------------------------------------------------- # Numpy extensions diff --git a/doc/source/dev/toolchain.rst b/doc/source/dev/toolchain.rst index a7e15e53dcce..b3f932c4e097 100644 --- a/doc/source/dev/toolchain.rst +++ b/doc/source/dev/toolchain.rst @@ -471,6 +471,7 @@ Building the Documentation Tool Version ==================== ================================================= Sphinx Whatever recent versions work. >= 5.0. +intersphinx_registry Whatever recent versions work. PyData Sphinx theme Whatever recent versions work. >= 0.15.2. Sphinx-Design Whatever recent versions work. >= 0.4.0. numpydoc Whatever recent versions work. >= 1.5.0. diff --git a/environment.yml b/environment.yml index 84c31d1c4a22..f7fe2f48c11b 100644 --- a/environment.yml +++ b/environment.yml @@ -36,6 +36,7 @@ dependencies: - types-psutil # For building docs - sphinx + - intersphinx_registry - numpydoc - ipython - setuptools<67.3 # avoid pkg_resources deprecation warnings from MPL/scikit-umfpack diff --git a/pyproject.toml b/pyproject.toml index 48a0abed65d2..6a0873770e57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,6 +87,7 @@ test = [ ] doc = [ "sphinx>=5.0.0", + "intersphinx_registry", "pydata-sphinx-theme>=0.15.2", "sphinx-design>=0.4.0", "matplotlib>=3.5", diff --git a/requirements/doc.txt b/requirements/doc.txt index fa160cc5d384..0df108072d84 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -1,6 +1,7 @@ # Generated via tools/generate_requirements.py. # Do not edit this file; modify `pyproject.toml` instead and run `python tools/generate_requirements.py`. sphinx>=5.0.0 +intersphinx_registry pydata-sphinx-theme>=0.15.2 sphinx-design>=0.4.0 matplotlib>=3.5 From 162a1765cb11e6cbb9bfb6b2b9964d80132e3fe4 Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Mon, 3 Jun 2024 13:30:24 -0500 Subject: [PATCH 346/500] MAINT: sparse: fix `__init__` func sig to allow `maxprint` to be set (#20719) * change init signature * add test of maxprint --- scipy/sparse/_base.py | 4 ++-- scipy/sparse/_bsr.py | 5 +++-- scipy/sparse/_compressed.py | 4 ++-- scipy/sparse/_coo.py | 4 ++-- scipy/sparse/_data.py | 4 ++-- scipy/sparse/_dia.py | 4 ++-- scipy/sparse/_dok.py | 4 ++-- scipy/sparse/_lil.py | 4 ++-- scipy/sparse/tests/test_base.py | 24 ++++++++++++++++++++++-- 9 files changed, 39 insertions(+), 18 deletions(-) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 510f7565065d..5a1c5747ff36 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -110,7 +110,7 @@ def _lil_container(self): from ._lil import lil_array return lil_array - def __init__(self, arg1, maxprint=MAXPRINT): + def __init__(self, arg1, *, maxprint=None): self._shape = None if self.__class__.__name__ == '_spbase': raise ValueError("This class is not intended" @@ -119,7 +119,7 @@ def __init__(self, arg1, maxprint=MAXPRINT): raise ValueError( "scipy sparse array classes do not support instantiation from a scalar" ) - self.maxprint = maxprint + self.maxprint = MAXPRINT if maxprint is None else maxprint @property def shape(self): diff --git a/scipy/sparse/_bsr.py b/scipy/sparse/_bsr.py index 6a8a1be7dabe..3c6b2c3f4a08 100644 --- a/scipy/sparse/_bsr.py +++ b/scipy/sparse/_bsr.py @@ -24,8 +24,9 @@ class _bsr_base(_cs_matrix, _minmax_mixin): _format = 'bsr' - def __init__(self, arg1, shape=None, dtype=None, copy=False, blocksize=None): - _data_matrix.__init__(self, arg1) + 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: diff --git a/scipy/sparse/_compressed.py b/scipy/sparse/_compressed.py index a86c09fc5edf..35a0d7df6029 100644 --- a/scipy/sparse/_compressed.py +++ b/scipy/sparse/_compressed.py @@ -24,8 +24,8 @@ 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): - _data_matrix.__init__(self, arg1) + def __init__(self, arg1, shape=None, dtype=None, copy=False, *, maxprint=None): + _data_matrix.__init__(self, arg1, maxprint=maxprint) is_array = isinstance(self, sparray) if issparse(arg1): diff --git a/scipy/sparse/_coo.py b/scipy/sparse/_coo.py index 2f4580b3f004..f33b95b5ef64 100644 --- a/scipy/sparse/_coo.py +++ b/scipy/sparse/_coo.py @@ -24,8 +24,8 @@ class _coo_base(_data_matrix, _minmax_mixin): _format = 'coo' - def __init__(self, arg1, shape=None, dtype=None, copy=False): - _data_matrix.__init__(self, arg1) + def __init__(self, arg1, shape=None, dtype=None, copy=False, *, maxprint=None): + _data_matrix.__init__(self, arg1, maxprint=maxprint) is_array = isinstance(self, sparray) if not copy: copy = copy_if_needed diff --git a/scipy/sparse/_data.py b/scipy/sparse/_data.py index 139888ee43e6..3fe6dc0a20d0 100644 --- a/scipy/sparse/_data.py +++ b/scipy/sparse/_data.py @@ -18,8 +18,8 @@ # TODO implement all relevant operations # use .data.__methods__() instead of /=, *=, etc. class _data_matrix(_spbase): - def __init__(self, arg1): - _spbase.__init__(self, arg1) + def __init__(self, arg1, *, maxprint=None): + _spbase.__init__(self, arg1, maxprint=maxprint) @property def dtype(self): diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index ab6d5dcdccad..5d98e815d515 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -19,8 +19,8 @@ class _dia_base(_data_matrix): _format = 'dia' - def __init__(self, arg1, shape=None, dtype=None, copy=False): - _data_matrix.__init__(self, arg1) + 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": diff --git a/scipy/sparse/_dok.py b/scipy/sparse/_dok.py index 08a039136ff3..a49c80b4fc30 100644 --- a/scipy/sparse/_dok.py +++ b/scipy/sparse/_dok.py @@ -18,8 +18,8 @@ class _dok_base(_spbase, IndexMixin, dict): _format = 'dok' - def __init__(self, arg1, shape=None, dtype=None, copy=False): - _spbase.__init__(self, arg1) + def __init__(self, arg1, shape=None, dtype=None, copy=False, *, maxprint=None): + _spbase.__init__(self, arg1, maxprint=maxprint) is_array = isinstance(self, sparray) if isinstance(arg1, tuple) and isshape(arg1, allow_1d=is_array): diff --git a/scipy/sparse/_lil.py b/scipy/sparse/_lil.py index 2503aa628b58..624a56d7911d 100644 --- a/scipy/sparse/_lil.py +++ b/scipy/sparse/_lil.py @@ -20,8 +20,8 @@ class _lil_base(_spbase, IndexMixin): _format = 'lil' - def __init__(self, arg1, shape=None, dtype=None, copy=False): - _spbase.__init__(self, arg1) + 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 diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index 3c2a4d352a2a..ab15f87da1ea 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -658,6 +658,26 @@ def test_repr(self): ) 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: @@ -4890,11 +4910,11 @@ def _same_sum_duplicate(data, *inds, **kwargs): class _NonCanonicalMixin: - def spcreator(self, D, sorted_indices=False, **kwargs): + 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, **kwargs) + M = construct(D, *args, **kwargs) zero_pos = (M.toarray() == 0).nonzero() has_zeros = (zero_pos[0].size > 0) From 23483978979cca91eca307c626c4e9aa092f8755 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 3 Jun 2024 16:55:44 -0700 Subject: [PATCH 347/500] ENH: `stats.ttest_ind`: add array API support (#20771) --- scipy/stats/_stats_py.py | 149 ++++++---- scipy/stats/tests/test_stats.py | 513 +++++++++++++++++++------------- 2 files changed, 397 insertions(+), 265 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 6171fdcc1711..b485c4278953 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -59,9 +59,8 @@ monte_carlo_test, permutation_test, bootstrap, _batch_generator) from ._axis_nan_policy import (_axis_nan_policy_factory, - _broadcast_concatenate, - _broadcast_shapes, - SmallSampleWarning) + _broadcast_concatenate, _broadcast_shapes, + _broadcast_array_shapes_remove_axis, SmallSampleWarning) from ._binomtest import _binary_search_for_binom_tst as _binary_search from scipy._lib._bunch import _make_tuple_bunch from scipy import stats @@ -6511,17 +6510,25 @@ def _t_confidence_interval(df, t, confidence_level, alternative, dtype=None, xp= return low, high -def _ttest_ind_from_stats(mean1, mean2, denom, df, alternative): +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 = np.divide(d, denom)[()] - prob = _get_pvalue(t, distributions.t(df), alternative, xp=np) + t = xp.divide(d, denom) + + t_np = np.asarray(t) + df_np = np.asarray(df) + prob = _get_pvalue(t_np, distributions.t(df_np), alternative, xp=np) + prob = xp.asarray(prob, dtype=t.dtype) - return (t, 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): +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'): @@ -6529,23 +6536,26 @@ def _unequal_var_ttest_denom(v1, n1, v2, n2): # 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 = np.where(np.isnan(df), 1, df) - denom = np.sqrt(vn1 + vn2) + df = xp.where(xp.isnan(df), xp.asarray(1.), df) + denom = xp.sqrt(vn1 + vn2) return df, denom -def _equal_var_ttest_denom(v1, n1, v2, n2): +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 = np.where(n1 == 1, 0, v1)[()] - v2 = np.where(n2 == 1, 0, v2)[()] + zero = xp.asarray(0.) + v1 = xp.where(xp.asarray(n1 == 1), zero, v1) + v2 = xp.where(xp.asarray(n2 == 1), zero, v2) df = n1 + n2 - 2.0 svar = ((n1 - 1) * v1 + (n2 - 1) * v2) / df - denom = np.sqrt(svar * (1.0 / n1 + 1.0 / n2)) + denom = xp.sqrt(svar * (1.0 / n1 + 1.0 / n2)) return df, denom @@ -6674,15 +6684,17 @@ def ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2, Ttest_indResult(statistic=-0.5627179589855622, pvalue=0.573989277115258) """ - mean1 = np.asarray(mean1) - std1 = np.asarray(std1) - mean2 = np.asarray(mean2) - std2 = np.asarray(std2) + xp = array_namespace(mean1, std1, mean2, std2) + + mean1 = xp.asarray(mean1) + std1 = xp.asarray(std1) + mean2 = xp.asarray(mean2) + std2 = xp.asarray(std2) + if equal_var: - df, denom = _equal_var_ttest_denom(std1**2, nobs1, std2**2, nobs2) + 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) + 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) @@ -6937,23 +6949,35 @@ def ttest_ind(a, b, axis=0, equal_var=True, nan_policy='propagate', Ttest_indResult(statistic=3.4463884028073513, pvalue=0.01369338726499547) """ + xp = array_namespace(a, b) + + default_float = xp.asarray(1.).dtype + if xp.isdtype(a.dtype, 'integral'): + a = xp.astype(a, default_float) + if xp.isdtype(b.dtype, 'integral'): + b = xp.astype(b, default_float) + if not (0 <= trim < .5): raise ValueError("Trimming percentage should be 0 <= `trim` < .5.") - NaN = _get_nan(a, b) - - if a.size == 0 or b.size == 0: - # _axis_nan_policy decorator ensures this only happens with 1d input + result_shape = _broadcast_array_shapes_remove_axis((a, b), axis=axis) + NaN = xp.full(result_shape, _get_nan(a, b, xp=xp)) + NaN = NaN[()] if NaN.ndim == 0 else NaN + 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} + + # This probably should be deprecated and replaced with a `method` argument if permutations is not None and permutations != 0: + message = "Use of `permutations` is compatible only with NumPy arrays." + if not is_numpy(xp): + raise NotImplementedError(message) + + message = "Use of `permutations` is incompatible with with use of `trim`." if trim != 0: - raise ValueError("Permutations are currently not supported " - "with trimming.") - if permutations < 0 or (np.isfinite(permutations) and - int(permutations) != permutations): - raise ValueError("Permutations must be a non-negative integer.") + raise NotImplementedError(message) t, prob = _permutation_ttest(a, b, permutations=permutations, axis=axis, equal_var=equal_var, @@ -6962,37 +6986,40 @@ def ttest_ind(a, b, axis=0, equal_var=True, nan_policy='propagate', alternative=alternative) df, denom, estimate = NaN, NaN, NaN + # _axis_nan_policy decorator doesn't play well with strings + return TtestResult(t, prob, df=df, alternative=alternative_nums[alternative], + standard_error=denom, estimate=estimate) + + n1 = xp.asarray(a.shape[axis], dtype=a.dtype) + n2 = xp.asarray(b.shape[axis], dtype=b.dtype) + + 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: - n1 = a.shape[axis] - n2 = b.shape[axis] - - if trim == 0: - if equal_var: - old_errstate = np.geterr() - np.seterr(divide='ignore', invalid='ignore') - v1 = _var(a, axis, ddof=1) - v2 = _var(b, axis, ddof=1) - if equal_var: - np.seterr(**old_errstate) - m1 = np.mean(a, axis) - m2 = np.mean(b, axis) - else: - v1, m1, n1 = _ttest_trim_var_mean_len(a, trim, axis) - v2, m2, n2 = _ttest_trim_var_mean_len(b, trim, axis) + message = "Use of `trim` is compatible only with NumPy arrays." + if not is_numpy(xp): + raise NotImplementedError(message) - if equal_var: - df, denom = _equal_var_ttest_denom(v1, n1, v2, n2) - else: - df, denom = _unequal_var_ttest_denom(v1, n1, v2, n2) - t, prob = _ttest_ind_from_stats(m1, m2, denom, df, alternative) + v1, m1, n1 = _ttest_trim_var_mean_len(a, trim, axis) + v2, m2, n2 = _ttest_trim_var_mean_len(b, trim, axis) - # when nan_policy='omit', `df` can be different for different axis-slices - df = np.broadcast_to(df, t.shape)[()] - estimate = m1-m2 + 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) + t, prob = _ttest_ind_from_stats(m1, m2, denom, df, alternative) - # _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, + # 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) @@ -7103,9 +7130,9 @@ def _calc_t_stat(a, b, equal_var, axis=-1): var_b = _var(b, axis=axis, ddof=1) if not equal_var: - denom = _unequal_var_ttest_denom(var_a, na, var_b, nb)[1] + _, denom = _unequal_var_ttest_denom(var_a, na, var_b, nb) else: - denom = _equal_var_ttest_denom(var_a, na, var_b, nb)[1] + _, denom = _equal_var_ttest_denom(var_a, na, var_b, nb) return (avg_a-avg_b)/denom @@ -7151,6 +7178,10 @@ def _permutation_ttest(a, b, permutations, axis=0, equal_var=True, The p-value. """ + if permutations < 0 or (np.isfinite(permutations) and + int(permutations) != permutations): + raise ValueError("Permutations must be a non-negative integer.") + random_state = check_random_state(random_state) t_stat_observed = _calc_t_stat(a, b, equal_var, axis=axis) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 5e39549b0af7..bfec8dd8761d 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -5071,90 +5071,114 @@ def test_ttest_ci_iv(test_fun, args): res.confidence_interval(confidence_level=10) -def _desc_stats(x1, x2, axis=0): +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 = np.asarray(x) - mu = np.mean(x, axis=axis) - std = np.std(x, axis=axis, ddof=1) + 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) -def test_ttest_ind(): +@array_api_compatible +@pytest.mark.skip_xp_backends(cpu_only=True, + reasons=['Uses NumPy for pvalue, CI']) +@pytest.mark.usefixtures("skip_xp_backends") +def test_ttest_ind(xp): # regression test - tr = 1.0912746897927283 - pr = 0.27647818616351882 - tpr = ([tr,-tr],[pr,pr]) + tr = xp.asarray(1.0912746897927283) + pr = xp.asarray(0.27647818616351882) + tr_2D = xp.asarray([tr, -tr]) + pr_2D = xp.asarray([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) - rvs2 = np.linspace(1,100,100) - rvs1 = np.linspace(5,105,100) - rvs1_2D = np.array([rvs1, rvs2]) - rvs2_2D = np.array([rvs2, rvs1]) + 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) - t,p = stats.ttest_ind(rvs1, rvs2, axis=0) - assert_array_almost_equal([t,p],(tr,pr)) - # test from_stats API - assert_array_almost_equal(stats.ttest_ind_from_stats(*_desc_stats(rvs1, - rvs2)), - [t, p]) - t,p = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0) - assert_array_almost_equal([t,p],tpr) - args = _desc_stats(rvs1_2D.T, rvs2_2D.T) - assert_array_almost_equal(stats.ttest_ind_from_stats(*args), - [t, p]) - t,p = stats.ttest_ind(rvs1_2D, rvs2_2D, axis=1) - assert_array_almost_equal([t,p],tpr) - args = _desc_stats(rvs1_2D, rvs2_2D, axis=1) - assert_array_almost_equal(stats.ttest_ind_from_stats(*args), - [t, p]) + 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) - # test scalars - with suppress_warnings() as sup, np.errstate(invalid="ignore"): - sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice") - t, p = stats.ttest_ind(4., 3.) - assert_(np.isnan(t)) - assert_(np.isnan(p)) + 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) - # test on 3 dimensions - rvs1_3D = np.dstack([rvs1_2D,rvs1_2D,rvs1_2D]) - rvs2_3D = np.dstack([rvs2_2D,rvs2_2D,rvs2_2D]) - t,p = stats.ttest_ind(rvs1_3D, rvs2_3D, axis=1) - assert_almost_equal(np.abs(t), np.abs(tr)) - assert_array_almost_equal(np.abs(p), pr) - assert_equal(t.shape, (2, 3)) + res = stats.ttest_ind(rvs1_2D, rvs2_2D, axis=1) + xp_assert_close(res.statistic, tr_2D) + xp_assert_close(res.pvalue, pr_2D) - t, p = stats.ttest_ind(np.moveaxis(rvs1_3D, 2, 0), - np.moveaxis(rvs2_3D, 2, 0), - axis=2) - assert_array_almost_equal(np.abs(t), np.abs(tr)) - assert_array_almost_equal(np.abs(p), pr) - assert_equal(t.shape, (3, 2)) + 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 - assert_raises(ValueError, stats.ttest_ind, rvs1, rvs2, alternative="error") - assert_raises(ValueError, stats.ttest_ind_from_stats, - *_desc_stats(rvs1_2D.T, rvs2_2D.T), alternative="error") + 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") - assert_allclose(p, 1 - (pr/2)) - assert_allclose(t, tr) + xp_assert_close(p, 1 - (pr/2)) + xp_assert_close(t, tr) t, p = stats.ttest_ind(rvs1, rvs2, alternative="greater") - assert_allclose(p, pr/2) - assert_allclose(t, tr) + xp_assert_close(p, pr/2) + xp_assert_close(t, tr) - # Below makes sure ttest_ind_from_stats p-val functions identically to - # ttest_ind - t, p = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, alternative="less") + # 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) - assert_allclose( - stats.ttest_ind_from_stats(*args, alternative="less"), [t, p]) + res2 = stats.ttest_ind_from_stats(*args, alternative="less") + xp_assert_close(res1.statistic, res2.statistic) + xp_assert_close(res1.pvalue, res2.pvalue) - t, p = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, alternative="greater") + res1 = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, alternative="less") args = _desc_stats(rvs1_2D.T, rvs2_2D.T) - assert_allclose( - stats.ttest_ind_from_stats(*args, alternative="greater"), [t, p]) + 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) + + +def test_ttest_ind_nan_policy(): + 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]) # check nan policy rng = np.random.RandomState(12345678) @@ -5206,6 +5230,15 @@ def convert(t, p, alt): assert_allclose(p, converter(tr, pr, 'greater'), rtol=1e-14) +def test_ttest_ind_scalar(): + # test scalars + with suppress_warnings() as sup, np.errstate(invalid="ignore"): + sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice") + t, p = stats.ttest_ind(4., 3.) + assert_(np.isnan(t)) + assert_(np.isnan(p)) + + class Test_ttest_ind_permutations: N = 20 @@ -5452,6 +5485,19 @@ def test_ttest_ind_permutation_check_p_values(self): print(0.0 not in p_values) assert 0.0 not in p_values + @array_api_compatible + @pytest.mark.skip_xp_backends(cpu_only=True, + reasons=['Uses NumPy for pvalue, CI']) + @pytest.mark.usefixtures("skip_xp_backends") + def test_permutation_not_implement_for_xp(self, xp): + message = "Use of `permutations` is compatible only with NumPy arrays." + a2, b2 = xp.asarray(self.a2), xp.asarray(self.b2) + if is_numpy(xp): # no error + stats.ttest_ind(a2, b2, permutations=10) + else: # NotImplementedError + with pytest.raises(NotImplementedError, match=message): + stats.ttest_ind(a2, b2, permutations=10) + class Test_ttest_ind_common: # for tests that are performed on variations of the t-test such as @@ -5644,12 +5690,24 @@ def test_alternatives(self, alt, pr, tr): assert_allclose(statistic, tr, atol=1e-10) def test_errors_unsupported(self): - # confirm that attempting to trim with NaNs or permutations raises an - # error - match = "Permutations are currently not supported with trimming." - with assert_raises(ValueError, match=match): + # confirm that attempting to trim with permutations raises an error + match = "Use of `permutations` is incompatible with with use of `trim`." + with assert_raises(NotImplementedError, match=match): stats.ttest_ind([1, 2], [2, 3], trim=.2, permutations=2) + @array_api_compatible + @pytest.mark.skip_xp_backends(cpu_only=True, + reasons=['Uses NumPy for pvalue, CI']) + @pytest.mark.usefixtures("skip_xp_backends") + def test_permutation_not_implement_for_xp(self, xp): + message = "Use of `trim` is compatible only with NumPy arrays." + a, b = xp.arange(10), xp.arange(10)+1 + if is_numpy(xp): # no error + stats.ttest_ind(a, b, trim=0.1) + else: # NotImplementedError + with pytest.raises(NotImplementedError, match=message): + stats.ttest_ind(a, b, trim=0.1) + @pytest.mark.parametrize("trim", [-.2, .5, 1]) def test_trim_bounds_error(self, trim): match = "Trimming percentage should be 0 <= `trim` < .5." @@ -5657,6 +5715,10 @@ def test_trim_bounds_error(self, trim): stats.ttest_ind([1, 2], [2, 1], trim=trim) +@array_api_compatible +@pytest.mark.skip_xp_backends(cpu_only=True, + reasons=['Uses NumPy for pvalue, CI']) +@pytest.mark.usefixtures("skip_xp_backends") class Test_ttest_CI: # indices in order [alternative={two-sided, less, greater}, # equal_var={False, True}, trim={0, 0.2}] @@ -5703,13 +5765,16 @@ class Test_ttest_CI: @pytest.mark.parametrize('alternative', ['two-sided', 'less', 'greater']) @pytest.mark.parametrize('equal_var', [False, True]) @pytest.mark.parametrize('trim', [0, 0.2]) - def test_confidence_interval(self, alternative, equal_var, trim): + 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 = rng.random(11) - y = rng.random(13) + 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) @@ -5717,13 +5782,16 @@ def test_confidence_interval(self, alternative, equal_var, 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 - assert_allclose(res.statistic, statistic) - assert_allclose(res.df, df) - assert_allclose(res.pvalue, pvalue) + + 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) - assert_allclose(ci.low, low) - assert_allclose(ci.high, high) + xp_assert_close(ci.low, xp.asarray(low), rtol=rtol) + xp_assert_close(ci.high, xp.asarray(high), rtol=rtol) def test__broadcast_concatenate(): @@ -5744,113 +5812,114 @@ def test__broadcast_concatenate(): assert b[i, j, k, l - a.shape[-3], m, n] == c[i, j, k, l, m, n] -def test_ttest_ind_with_uneq_var(): - # check vs. R - a = (1, 2, 3) - b = (1.1, 2.9, 4.2) - pr = 0.53619490753126731 - tr = -0.68649512735572582 +@array_api_compatible +@pytest.mark.skip_xp_backends(cpu_only=True, + reasons=['Uses NumPy for pvalue, CI']) +@pytest.mark.usefixtures("skip_xp_backends") +def test_ttest_ind_with_uneq_var(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) - assert_array_almost_equal([t,p], [tr, pr]) - # test from desc stats API - assert_array_almost_equal(stats.ttest_ind_from_stats(*_desc_stats(a, b), - equal_var=False), - [t, p]) - - a = (1, 2, 3, 4) - pr = 0.84354139131608286 - tr = -0.2108663315950719 + 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) - assert_array_almost_equal([t,p], [tr, pr]) - assert_array_almost_equal(stats.ttest_ind_from_stats(*_desc_stats(a, b), - equal_var=False), - [t, p]) + 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 = 1.0912746897927283 - tr_uneq_n = 0.66745638708050492 - pr = 0.27647831993021388 - pr_uneq_n = 0.50873585065616544 - tpr = ([tr,-tr],[pr,pr]) + 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.asarray([tr, -tr]) + pr_2D = xp.asarray([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) - rvs3 = np.linspace(1,100, 25) - rvs2 = np.linspace(1,100,100) - rvs1 = np.linspace(5,105,100) - rvs1_2D = np.array([rvs1, rvs2]) + t, p = stats.ttest_ind_from_stats(*_desc_stats(rvs1, rvs2), equal_var=False) + xp_assert_close(t, tr) + xp_assert_close(p, pr) - rvs2_2D = np.array([rvs2, rvs1]) + 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(rvs1, rvs2, axis=0, equal_var=False) - assert_array_almost_equal([t,p],(tr,pr)) - assert_array_almost_equal(stats.ttest_ind_from_stats(*_desc_stats(rvs1, - rvs2), - equal_var=False), - (t, p)) - - t,p = stats.ttest_ind(rvs1, rvs3, axis=0, equal_var=False) - assert_array_almost_equal([t,p], (tr_uneq_n, pr_uneq_n)) - assert_array_almost_equal(stats.ttest_ind_from_stats(*_desc_stats(rvs1, - rvs3), - equal_var=False), - (t, p)) - - t,p = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, equal_var=False) - assert_array_almost_equal([t,p],tpr) - args = _desc_stats(rvs1_2D.T, rvs2_2D.T) - assert_array_almost_equal(stats.ttest_ind_from_stats(*args, - equal_var=False), - (t, p)) + 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) - t,p = stats.ttest_ind(rvs1_2D, rvs2_2D, axis=1, equal_var=False) - assert_array_almost_equal([t,p],tpr) - args = _desc_stats(rvs1_2D, rvs2_2D, axis=1) - assert_array_almost_equal(stats.ttest_ind_from_stats(*args, - equal_var=False), - (t, p)) + 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) - # test for namedtuple attribute results - attributes = ('statistic', 'pvalue') - res = stats.ttest_ind(rvs1, rvs2, axis=0, equal_var=False) - check_named_results(res, attributes) + 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) - # test on 3 dimensions - rvs1_3D = np.dstack([rvs1_2D,rvs1_2D,rvs1_2D]) - rvs2_3D = np.dstack([rvs2_2D,rvs2_2D,rvs2_2D]) - t,p = stats.ttest_ind(rvs1_3D, rvs2_3D, axis=1, equal_var=False) - assert_almost_equal(np.abs(t), np.abs(tr)) - assert_array_almost_equal(np.abs(p), pr) - assert_equal(t.shape, (2, 3)) - args = _desc_stats(rvs1_3D, rvs2_3D, axis=1) - t, p = stats.ttest_ind_from_stats(*args, equal_var=False) - assert_almost_equal(np.abs(t), np.abs(tr)) - assert_array_almost_equal(np.abs(p), pr) - assert_equal(t.shape, (2, 3)) + 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) - t, p = stats.ttest_ind(np.moveaxis(rvs1_3D, 2, 0), - np.moveaxis(rvs2_3D, 2, 0), - axis=2, equal_var=False) - assert_array_almost_equal(np.abs(t), np.abs(tr)) - assert_array_almost_equal(np.abs(p), pr) - assert_equal(t.shape, (3, 2)) - args = _desc_stats(np.moveaxis(rvs1_3D, 2, 0), - np.moveaxis(rvs2_3D, 2, 0), axis=2) - t, p = stats.ttest_ind_from_stats(*args, equal_var=False) - assert_array_almost_equal(np.abs(t), np.abs(tr)) - assert_array_almost_equal(np.abs(p), pr) - assert_equal(t.shape, (3, 2)) + 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) + +@array_api_compatible +@pytest.mark.skip_xp_backends(cpu_only=True, + reasons=['Uses NumPy for pvalue, CI']) +@pytest.mark.usefixtures("skip_xp_backends") +def test_ttest_ind_zero_division(xp): # test zero division problem + x = xp.zeros(3) + y = xp.ones(3) with pytest.warns(RuntimeWarning, match="Precision loss occurred"): - t, p = stats.ttest_ind([0, 0, 0], [1, 1, 1], equal_var=False) - assert_equal((np.abs(t), p), (np.inf, 0)) + 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'): - assert_equal(stats.ttest_ind([0, 0, 0], [0, 0, 0], equal_var=False), - (np.nan, np.nan)) + 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 = np.array([[1, np.nan], [-1, 1]]) - assert_equal(stats.ttest_ind(anan, np.zeros((2, 2)), equal_var=False), - ([0, np.nan], [1, np.nan])) + 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(): @@ -5875,94 +5944,126 @@ def test_ttest_ind_nan_2nd_arg(): atol=1e-15) -def test_ttest_ind_empty_1d_returns_nan(): +@array_api_compatible +def test_ttest_ind_empty_1d_returns_nan(xp): # Two empty inputs should return a TtestResult containing nan # for both values. - with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): - result = stats.ttest_ind([], []) - assert isinstance(result, stats._stats_py.TtestResult) - assert_equal(result, (np.nan, np.nan)) + if is_numpy(xp): + with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit): + res = stats.ttest_ind(xp.asarray([]), xp.asarray([])) + else: + 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) +@array_api_compatible @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(b, expected_shape): +def test_ttest_ind_axis_size_zero(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 = np.empty((3, 1, 0)) + a = xp.empty((3, 1, 0)) + b = xp.asarray(b) with np.testing.suppress_warnings() as sup: # first case should warn, second shouldn't? sup.filter(SmallSampleWarning, too_small_nd_not_omit) - result = stats.ttest_ind(a, b, axis=-1) - assert isinstance(result, stats._stats_py.TtestResult) - expected_value = np.full(expected_shape, fill_value=np.nan) - assert_equal(result.statistic, expected_value) - assert_equal(result.pvalue, expected_value) + 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(): +@array_api_compatible +def test_ttest_ind_nonaxis_size_zero(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 = np.empty((1, 8, 0)) - b = np.empty((5, 8, 1)) - result = stats.ttest_ind(a, b, axis=1) - assert isinstance(result, stats._stats_py.TtestResult) - assert_equal(result.statistic.shape, (5, 0)) - assert_equal(result.pvalue.shape, (5, 0)) + 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(): +@array_api_compatible +def test_ttest_ind_nonaxis_size_zero_different_lengths(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 = np.empty((1, 7, 0)) - b = np.empty((5, 8, 1)) - result = stats.ttest_ind(a, b, axis=1) - assert isinstance(result, stats._stats_py.TtestResult) - assert_equal(result.statistic.shape, (5, 0)) - assert_equal(result.pvalue.shape, (5, 0)) + 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) -def test_gh5686(): - mean1, mean2 = np.array([1, 2]), np.array([3, 4]) - std1, std2 = np.array([5, 3]), np.array([4, 5]) - nobs1, nobs2 = np.array([130, 140]), np.array([100, 150]) +@array_api_compatible +@pytest.mark.skip_xp_backends(np_only=True, + reasons=["Other backends don't like integers"]) +@pytest.mark.usefixtures("skip_xp_backends") +def test_gh5686(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) -def test_ttest_ind_from_stats_inputs_zero(): +@array_api_compatible +@pytest.mark.skip_xp_backends(cpu_only=True, + reasons=['Uses NumPy for pvalue, CI']) +@pytest.mark.usefixtures("skip_xp_backends") +def test_ttest_ind_from_stats_inputs_zero(xp): # Regression test for gh-6409. - result = stats.ttest_ind_from_stats(0, 0, 6, 0, 0, 6, equal_var=False) - assert_equal(result, [np.nan, np.nan]) + 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) -def test_ttest_single_observation(): +@array_api_compatible +@pytest.mark.skip_xp_backends(cpu_only=True, + reasons=['Uses NumPy for pvalue, CI']) +@pytest.mark.usefixtures("skip_xp_backends") +def test_ttest_uniform_pvalues(xp): # test that p-values are uniformly distributed under the null hypothesis rng = np.random.default_rng(246834602926842) - x = rng.normal(size=(10000, 2)) - y = rng.normal(size=(10000, 1)) + 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) - assert stats.ks_1samp(res.pvalue, stats.uniform().cdf).pvalue > 0.1 - assert_allclose(np.percentile(res.pvalue, q*100), q, atol=1e-2) + 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) res = stats.ttest_ind(y, x, equal_var=True, axis=-1) - assert stats.ks_1samp(res.pvalue, stats.uniform().cdf).pvalue > 0.1 - assert_allclose(np.percentile(res.pvalue, q*100), q, atol=1e-2) + 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) - res = stats.ttest_ind([2, 3, 5], [1.5], equal_var=True) - assert_allclose(res, (1.0394023007754, 0.407779907736), rtol=1e-10) + x, y = xp.asarray([2, 3, 5]), xp.asarray([1.5]) + + res = stats.ttest_ind(x, y, equal_var=True) + rtol = 1e-6 if is_torch(xp) else 1e-10 + xp_assert_close(res.statistic, xp.asarray(1.0394023007754), rtol=rtol) + xp_assert_close(res.pvalue, xp.asarray(0.407779907736), rtol=rtol) def _convert_pvalue_alternative(t, p, alt, xp): From 028b5d67cabdc935304d580557505b09768a73d7 Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Mon, 3 Jun 2024 21:01:28 -0700 Subject: [PATCH 348/500] ENH: sparse: add axis parameter to `count_nonzero` method (#20857) * Add tests for axis kwarg in count_nonzero * add axis parameter to count_nonzero * Add examples and notes to the docs for count_nonzero * fix downcast for bincount in COO * [docs only] add axis to doc demo * add code changes from review; pairwise option * update to pairwise --- scipy/sparse/_base.py | 59 +++++++++++++++++++++++++++++---- scipy/sparse/_bsr.py | 9 +++++ scipy/sparse/_compressed.py | 32 ++++++++++++++++-- scipy/sparse/_coo.py | 15 +++++++++ scipy/sparse/_data.py | 5 --- scipy/sparse/_dia.py | 9 +++-- scipy/sparse/_dok.py | 6 +++- scipy/sparse/_lil.py | 23 +++++++++++-- scipy/sparse/tests/test_base.py | 16 +++++++-- 9 files changed, 151 insertions(+), 23 deletions(-) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 5a1c5747ff36..7e92fd93509e 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -263,26 +263,71 @@ def _getmaxprint(self): """Maximum number of elements to display when printed.""" return self.maxprint - def count_nonzero(self): + def count_nonzero(self, axis=None): """Number of non-zero entries, equivalent to - np.count_nonzero(a.toarray()) + 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]) """ - raise NotImplementedError("count_nonzero not implemented for %s." % - self.__class__.__name__) + 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 : None, 0, or 1 - Select between the number of values across the whole array, in - each column, or in each row. + axis : {-2, -1, 0, 1, None} optional + Report stored values for the whole array, or along a specified axis. See also -------- diff --git a/scipy/sparse/_bsr.py b/scipy/sparse/_bsr.py index 3c6b2c3f4a08..40bc949d4a93 100644 --- a/scipy/sparse/_bsr.py +++ b/scipy/sparse/_bsr.py @@ -220,6 +220,15 @@ def _getnnz(self, axis=None): _getnnz.__doc__ = _spbase._getnnz.__doc__ + def count_nonzero(self, axis=None): + if axis is not None: + raise NotImplementedError( + "count_nonzero over axis is not implemented for BSR format." + ) + 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' diff --git a/scipy/sparse/_compressed.py b/scipy/sparse/_compressed.py index 35a0d7df6029..3ac39cbcf4ee 100644 --- a/scipy/sparse/_compressed.py +++ b/scipy/sparse/_compressed.py @@ -2,6 +2,7 @@ __all__ = [] from warnings import warn +import itertools import operator import numpy as np @@ -124,14 +125,41 @@ def _getnnz(self, axis=None): axis, _ = self._swap((axis, 1 - axis)) _, N = self._swap(self.shape) if axis == 0: - return np.bincount(downcast_intp_index(self.indices), - minlength=N) + 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. diff --git a/scipy/sparse/_coo.py b/scipy/sparse/_coo.py index f33b95b5ef64..6dbf08b2415f 100644 --- a/scipy/sparse/_coo.py +++ b/scipy/sparse/_coo.py @@ -182,6 +182,21 @@ def _getnnz(self, axis=None): _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): diff --git a/scipy/sparse/_data.py b/scipy/sparse/_data.py index 3fe6dc0a20d0..7cf95becb5bc 100644 --- a/scipy/sparse/_data.py +++ b/scipy/sparse/_data.py @@ -97,11 +97,6 @@ def copy(self): copy.__doc__ = _spbase.copy.__doc__ - def count_nonzero(self): - return np.count_nonzero(self._deduped_data()) - - count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ - def power(self, n, dtype=None): """ This function performs element-wise power. diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index 5d98e815d515..4b38ca4937c6 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -115,10 +115,16 @@ def _data_mask(self): mask &= (offset_inds < num_cols) return mask - def count_nonzero(self): + 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 " @@ -133,7 +139,6 @@ def _getnnz(self, axis=None): return int(nnz) _getnnz.__doc__ = _spbase._getnnz.__doc__ - count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ def sum(self, axis=None, dtype=None, out=None): validateaxis(axis) diff --git a/scipy/sparse/_dok.py b/scipy/sparse/_dok.py index a49c80b4fc30..fd2ed06b80b3 100644 --- a/scipy/sparse/_dok.py +++ b/scipy/sparse/_dok.py @@ -69,7 +69,11 @@ def _getnnz(self, axis=None): ) return len(self._dict) - def count_nonzero(self): + def count_nonzero(self, axis=None): + if axis is not None: + raise NotImplementedError( + "count_nonzero over an axis is not implemented for DOK format." + ) return sum(x != 0 for x in self.values()) _getnnz.__doc__ = _spbase._getnnz.__doc__ diff --git a/scipy/sparse/_lil.py b/scipy/sparse/_lil.py index 624a56d7911d..57fbdc3ca672 100644 --- a/scipy/sparse/_lil.py +++ b/scipy/sparse/_lil.py @@ -106,10 +106,27 @@ def _getnnz(self, axis=None): else: raise ValueError('axis out of bounds') - def count_nonzero(self): - return sum(np.count_nonzero(rowvals) for rowvals in self.data) - _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): diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index ab15f87da1ea..ea7b2636c7d4 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -633,11 +633,21 @@ def test_empty(self): 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 in ["coo", "csr", "csc", "lil"]: + assert_equal(self.spcreator((3, 3)).count_nonzero(axis=0), array([0, 0, 0])) def test_count_nonzero(self): - expected = np.count_nonzero(self.datsp.toarray()) - assert_equal(self.datsp.count_nonzero(), expected) - assert_equal(self.datsp.T.count_nonzero(), expected) + axis_support = self.datsp.format in ["coo", "csr", "csc", "lil"] + 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)) From 87dd6f82fbeba9d97b6a68e30ee0081fd30494e1 Mon Sep 17 00:00:00 2001 From: Pamphile Roy <23188539+tupui@users.noreply.github.com> Date: Mon, 3 Jun 2024 23:27:08 -0700 Subject: [PATCH 349/500] DOC: mailing list to forum (#20881) --- .github/ISSUE_TEMPLATE/config.yml | 6 +++--- README.rst | 5 +++-- .../dev/contributor/development_workflow.rst | 2 +- doc/source/dev/core-dev/decisions.rst.inc | 6 +++--- doc/source/dev/core-dev/deprecations.rst.inc | 4 ++-- doc/source/dev/core-dev/distributing.rst.inc | 4 ++-- doc/source/dev/core-dev/github.rst.inc | 2 +- doc/source/dev/core-dev/licensing.rst.inc | 2 +- doc/source/dev/gitwash/git_links.inc | 2 +- doc/source/dev/governance.rst | 8 +++---- doc/source/dev/hacking.rst | 21 +++++++++---------- doc/source/dev/missing-bits.rst | 2 +- doc/source/dev/roadmap-detailed.rst | 6 +++--- doc/source/index.rst | 2 +- 14 files changed, 36 insertions(+), 36 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 1e9e39ab3a5f..f1f1e694bc7d 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,9 +3,9 @@ contact_links: - name: Stack Overflow url: https://stackoverflow.com/questions/tagged/scipy about: Please ask and answer usage questions on Stack Overflow - - name: Developer Mailing list - url: https://mail.python.org/mailman3/lists/scipy-dev.python.org/ - about: Development discussions and announcements on the mailing list + - name: Developer Forum + url: https://discuss.scientific-python.org/c/contributor/scipy + about: Development discussions and announcements on the forum - name: Blank issue url: https://github.com/scipy/scipy/issues/new about: Please note that other templates should be used in most cases diff --git a/README.rst b/README.rst index 7d5cc292de48..01ec8a053f9b 100644 --- a/README.rst +++ b/README.rst @@ -66,8 +66,9 @@ Writing code isn’t the only way to contribute to SciPy. You can also: - 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 mailing list or here, on GitHub, by leaving a -comment on a relevant issue that is already open. +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, diff --git a/doc/source/dev/contributor/development_workflow.rst b/doc/source/dev/contributor/development_workflow.rst index 3f84bf4b269a..4e7da3421277 100644 --- a/doc/source/dev/contributor/development_workflow.rst +++ b/doc/source/dev/contributor/development_workflow.rst @@ -259,7 +259,7 @@ has a nice help page that outlines the process for `filing pull requests`_. If your changes involve modifications to the API or addition/modification of a function, you should initiate a code review. This involves sending an email to -the `SciPy mailing list`_ with a link to your PR along with a description of +the `SciPy forum`_ with a link to your PR along with a description of and a motivation for your changes. .. _pr-checklist: diff --git a/doc/source/dev/core-dev/decisions.rst.inc b/doc/source/dev/core-dev/decisions.rst.inc index 78fd941cdf22..7a7c457245d7 100644 --- a/doc/source/dev/core-dev/decisions.rst.inc +++ b/doc/source/dev/core-dev/decisions.rst.inc @@ -11,14 +11,14 @@ Code Any significant decisions on adding (or not adding) new features, breaking backwards compatibility or making other significant changes to the codebase -should be made on the scipy-dev mailing list after a discussion (preferably +should be made on the scipy-dev forum after a discussion (preferably with full consensus). Any non-trivial change (where trivial means a typo, or a one-liner maintenance commit) has to go in through a pull request (PR). It has to be reviewed by another developer. In case review doesn't happen quickly enough and it is important that the PR is merged quickly, the submitter of the PR should send a -message to mailing list saying they intend to merge that PR without review +message to the forum saying they intend to merge that PR without review at time X for reason Y unless someone reviews it before then. Changes and new additions should be tested. Untested code is broken code. @@ -27,4 +27,4 @@ Commit rights ------------- Who gets commit rights is decided by the SciPy Steering Council; changes in -commit rights will then be announced on the scipy-dev mailing list. +commit rights will then be announced on the scipy-dev forum. diff --git a/doc/source/dev/core-dev/deprecations.rst.inc b/doc/source/dev/core-dev/deprecations.rst.inc index 07dbe6b503d7..de146d79335a 100644 --- a/doc/source/dev/core-dev/deprecations.rst.inc +++ b/doc/source/dev/core-dev/deprecations.rst.inc @@ -11,7 +11,7 @@ In general, it's not a good idea to remove something without warning users about that removal first. Therefore, this is what should be done before removing something from the public API: -#. Propose to deprecate the functionality on the scipy-dev mailing list and get +#. Propose to deprecate the functionality on the scipy-dev forum and get agreement that that's OK. #. Add a ``DeprecationWarning`` for it, which states that the functionality was deprecated, and in which release. For Cython APIs, see @@ -27,4 +27,4 @@ when running the test suite so they don't pollute the output. It's possible that there is reason to want to ignore this deprecation policy for a particular deprecation; this can always be discussed on the scipy-dev -mailing list. +forum. diff --git a/doc/source/dev/core-dev/distributing.rst.inc b/doc/source/dev/core-dev/distributing.rst.inc index ba717d3210db..be31b009b5d5 100644 --- a/doc/source/dev/core-dev/distributing.rst.inc +++ b/doc/source/dev/core-dev/distributing.rst.inc @@ -77,7 +77,7 @@ classifiers in ``setup.py``, and mentioned in the release notes for each release. All newly released Python versions will be supported as soon as possible. For the general policy on dropping support for a Python or NumPy version, see :ref:`NEP 29 `. The final decision on dropping support is -always taken on the scipy-dev mailing list. +always taken on the scipy-dev forum. The lowest supported Numpy_ version for a SciPy version is mentioned in the release notes and is encoded in ``pyproject.toml``, ``scipy/__init__.py`` and the @@ -90,7 +90,7 @@ Supported versions of optional dependencies and compilers is documented in :ref:`toolchain-roadmap`. Note that not all versions of optional dependencies that are supported are tested well or at all by SciPy's Continuous Integration setup. Issues regarding this are dealt with as they come up in the -issue tracker or mailing list. +issue tracker or forum. Building binary installers diff --git a/doc/source/dev/core-dev/github.rst.inc b/doc/source/dev/core-dev/github.rst.inc index 9e4f7a81bdf8..6152131d4fd7 100644 --- a/doc/source/dev/core-dev/github.rst.inc +++ b/doc/source/dev/core-dev/github.rst.inc @@ -43,7 +43,7 @@ Dealing with pull requests - When merging contributions, a committer is responsible for ensuring that those meet the requirements outlined in :ref:`Contributing to SciPy `. Also check that new features and backwards compatibility breaks were discussed - on the scipy-dev mailing list. + on the scipy-dev forum. - New code goes in via a pull request (PR). - Merge new code with the green button. In case of merge conflicts, ask the PR submitter to rebase (this may require providing some ``git`` instructions). diff --git a/doc/source/dev/core-dev/licensing.rst.inc b/doc/source/dev/core-dev/licensing.rst.inc index 5d251cc74eda..b44a805a602c 100644 --- a/doc/source/dev/core-dev/licensing.rst.inc +++ b/doc/source/dev/core-dev/licensing.rst.inc @@ -21,7 +21,7 @@ These contributions cannot be accepted for inclusion in SciPy unless the original code author is willing to (re)license their code under the modified BSD (or compatible) license. If the original author agrees, add a comment saying so to the source files and forward the relevant -communication to the scipy-dev mailing list. +communication to the scipy-dev forum. Another common occurrence is for code to be translated or derived from code in R, Octave (both GPL-licensed) or a commercial application. Such code also diff --git a/doc/source/dev/gitwash/git_links.inc b/doc/source/dev/gitwash/git_links.inc index eafb3011e262..a4fb28f5a036 100755 --- a/doc/source/dev/gitwash/git_links.inc +++ b/doc/source/dev/gitwash/git_links.inc @@ -64,5 +64,5 @@ .. _`NumPy mailing list`: https://numpy.org/community/ .. _SciPy: https://www.scipy.org .. _`SciPy github`: https://github.com/scipy/scipy -.. _`SciPy mailing list`: https://mail.python.org/mailman3/lists/scipy-dev.python.org/ +.. _`SciPy forum`: https://discuss.scientific-python.org/c/contributor/scipy .. _`SciPy repository`: https://github.com/scipy/scipy diff --git a/doc/source/dev/governance.rst b/doc/source/dev/governance.rst index ebd93c95050f..a2d7556abc21 100644 --- a/doc/source/dev/governance.rst +++ b/doc/source/dev/governance.rst @@ -28,7 +28,7 @@ documentation, designs, or other work to the Project. Anyone can be a Contributor. Contributors can be affiliated with any legal entity or none. Contributors participate in the project by submitting, reviewing, and discussing GitHub Pull Requests and Issues and participating in open -and public Project discussions on GitHub, mailing lists, and other +and public Project discussions on GitHub, forums, and other channels. The foundation of Project participation is openness and transparency. @@ -146,7 +146,7 @@ active over the last two years. When considering potential Members, the Council will look at candidates with a comprehensive view of their contributions. This will include, but is not limited -to, code, code review, infrastructure work, mailing list and chat participation, +to, code, code review, infrastructure work, forum and chat participation, community help/building, education and outreach, design work, etc. We are deliberately not setting arbitrary quantitative metrics (like “100 commits in this repo”) to avoid encouraging behavior that plays to the metrics rather than @@ -186,10 +186,10 @@ responsible for: the :ref:`scipy-roadmap`) bi-yearly, around mid-April and mid-October. - At the same times of the year, summarizing any relevant organizational updates and issues in the preceding period, and asking for - feedback/suggestions on the mailing list. + feedback/suggestions on the forum. - Ensuring the composition of the Steering Council stays current. - Ensuring matters discussed in private by the Steering Council get - summarized on the mailing list to keep the Community informed. + summarized on the forum to keep the Community informed. - Ensuring other important organizational documents (e.g., Code of Conduct, Fiscal Sponsorship Agreement) stay current after they are added. diff --git a/doc/source/dev/hacking.rst b/doc/source/dev/hacking.rst index 6b233420222c..60075ce5d4cb 100644 --- a/doc/source/dev/hacking.rst +++ b/doc/source/dev/hacking.rst @@ -17,8 +17,7 @@ There are a lot of ways you can contribute: - Reviewing open pull requests - Triaging issues - Working on the `scipy.org`_ website -- Answering questions and participating on the scipy-dev and scipy-user - `mailing lists`_. +- Answering questions and participating on the `forum`_. Contributing new code ===================== @@ -41,14 +40,14 @@ more domain-specific code than SciPy. Now if you have code that you would like to see included in SciPy, how do you go about it? After checking that your code can be distributed in SciPy under a compatible license (see :ref:`license-considerations`), the first step is to -discuss it on the scipy-dev mailing list. All new features, as well as changes to +discuss it on the scipy-dev `forum`_. All new features, as well as changes to existing code, are discussed and decided on there. You can, and probably should already start this discussion before your code is finished. Remember that in order to be added to SciPy your code will need to be reviewed by someone else, so try to find someone willing to review your work while you're at it. -Assuming the outcome of the discussion on the mailing list is positive and you +Assuming the outcome of the discussion on the `forum`_ is positive and you have a function or piece of code that does what you need it to do, what next? Before code is added to SciPy, it at least has to have good documentation, unit tests, benchmarks, and correct code style. @@ -120,13 +119,13 @@ Once you think your code is ready for inclusion in SciPy, you can send a pull request (PR) on Github. We won't go into the details of how to work with git here, this is described well in :ref:`git-development` and on the `Github help pages`_. When you send the PR for a new -feature, be sure to also mention this on the scipy-dev mailing list. This can +feature, be sure to also mention this on the scipy-dev `forum`_. This can prompt interested people to help review your PR. Assuming that you already got positive feedback before on the general idea of your code/feature, the purpose of the code review is to ensure that the code is correct, efficient and meets the requirements outlined above. In many cases, the code review happens relatively quickly, but it's possible that it stalls. If you have addressed -all feedback already given, it's perfectly fine to ask on the mailing list +all feedback already given, it's perfectly fine to ask on the `forum`_ again for review (after a reasonable amount of time, say a couple of weeks, has passed). Once the review is completed, the PR is merged into the "main" branch of SciPy. @@ -134,7 +133,7 @@ branch of SciPy. The above describes the requirements and process for adding code to SciPy. It doesn't yet answer the question though how decisions are made exactly. The basic answer is: decisions are made by consensus, by everyone who chooses to -participate in the discussion on the mailing list. This includes developers, +participate in the discussion on the `forum`_. This includes developers, other users and yourself. Aiming for consensus in the discussion is important -- SciPy is a project by and for the scientific Python community. In those rare cases that agreement cannot be reached, the maintainers of the module @@ -153,7 +152,7 @@ MIT, PSF) then it's OK. Code which is GPL or Apache licensed, has no clear license, requires citation or is free for academic use only can't be included in SciPy. Therefore if you copied existing code with such a license or made a direct translation to Python of it, your code can't be included. -If you're unsure, please ask on the scipy-dev `mailing list `_. +If you're unsure, please ask on the scipy-dev `forum`_. *Why is SciPy under the BSD license and not, say, the GPL?* @@ -183,7 +182,7 @@ The discussion on code style and unit testing above applies equally to bug fixes. It is usually best to start by writing a unit test that shows the problem, i.e. it should pass but doesn't. Once you have that, you can fix the code so that the test does pass. That should be enough to send a PR for this -issue. Unlike when adding new code, discussing this on the mailing list may +issue. Unlike when adding new code, discussing this on the `forum`_ may not be necessary - if the old behavior of the code is clearly incorrect, no one will object to having it fixed. It may be necessary to add some warning or deprecation message for the changed behavior. This should be part of the @@ -236,7 +235,7 @@ thoughts in a comment) allows prioritizing maintenance work and finding related issues easily when working on an existing function or subpackage. To read more about issue triage, see :ref:`triaging`. -Participating in discussions on the scipy-user and scipy-dev `mailing lists`_ is +Participating in discussions on the scipy-user and scipy-dev `forum`_ is a contribution in itself. Everyone who writes to those lists with a problem or an idea would like to get responses, and writing such responses makes the project and community function better and appear more welcoming. @@ -296,7 +295,7 @@ improvements, and submit your first PR! .. _Pytest: https://pytest.org/ -.. _mailing lists: https://scipy.org/community/#scipy-mailing-list +.. _forum: https://discuss.scientific-python.org/c/contributor/scipy .. _Spyder: https://www.spyder-ide.org/ diff --git a/doc/source/dev/missing-bits.rst b/doc/source/dev/missing-bits.rst index 903311066b51..a8d5b2483411 100644 --- a/doc/source/dev/missing-bits.rst +++ b/doc/source/dev/missing-bits.rst @@ -82,7 +82,7 @@ is simple enough to fully document all attributes immediately below its name. Some return classes are sufficiently complex to deserve their own rendered documentation. This is fairly standard if the return class is public, but return classes should only be public if 1) they are intended to be imported by -end-users and 2) if they have been approved by the mailing list. For complex, +end-users and 2) if they have been approved by the forum. For complex, private return classes, please see how `~scipy.stats.binomtest` summarizes `~scipy.stats._result_classes.BinomTestResult` and links to its documentation, and note that ``BinomTestResult`` cannot be imported from `~scipy.stats`. diff --git a/doc/source/dev/roadmap-detailed.rst b/doc/source/dev/roadmap-detailed.rst index bcdaf799f641..173e6f7511cb 100644 --- a/doc/source/dev/roadmap-detailed.rst +++ b/doc/source/dev/roadmap-detailed.rst @@ -18,9 +18,9 @@ going and where help is needed most. General ------- -This roadmap will be evolving together with SciPy. Updates can be submitted as -pull requests. For large or disruptive changes you may want to discuss -those first on the scipy-dev mailing list. +This roadmap will be evolving together with SciPy. Updates can be submitted as +pull requests. For large or disruptive changes you may want to discuss +those first on the scipy-dev forum. API changes diff --git a/doc/source/index.rst b/doc/source/index.rst index fda5aa05c79a..3dde480d9dcf 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -17,7 +17,7 @@ SciPy documentation `Source Repository `__ | `Issues & Ideas `__ | `Q&A Support `__ | -`Mailing List `__ +`Forum `__ **SciPy** (pronounced "Sigh Pie") is an open-source software for mathematics, science, and engineering. From f9b496224725a1d56b96635969060d3c240efc7b Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 4 Jun 2024 00:25:32 -0700 Subject: [PATCH 350/500] DOC: stats.bootstrap: warning admonition -> versionchanged [skip cirrus] [skip circle] --- scipy/stats/_resampling.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/scipy/stats/_resampling.py b/scipy/stats/_resampling.py index b4e06979bbda..d7d4d50a7b7f 100644 --- a/scipy/stats/_resampling.py +++ b/scipy/stats/_resampling.py @@ -333,12 +333,13 @@ def bootstrap(data, statistic, *, n_resamples=9999, batch=None, underlying distribution. Elements of `data` must be broadcastable to the same shape (with the possible exception of the dimension specified by `axis`). - .. warning:: Beginning in SciPy 1.14.0, `bootstrap` will emit a `FutureWarning` - if the shapes of the elements of `data` are not the same (with the - exception of the dimension specified by `axis`). - Beginning in SciPy 1.16.0, `bootstrap` will explicitly broadcast - the elements to the same shape (except along `axis) before - performing the calculation. + .. versionchanged:: 1.14.0 + `bootstrap` will now emit a ``FutureWarning`` if the shapes of the + elements of `data` are not the same (with the exception of the dimension + specified by `axis`). + Beginning in SciPy 1.16.0, `bootstrap` will explicitly broadcast the + elements to the same shape (except along `axis`) before performing + the calculation. statistic : callable Statistic for which the confidence interval is to be calculated. From d2b3067bc8f7c523bc9a94e238bfcf0fbc7ca104 Mon Sep 17 00:00:00 2001 From: Albert Steppi Date: Tue, 4 Jun 2024 04:17:13 -0400 Subject: [PATCH 351/500] DOC/DEV: add docs for enabling interactive examples (#20843) * Add release documentation on enabling interactive docs * Add docs on enabling interactive docs locally * Fix incorrect key name in try_examples.json --- .../contributor/rendering_documentation.rst | 25 +++++++++++++++++++ doc/source/dev/core-dev/releasing.rst.inc | 11 ++++++++ doc/source/try_examples.json | 2 +- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/doc/source/dev/contributor/rendering_documentation.rst b/doc/source/dev/contributor/rendering_documentation.rst index 4d952e998177..c938f4df4c87 100644 --- a/doc/source/dev/contributor/rendering_documentation.rst +++ b/doc/source/dev/contributor/rendering_documentation.rst @@ -69,6 +69,31 @@ To render the documentation on your own machine: with ``index.html`` and browse, or you can jump straight to the file you’re interested in. +**Interactive Examples** + +Examples within docstrings can be made interactive using ``jupyterlite-sphinx``. +The buttons for converting examples sections into embedded interactive +notebooks are hidden by default on clean docs builds. To enable interactive +examples after building the documentation locally, edit the +``ignore_patterns`` list in the runtime configuration file ``try_examples.json`` +within ``scipy/doc/build/html/``. The initial version of this file in a clean +documentation build is + +.. code-block:: json + + { + "global_min_height": "400px", + "ignore_patterns": [".*"] + } + +The buttons that turn docstring examples into embedded notebooks will be hidden +for all url paths matching the JavaScript Regex patterns in the +``ignore_patterns`` list. ``[".*"]`` includes a pattern which matches all url +paths. Removing this pattern from the list will enable interactivity for all +examples. See the documentation for the ``jupyterlite-sphinx`` +`TryExamples directive `_ +for more information. + .. note:: - Changes to certain documents do not take effect when Sphinx documentation diff --git a/doc/source/dev/core-dev/releasing.rst.inc b/doc/source/dev/core-dev/releasing.rst.inc index 8ef9e0883355..33e58cf92cdf 100644 --- a/doc/source/dev/core-dev/releasing.rst.inc +++ b/doc/source/dev/core-dev/releasing.rst.inc @@ -238,3 +238,14 @@ and test against their own code) and report issues on Github or Discourse. After the final release is done, port relevant changes to release notes, build scripts, author name mapping in ``tools/authors.py`` and any other changes that were only made on the maintenance branch to main. + +Enable interactive examples by editing the runtime configuration file, +``try_examples.json``, in the root folder of the uploaded documentation on the +release server. One must remove the regular expression pattern ``".*"`` from +the ``ignore_patterns`` list. + +.. code-block:: console + + $ ssh your-username@docs.scipy.org + $ cd /srv/docs_scipy_org/doc/scipy-1.13.1 + $ vim try_examples.json # edit the ignore list to remove: ".*" diff --git a/doc/source/try_examples.json b/doc/source/try_examples.json index f5429194157b..7e1b894774df 100644 --- a/doc/source/try_examples.json +++ b/doc/source/try_examples.json @@ -1,4 +1,4 @@ { - "min_height": "400px", + "global_min_height": "400px", "ignore_patterns": [".*"] } From 1abe5c1215023a4c3ed20c69a42099338ed7da93 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 4 Jun 2024 01:19:51 -0700 Subject: [PATCH 352/500] MAINT: optimize.differential_evolution: add fail_slow exception [skip ci] --- scipy/optimize/tests/test__differential_evolution.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/optimize/tests/test__differential_evolution.py b/scipy/optimize/tests/test__differential_evolution.py index 1c71d332ee65..536f928a8480 100644 --- a/scipy/optimize/tests/test__differential_evolution.py +++ b/scipy/optimize/tests/test__differential_evolution.py @@ -1420,6 +1420,7 @@ def c1(x): assert_(np.all(res.x >= np.array(bounds)[:, 0])) assert_(np.all(res.x <= np.array(bounds)[:, 1])) + @pytest.mark.fail_slow(5) def test_L9(self): # Lampinen ([5]) test problem 9 From 069015900f4a9885afa4b7d3a7e33e3be019274a Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Tue, 4 Jun 2024 09:22:22 +0100 Subject: [PATCH 353/500] ENH: stats: rewrite `ttest_rel` in terms of `ttest_1samp` (#20883) Co-authored-by: Matt Haberland Co-authored-by: Lucas Colley --- scipy/stats/_stats_py.py | 34 ++------------------------------- scipy/stats/tests/test_stats.py | 4 ++-- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index b485c4278953..0e2b2ead77ea 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -7326,38 +7326,8 @@ def ttest_rel(a, b, axis=0, nan_policy='propagate', alternative="two-sided"): TtestResult(statistic=-5.879467544540889, pvalue=7.540777129099917e-09, df=499) """ - a, b, axis = _chk2_asarray(a, b, axis) - - na = _get_len(a, axis, "first argument") - nb = _get_len(b, axis, "second argument") - if na != nb: - raise ValueError('unequal length arrays') - - if na == 0 or nb == 0: - # _axis_nan_policy decorator ensures this only happens with 1d input - NaN = _get_nan(a, b) - return TtestResult(NaN, NaN, df=NaN, alternative=NaN, - standard_error=NaN, estimate=NaN) - - n = a.shape[axis] - df = n - 1 - - d = (a - b).astype(np.float64) - v = _var(d, axis, ddof=1) - dm = np.mean(d, axis) - denom = np.sqrt(v / n) - - with np.errstate(divide='ignore', invalid='ignore'): - t = np.divide(dm, denom)[()] - prob = _get_pvalue(t, distributions.t(df), alternative, xp=np) - - # when nan_policy='omit', `df` can be different for different axis-slices - df = np.broadcast_to(df, t.shape)[()] - - # _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=dm) + return ttest_1samp(a - b, popmean=0, axis=axis, alternative=alternative, + _no_deco=True) # Map from names to lambda_ values used in power_divergence(). diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index bfec8dd8761d..e0044802e3be 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -5235,8 +5235,8 @@ def test_ttest_ind_scalar(): with suppress_warnings() as sup, np.errstate(invalid="ignore"): sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice") t, p = stats.ttest_ind(4., 3.) - assert_(np.isnan(t)) - assert_(np.isnan(p)) + assert np.isnan(t) + assert np.isnan(p) class Test_ttest_ind_permutations: From fc02da511b0e2ddf24a60344b5227d0ffe814e79 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Tue, 4 Jun 2024 09:31:00 +0100 Subject: [PATCH 354/500] DEP: interpolate: deprecate complex dtypes in `{Akima1D, Pchip}Interpolator` (#20868) --- scipy/interpolate/_cubic.py | 31 +++++---------------- scipy/interpolate/_rgi.py | 15 ++-------- scipy/interpolate/tests/test_interpolate.py | 22 +++++++-------- scipy/interpolate/tests/test_rgi.py | 2 +- 4 files changed, 22 insertions(+), 48 deletions(-) diff --git a/scipy/interpolate/_cubic.py b/scipy/interpolate/_cubic.py index 3277be0d0944..878a30904de7 100644 --- a/scipy/interpolate/_cubic.py +++ b/scipy/interpolate/_cubic.py @@ -4,8 +4,6 @@ from typing import TYPE_CHECKING -import warnings - import numpy as np from scipy.linalg import solve, solve_banded @@ -180,12 +178,6 @@ class PchipInterpolator(CubicHermiteSpline): A N-D array of real values. ``y``'s length along the interpolation axis must be equal to the length of ``x``. Use the ``axis`` parameter to select the interpolation axis. - - .. deprecated:: 1.13.0 - Complex data is deprecated and will raise an error in SciPy 1.15.0. - If you are trying to use the real components of the passed array, - use ``np.real`` on ``y``. - axis : int, optional Axis in the ``y`` array corresponding to the x-coordinate values. Defaults to ``axis=0``. @@ -249,11 +241,9 @@ def __init__(self, x, y, axis=0, extrapolate=None): x, _, y, axis, _ = prepare_input(x, y, axis) if np.iscomplexobj(y): msg = ("`PchipInterpolator` only works with real values for `y`. " - "Passing an array with a complex dtype for `y` is deprecated " - "and will raise an error in SciPy 1.15.0. If you are trying to " - "use the real components of the passed array, use `np.real` on " - "the array before passing to `PchipInterpolator`.") - warnings.warn(msg, DeprecationWarning, stacklevel=2) + "If you are trying to use the real components of the passed array, " + "use `np.real` on the array before passing to `PchipInterpolator`.") + raise ValueError(msg) xp = x.reshape((x.shape[0],) + (1,)*(y.ndim-1)) dk = self._find_derivatives(xp, y) super().__init__(x, y, dk, axis=0, extrapolate=extrapolate) @@ -409,12 +399,6 @@ class Akima1DInterpolator(CubicHermiteSpline): N-D array of real values. The length of ``y`` along the interpolation axis must be equal to the length of ``x``. Use the ``axis`` parameter to select the interpolation axis. - - .. deprecated:: 1.13.0 - Complex data is deprecated and will raise an error in SciPy 1.15.0. - If you are trying to use the real components of the passed array, - use ``np.real`` on ``y``. - axis : int, optional Axis in the ``y`` array corresponding to the x-coordinate values. Defaults to ``axis=0``. @@ -520,11 +504,10 @@ def __init__(self, x, y, axis=0, *, method: Literal["akima", "makima"]="akima", if np.iscomplexobj(y): msg = ("`Akima1DInterpolator` only works with real values for `y`. " - "Passing an array with a complex dtype for `y` is deprecated " - "and will raise an error in SciPy 1.15.0. If you are trying to " - "use the real components of the passed array, use `np.real` on " - "the array before passing to `Akima1DInterpolator`.") - warnings.warn(msg, DeprecationWarning, stacklevel=2) + "If you are trying to use the real components of the passed array, " + "use `np.real` on the array before passing to " + "`Akima1DInterpolator`.") + raise ValueError(msg) # Akima extrapolation historically False; parent class defaults to True. extrapolate = False if extrapolate is None else extrapolate diff --git a/scipy/interpolate/_rgi.py b/scipy/interpolate/_rgi.py index eb17bf9c8b57..afdb8ff399ed 100644 --- a/scipy/interpolate/_rgi.py +++ b/scipy/interpolate/_rgi.py @@ -1,7 +1,6 @@ __all__ = ['RegularGridInterpolator', 'interpn'] import itertools -import warnings import numpy as np @@ -70,12 +69,6 @@ class RegularGridInterpolator: The data on the regular grid in n dimensions. Complex data is accepted. - .. deprecated:: 1.13.0 - Complex data is deprecated with ``method="pchip"`` and will raise an - error in SciPy 1.15.0. This is because ``PchipInterpolator`` only - works with real values. If you are trying to use the real components of - the passed array, use ``np.real`` on ``values``. - method : str, optional The method of interpolation to perform. Supported are "linear", "nearest", "slinear", "cubic", "quintic" and "pchip". This @@ -286,12 +279,10 @@ def __init__(self, points, values, method="linear", bounds_error=True, if self._descending_dimensions: self.values = np.flip(values, axis=self._descending_dimensions) if self.method == "pchip" and np.iscomplexobj(self.values): - msg = ("`PchipInterpolator` only works with real values. Passing " - "complex-dtyped `values` with `method='pchip'` is deprecated " - "and will raise an error in SciPy 1.15.0. If you are trying to " - "use the real components of the passed array, use `np.real` on " + msg = ("`PchipInterpolator` only works with real values. If you are trying " + "to use the real components of the passed array, use `np.real` on " "the array before passing to `RegularGridInterpolator`.") - warnings.warn(msg, DeprecationWarning, stacklevel=2) + raise ValueError(msg) if method in self._SPLINE_METHODS_ndbspl: if solver_args is None: solver_args = {} diff --git a/scipy/interpolate/tests/test_interpolate.py b/scipy/interpolate/tests/test_interpolate.py index 82bddb7f0319..b551f469701a 100644 --- a/scipy/interpolate/tests/test_interpolate.py +++ b/scipy/interpolate/tests/test_interpolate.py @@ -9,7 +9,7 @@ from scipy.interpolate import (interp1d, interp2d, lagrange, PPoly, BPoly, splrep, splev, splantider, splint, sproot, Akima1DInterpolator, - NdPPoly, BSpline) + NdPPoly, BSpline, PchipInterpolator) from scipy.special import poch, gamma @@ -947,16 +947,16 @@ def test_extrapolate_attr(self): # Testing extrapoation to actual function. assert_allclose(y_ext, ak_true(x_ext), atol=1e-15) - def test_complex(self): - # Complex-valued data deprecated - x = np.arange(0., 11.) - y = np.array([0., 2., 1., 3., 2., 6., 5.5, 5.5, 2.7, 5.1, 3.]) - y = y - 2j*y - # actually raises ComplexWarning, which subclasses RuntimeWarning, see - # https://github.com/numpy/numpy/blob/main/numpy/exceptions.py - msg = "Passing an array with a complex.*|Casting complex values to real.*" - with pytest.warns((RuntimeWarning, DeprecationWarning), match=msg): - Akima1DInterpolator(x, y) + +@pytest.mark.parametrize("method", [Akima1DInterpolator, PchipInterpolator]) +def test_complex(method): + # Complex-valued data deprecated + x = np.arange(0., 11.) + y = np.array([0., 2., 1., 3., 2., 6., 5.5, 5.5, 2.7, 5.1, 3.]) + y = y - 2j*y + msg = "real values" + with pytest.raises(ValueError, match=msg): + method(x, y) class TestPPolyCommon: diff --git a/scipy/interpolate/tests/test_rgi.py b/scipy/interpolate/tests/test_rgi.py index ed4b146b3a12..5239c04f0bcb 100644 --- a/scipy/interpolate/tests/test_rgi.py +++ b/scipy/interpolate/tests/test_rgi.py @@ -947,7 +947,7 @@ def test_complex_pchip(self): sample = np.array([[1, 2.3, 5.3, 0.5, 3.3, 1.2, 3], [1, 3.3, 1.2, 4.0, 5.0, 1.0, 3]]).T - with pytest.deprecated_call(match='complex'): + with pytest.raises(ValueError, match='real'): interpn(points, values, sample, method='pchip') def test_complex_spline2fd(self): From dee9b0902f1f826906b18fa99b4cbd9e459c7e5d Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Tue, 4 Jun 2024 10:44:08 +0200 Subject: [PATCH 355/500] BUG: fix incorrect intersphinx-registry entry in environment.yml [skip cirrus] [skip circle] --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index f7fe2f48c11b..05bba0f3fc53 100644 --- a/environment.yml +++ b/environment.yml @@ -36,7 +36,7 @@ dependencies: - types-psutil # For building docs - sphinx - - intersphinx_registry + - intersphinx-registry - numpydoc - ipython - setuptools<67.3 # avoid pkg_resources deprecation warnings from MPL/scikit-umfpack From 9c14869261ee23d78b2ee14142a7544c7b8c3c42 Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Tue, 4 Jun 2024 14:46:44 +0530 Subject: [PATCH 356/500] DEV: Use const wherever possible in signal --- scipy/signal/_max_len_seq_inner.pyx | 6 +++--- scipy/signal/_peak_finding_utils.pyx | 14 +++++++------- scipy/signal/_spectral.pyx | 6 +++--- scipy/signal/_upfirdn_apply.pyx | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/scipy/signal/_max_len_seq_inner.pyx b/scipy/signal/_max_len_seq_inner.pyx index 8ce5bc4e72a2..b3734d20bfb8 100644 --- a/scipy/signal/_max_len_seq_inner.pyx +++ b/scipy/signal/_max_len_seq_inner.pyx @@ -11,10 +11,10 @@ np.import_array() @cython.cdivision(True) # faster modulo @cython.boundscheck(False) # designed to stay within bounds @cython.wraparound(False) # we don't use negative indexing -def _max_len_seq_inner(Py_ssize_t[::1] taps, - np.int8_t[::1] state, +def _max_len_seq_inner(const Py_ssize_t[::1] taps, + const np.int8_t[::1] state, Py_ssize_t nbits, Py_ssize_t length, - np.int8_t[::1] seq): + const np.int8_t[::1] seq): # Here we compute MLS using a shift register, indexed using a ring buffer # technique (faster than using something like np.roll to shift) cdef Py_ssize_t n_taps = taps.shape[0] diff --git a/scipy/signal/_peak_finding_utils.pyx b/scipy/signal/_peak_finding_utils.pyx index 997272cc60e7..16c67ecc7c14 100644 --- a/scipy/signal/_peak_finding_utils.pyx +++ b/scipy/signal/_peak_finding_utils.pyx @@ -88,8 +88,8 @@ def _local_maxima_1d(const np.float64_t[::1] x not None): return midpoints.base, left_edges.base, right_edges.base -def _select_by_peak_distance(np.intp_t[::1] peaks not None, - np.float64_t[::1] priority not None, +def _select_by_peak_distance(const np.intp_t[::1] peaks not None, + const np.float64_t[::1] priority not None, np.float64_t distance): """ Evaluate which peaks fulfill the distance condition. @@ -164,7 +164,7 @@ class PeakPropertyWarning(RuntimeWarning): def _peak_prominences(const np.float64_t[::1] x not None, - np.intp_t[::1] peaks not None, + const np.intp_t[::1] peaks not None, np.intp_t wlen): """ Calculate the prominence of each peak in a signal. @@ -262,11 +262,11 @@ def _peak_prominences(const np.float64_t[::1] x not None, def _peak_widths(const np.float64_t[::1] x not None, - np.intp_t[::1] peaks not None, + const np.intp_t[::1] peaks not None, np.float64_t rel_height, - np.float64_t[::1] prominences not None, - np.intp_t[::1] left_bases not None, - np.intp_t[::1] right_bases not None): + const np.float64_t[::1] prominences not None, + const np.intp_t[::1] left_bases not None, + const np.intp_t[::1] right_bases not None): """ Calculate the width of each each peak in a signal. diff --git a/scipy/signal/_spectral.pyx b/scipy/signal/_spectral.pyx index 76720f4175cc..0482a55a5a67 100644 --- a/scipy/signal/_spectral.pyx +++ b/scipy/signal/_spectral.pyx @@ -20,9 +20,9 @@ cdef extern from "math.h": double atan2(double, double) @cython.boundscheck(False) -def _lombscargle(np.ndarray[np.float64_t, ndim=1] x, - np.ndarray[np.float64_t, ndim=1] y, - np.ndarray[np.float64_t, ndim=1] freqs): +def _lombscargle(const np.ndarray[np.float64_t, ndim=1] x, + const np.ndarray[np.float64_t, ndim=1] y, + const np.ndarray[np.float64_t, ndim=1] freqs): """ _lombscargle(x, y, freqs) diff --git a/scipy/signal/_upfirdn_apply.pyx b/scipy/signal/_upfirdn_apply.pyx index 6419816b1984..27b2361cc4f7 100644 --- a/scipy/signal/_upfirdn_apply.pyx +++ b/scipy/signal/_upfirdn_apply.pyx @@ -274,7 +274,7 @@ cpdef _pad_test(np.ndarray[DTYPE_t] data, np.intp_t npre=0, np.intp_t npost=0, return np.asarray(out) -def _apply(np.ndarray data, DTYPE_t [::1] h_trans_flip, np.ndarray out, +def _apply(np.ndarray data, const DTYPE_t [::1] h_trans_flip, np.ndarray out, np.intp_t up, np.intp_t down, np.intp_t axis, np.intp_t mode, DTYPE_t cval): cdef ArrayInfo data_info, output_info From 84042c776d0b31110e62fbc10802902173eae926 Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Tue, 4 Jun 2024 15:55:42 +0530 Subject: [PATCH 357/500] DEV: Undo const usage for Windows CI --- scipy/signal/_max_len_seq_inner.pyx | 4 ++-- scipy/signal/_spectral.pyx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scipy/signal/_max_len_seq_inner.pyx b/scipy/signal/_max_len_seq_inner.pyx index b3734d20bfb8..f9bb116cefa6 100644 --- a/scipy/signal/_max_len_seq_inner.pyx +++ b/scipy/signal/_max_len_seq_inner.pyx @@ -12,9 +12,9 @@ np.import_array() @cython.boundscheck(False) # designed to stay within bounds @cython.wraparound(False) # we don't use negative indexing def _max_len_seq_inner(const Py_ssize_t[::1] taps, - const np.int8_t[::1] state, + np.int8_t[::1] state, Py_ssize_t nbits, Py_ssize_t length, - const np.int8_t[::1] seq): + np.int8_t[::1] seq): # Here we compute MLS using a shift register, indexed using a ring buffer # technique (faster than using something like np.roll to shift) cdef Py_ssize_t n_taps = taps.shape[0] diff --git a/scipy/signal/_spectral.pyx b/scipy/signal/_spectral.pyx index 0482a55a5a67..76720f4175cc 100644 --- a/scipy/signal/_spectral.pyx +++ b/scipy/signal/_spectral.pyx @@ -20,9 +20,9 @@ cdef extern from "math.h": double atan2(double, double) @cython.boundscheck(False) -def _lombscargle(const np.ndarray[np.float64_t, ndim=1] x, - const np.ndarray[np.float64_t, ndim=1] y, - const np.ndarray[np.float64_t, ndim=1] freqs): +def _lombscargle(np.ndarray[np.float64_t, ndim=1] x, + np.ndarray[np.float64_t, ndim=1] y, + np.ndarray[np.float64_t, ndim=1] freqs): """ _lombscargle(x, y, freqs) From 74f896f38367aa8e21c4389de0d983d9c1404d08 Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Tue, 4 Jun 2024 13:16:40 +0100 Subject: [PATCH 358/500] CI/DEV: fix Node.js 16 warnings by bumping actions (#20886) --- .github/workflows/array_api.yml | 2 +- .github/workflows/issue-labeler.yml | 2 +- .github/workflows/linux.yml | 6 +++--- .github/workflows/macos.yml | 10 +++++----- .github/workflows/pull-request-labeler.yml | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 1166702a5662..8d784adac15f 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -78,7 +78,7 @@ jobs: echo "timestamp=${NOW}" >> $GITHUB_OUTPUT - name: Setup compiler cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-ccache with: path: ${{ steps.prep-ccache.outputs.dir }} diff --git a/.github/workflows/issue-labeler.yml b/.github/workflows/issue-labeler.yml index cce256d207a3..aaf86160aec5 100644 --- a/.github/workflows/issue-labeler.yml +++ b/.github/workflows/issue-labeler.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: # label based on issue title - - uses: github/issue-labeler@v3.3 + - uses: github/issue-labeler@v3.4 if: github.repository == 'scipy/scipy' with: configuration-path: .github/labeler.yml diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 6a562606dac8..3e02ce7fa1fa 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -84,7 +84,7 @@ jobs: echo "timestamp=${NOW}" >> $GITHUB_OUTPUT - name: Setup compiler cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-ccache # Reference: https://docs.github.com/en/actions/guides/caching-dependencies-to-speed-up-workflows#matching-a-cache-key # NOTE: The caching strategy is modeled in a way that it will always have a unique cache key for each workflow run @@ -308,7 +308,7 @@ jobs: sudo apt-get install -y libgmp-dev libmpfr-dev libmpc-dev ccache gfortran - name: Caching Python dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache with: path: ~/.cache/pip @@ -335,7 +335,7 @@ jobs: echo "timestamp=${NOW}" >> $GITHUB_OUTPUT - name: Setup compiler cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-ccache with: path: ${{ steps.prep-ccache.outputs.dir }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index edb8a9859362..a27be838b703 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -57,7 +57,7 @@ jobs: echo "timestamp=${NOW}" >> $GITHUB_OUTPUT - name: Setup compiler cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-ccache # Reference: https://docs.github.com/en/actions/guides/caching-dependencies-to-speed-up-workflows#matching-a-cache-key # NOTE: The caching strategy is modeled in a way that it will always have @@ -92,7 +92,7 @@ jobs: shell: bash - name: Cache conda - uses: actions/cache@v3 + uses: actions/cache@v4 env: # Increase this value to reset cache if environment.yml has not changed CACHE_NUMBER: 1 @@ -151,12 +151,12 @@ jobs: python-version: ["3.11"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' @@ -201,7 +201,7 @@ jobs: submodules: recursive - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' diff --git a/.github/workflows/pull-request-labeler.yml b/.github/workflows/pull-request-labeler.yml index 687bd74a628c..27c81c234728 100644 --- a/.github/workflows/pull-request-labeler.yml +++ b/.github/workflows/pull-request-labeler.yml @@ -20,7 +20,7 @@ jobs: repo-token: "${{ secrets.GITHUB_TOKEN }}" configuration-path: ".github/label-globs.yml" # label based on PR title - - uses: github/issue-labeler@v3.3 + - uses: github/issue-labeler@v3.4 if: github.repository == 'scipy/scipy' with: configuration-path: .github/labeler.yml From 919f0471ede4c366cfb7266b334289b6ea4923ac Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 4 Jun 2024 05:22:55 -0700 Subject: [PATCH 359/500] ENH: stats: end-to-end array-API support for NHSTs with beta null distribution (#20793) * ENH: special.chdtr add array API support * ENH: stats._SimpleChi2: use chdtr for CDF * ENH: stats.special: array API support for betainc * ENH: stats.special: array API support for betaincc * ENH: stats._SimpleBeta: add and use _SimpleBeta distribution * TST: stats.mstats.pointbiserialr: skip test that is array-API incompatible --- scipy/special/__init__.py | 3 +- .../special/_support_alternative_backends.py | 44 ++++++++++++++++-- scipy/stats/_stats_py.py | 46 +++++++++++++++---- scipy/stats/tests/test_mstats_basic.py | 4 ++ 4 files changed, 84 insertions(+), 13 deletions(-) diff --git a/scipy/special/__init__.py b/scipy/special/__init__.py index a86d6f9f69f8..46de4200e4e2 100644 --- a/scipy/special/__init__.py +++ b/scipy/special/__init__.py @@ -810,7 +810,8 @@ def _load_libsf_error_state(): # Replace some function definitions from _ufuncs to add Array API support from ._support_alternative_backends import ( log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, - gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy, chdtrc) + gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy, + chdtr, chdtrc, betainc, betaincc) from . import _basic from ._basic import * diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 1be09d29cc2f..7a257aeb3cfc 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -13,7 +13,7 @@ from ._ufuncs import ( log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, # noqa: F401 gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy, # noqa: F401 - chdtrc # noqa: F401 + chdtr, chdtrc, betainc, betaincc # noqa: F401 ) _SCIPY_ARRAY_API = os.environ.get("SCIPY_ARRAY_API", False) @@ -81,11 +81,30 @@ def __xlogy(x, y, *, xp=xp): return __xlogy +def _chdtr(xp, spx): + # 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 = getattr(spx, 'gammainc', None) # noqa: F811 + if gammainc is None and hasattr(xp, 'special'): + gammainc = getattr(xp.special, 'gammainc', None) + if gammainc is None: + return None + + def __chdtr(v, x): + res = xp.where(x >= 0, gammainc(v/2, x/2), 0) + i_nan = ((x == 0) & (v == 0)) | xp.isnan(x) | xp.isnan(v) + res = xp.where(i_nan, xp.nan, res) + return res + return __chdtr + + def _chdtrc(xp, spx): # 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`.. + # return None here and use the SciPy version of `chdtrc`. gammaincc = getattr(spx, 'gammaincc', None) # noqa: F811 if gammaincc is None and hasattr(xp, 'special'): gammaincc = getattr(xp.special, 'gammaincc', None) @@ -100,9 +119,25 @@ def __chdtrc(v, x): return __chdtrc +def _betaincc(xp, spx): + betainc = getattr(spx, 'betainc', None) # noqa: F811 + if betainc is None and hasattr(xp, 'special'): + betainc = getattr(xp.special, 'betainc', None) + 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 + + _generic_implementations = {'rel_entr': _rel_entr, 'xlogy': _xlogy, - 'chdtrc': _chdtrc} + 'chdtr,': _chdtr, + 'chdtrc': _chdtrc, + 'betaincc': _betaincc, + } # functools.wraps doesn't work because: @@ -137,7 +172,10 @@ def wrapped(*args, **kwargs): 'entr': 1, 'rel_entr': 2, 'xlogy': 2, + 'chdtr': 2, 'chdtrc': 2, + 'betainc': 3, + 'betaincc': 3, } for f_name, n_array_args in array_special_func_map.items(): diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 0e2b2ead77ea..d322d99c340d 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -4919,11 +4919,9 @@ def statistic(x, y, axis): # 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. - # This needs to be done with NumPy arrays given the existing infrastructure. - ab = n/2 - 1 - dist = stats.beta(ab, ab, loc=-1, scale=2) - pvalue = _get_pvalue(np.asarray(r), dist, alternative, xp=np) - pvalue = xp.asarray(pvalue, dtype=dtype) + ab = xp.asarray(n/2 - 1) + dist = _SimpleBeta(ab, ab, loc=-1, scale=2) + pvalue = _get_pvalue(r, dist, alternative, xp=xp) r = r[()] if r.ndim == 0 else r pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue @@ -9259,9 +9257,9 @@ def combine_pvalues(pvalues, method='fisher', weights=None): symmetric=False, xp=np) elif method == 'pearson': statistic = 2 * np.sum(np.log1p(-pvalues)) - # _SimpleChi2 doesn't have `cdf` yet; - # add it when `combine_pvalues` is converted to array API - pval = distributions.chi2.cdf(-statistic, 2 * len(pvalues)) + chi2 = _SimpleChi2(2 * len(pvalues)) + pval = _get_pvalue(-statistic, chi2, alternative='less', + symmetric=False, xp=np) elif method == 'mudholkar_george': normalizing_factor = np.sqrt(3/len(pvalues))/np.pi statistic = -np.sum(np.log(pvalues)) + np.sum(np.log1p(-pvalues)) @@ -9271,7 +9269,9 @@ def combine_pvalues(pvalues, method='fisher', weights=None): * approx_factor, nu) elif method == 'tippett': statistic = np.min(pvalues) - pval = distributions.beta.cdf(statistic, 1, len(pvalues)) + beta = _SimpleBeta(1, len(pvalues)) + pval = _get_pvalue(statistic, beta, alternative='less', + symmetric=False, xp=np) elif method == 'stouffer': if weights is None: weights = np.ones_like(pvalues) @@ -10898,5 +10898,33 @@ class _SimpleChi2: 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) diff --git a/scipy/stats/tests/test_mstats_basic.py b/scipy/stats/tests/test_mstats_basic.py index 74701cdb8b62..68d0a8547140 100644 --- a/scipy/stats/tests/test_mstats_basic.py +++ b/scipy/stats/tests/test_mstats_basic.py @@ -475,6 +475,10 @@ def test_kendall_p_exact_large(self): 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] From 5ffdd8baad1fc208c4767c9e2f8e087f14d2f429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Mon, 3 Jun 2024 13:58:40 -0700 Subject: [PATCH 360/500] DOC: Write API reference titles in monospace font Also splits module, class and object information for better readability. [docs only] --- doc/source/_static/scipy.css | 14 ++++++++++++++ doc/source/_templates/autosummary/attribute.rst | 7 ++++++- doc/source/_templates/autosummary/class.rst | 7 ++++++- doc/source/_templates/autosummary/function.rst | 11 +++++++++++ doc/source/_templates/autosummary/method.rst | 9 +++++++-- .../_templates/autosummary/ndarray_subclass.rst | 7 ++++++- doc/source/_templates/autosummary/property.rst | 7 ++++++- 7 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 doc/source/_templates/autosummary/function.rst diff --git a/doc/source/_static/scipy.css b/doc/source/_static/scipy.css index e97cfc7066b7..b924c7cc0a24 100644 --- a/doc/source/_static/scipy.css +++ b/doc/source/_static/scipy.css @@ -83,3 +83,17 @@ div.admonition>.admonition-title { h1 { word-wrap: break-word; } + +/* Monospace titles for API docs */ +div.empty + section>h1 { + font-family: var(--pst-font-family-monospace); +} + +.prename { + font-family: var(--pst-font-family-monospace); + font-size: var(--pst-font-size-h4); +} + +.sig-prename { + display: none; +} diff --git a/doc/source/_templates/autosummary/attribute.rst b/doc/source/_templates/autosummary/attribute.rst index a176e99affe3..6c334f9d95db 100644 --- a/doc/source/_templates/autosummary/attribute.rst +++ b/doc/source/_templates/autosummary/attribute.rst @@ -1,8 +1,13 @@ :orphan: +.. raw:: html + +
{{ module }}.{{ class }}.
+
+ {{ fullname }} {{ underline }} .. currentmodule:: {{ module }} -.. autoattribute:: {{ objname }} \ No newline at end of file +.. autoattribute:: {{ objname }} diff --git a/doc/source/_templates/autosummary/class.rst b/doc/source/_templates/autosummary/class.rst index de8a20bb0768..e7637016a652 100644 --- a/doc/source/_templates/autosummary/class.rst +++ b/doc/source/_templates/autosummary/class.rst @@ -1,4 +1,9 @@ -{{ fullname }} +.. raw:: html + +
{{ module }}.
+
+ +{{ name }} {{ underline }} .. currentmodule:: {{ module }} diff --git a/doc/source/_templates/autosummary/function.rst b/doc/source/_templates/autosummary/function.rst new file mode 100644 index 000000000000..a0965e8bf1cb --- /dev/null +++ b/doc/source/_templates/autosummary/function.rst @@ -0,0 +1,11 @@ +.. raw:: html + +
{{ module }}.
+
+ +{{ name }} +{{ underline }} + +.. currentmodule:: {{ module }} + +.. autofunction:: {{ objname }} \ No newline at end of file diff --git a/doc/source/_templates/autosummary/method.rst b/doc/source/_templates/autosummary/method.rst index 6dce2229ed12..1e7e0c286c6f 100644 --- a/doc/source/_templates/autosummary/method.rst +++ b/doc/source/_templates/autosummary/method.rst @@ -1,8 +1,13 @@ :orphan: -{{ fullname }} +.. raw:: html + +
{{ module }}.{{ class }}.
+
+ +{{ name }} {{ underline }} .. currentmodule:: {{ module }} -.. automethod:: {{ objname }} \ No newline at end of file +.. automethod:: {{ objname }} diff --git a/doc/source/_templates/autosummary/ndarray_subclass.rst b/doc/source/_templates/autosummary/ndarray_subclass.rst index 1403e7eceecc..90bbcc2c10c9 100644 --- a/doc/source/_templates/autosummary/ndarray_subclass.rst +++ b/doc/source/_templates/autosummary/ndarray_subclass.rst @@ -1,4 +1,9 @@ -{{ fullname }} +.. raw:: html + +
{{ module }}.
+
+ +{{ name }} {{ underline }} .. currentmodule:: {{ module }} diff --git a/doc/source/_templates/autosummary/property.rst b/doc/source/_templates/autosummary/property.rst index 8c3d07350bde..d08c9de3d705 100644 --- a/doc/source/_templates/autosummary/property.rst +++ b/doc/source/_templates/autosummary/property.rst @@ -1,8 +1,13 @@ :orphan: +.. raw:: html + +
{{ module }}.{{ class }}.
+
+ {{ fullname }} {{ underline }} .. currentmodule:: {{ module }} -.. autoproperty:: {{ objname }} \ No newline at end of file +.. autoproperty:: {{ objname }} From b12bc585be1036cae0a1ccf862a9514bca59d489 Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Tue, 4 Jun 2024 20:45:58 +0530 Subject: [PATCH 361/500] DEV: Use const wherever possible in stats --- scipy/stats/_ansari_swilk_statistics.pyx | 6 ++-- scipy/stats/_qmc_cy.pyx | 42 ++++++++++++------------ scipy/stats/_rcont/rcont.pyx | 4 +-- scipy/stats/_sobol.pyx | 16 ++++----- scipy/stats/_stats.pyx | 12 +++---- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/scipy/stats/_ansari_swilk_statistics.pyx b/scipy/stats/_ansari_swilk_statistics.pyx index 43558a661bd5..7fd302288cd4 100644 --- a/scipy/stats/_ansari_swilk_statistics.pyx +++ b/scipy/stats/_ansari_swilk_statistics.pyx @@ -150,7 +150,7 @@ cdef inline void _start2(float[::1] a, int n) noexcept nogil: a[(ndo*2)-1] = 2 -cdef inline int _frqadd(float[::1] a, float[::1] b, int lenb, +cdef inline int _frqadd(float[::1] a, const float[::1] b, int lenb, int offset) noexcept nogil: """ Helper function for gscale function, see gscale docstring. @@ -206,7 +206,7 @@ cdef int _imply(float[::1] a, int curlen, int reslen, float[::1] b, return nextlenb -def swilk(double[::1] x, double[::1] a, bint init=False, int n1=-1): +def swilk(const double[::1] x, double[::1] a, bint init=False, int n1=-1): """ Calculates the Shapiro-Wilk W test and its significance level @@ -521,7 +521,7 @@ cdef double _ppnd(double p) noexcept: return (-temp if q < 0 else temp) #, 0 -cdef double _poly(double[::1]c, int nord, double x) noexcept nogil: +cdef double _poly(const double[::1] c, int nord, double x) noexcept nogil: """ Helper function for swilk function that evaluates polynomials. For some reason, the coefficients are given as diff --git a/scipy/stats/_qmc_cy.pyx b/scipy/stats/_qmc_cy.pyx index 72304005eadb..45749967e94d 100644 --- a/scipy/stats/_qmc_cy.pyx +++ b/scipy/stats/_qmc_cy.pyx @@ -38,27 +38,27 @@ from libcpp.vector cimport vector cdef mutex threaded_sum_mutex -def _cy_wrapper_centered_discrepancy(double[:, ::1] sample, bint iterative, +def _cy_wrapper_centered_discrepancy(const double[:, ::1] sample, bint iterative, workers): return centered_discrepancy(sample, iterative, workers) -def _cy_wrapper_wrap_around_discrepancy(double[:, ::1] sample, +def _cy_wrapper_wrap_around_discrepancy(const double[:, ::1] sample, bint iterative, workers): return wrap_around_discrepancy(sample, iterative, workers) -def _cy_wrapper_mixture_discrepancy(double[:, ::1] sample, +def _cy_wrapper_mixture_discrepancy(const double[:, ::1] sample, bint iterative, workers): return mixture_discrepancy(sample, iterative, workers) -def _cy_wrapper_l2_star_discrepancy(double[:, ::1] sample, +def _cy_wrapper_l2_star_discrepancy(const double[:, ::1] sample, bint iterative, workers): return l2_star_discrepancy(sample, iterative, workers) -cdef double centered_discrepancy(double[:, ::1] sample_view, +cdef double centered_discrepancy(const double[:, ::1] sample_view, bint iterative, unsigned int workers) noexcept nogil: cdef: Py_ssize_t n = sample_view.shape[0] @@ -85,7 +85,7 @@ cdef double centered_discrepancy(double[:, ::1] sample_view, + 1.0 / (n ** 2) * disc2) -cdef double centered_discrepancy_loop(double[:, ::1] sample_view, +cdef double centered_discrepancy_loop(const double[:, ::1] sample_view, Py_ssize_t istart, Py_ssize_t istop) noexcept nogil: cdef: @@ -106,7 +106,7 @@ cdef double centered_discrepancy_loop(double[:, ::1] sample_view, return disc2 -cdef double wrap_around_discrepancy(double[:, ::1] sample_view, +cdef double wrap_around_discrepancy(const double[:, ::1] sample_view, bint iterative, unsigned int workers) noexcept nogil: cdef: Py_ssize_t n = sample_view.shape[0] @@ -122,7 +122,7 @@ cdef double wrap_around_discrepancy(double[:, ::1] sample_view, return - (4.0 / 3.0) ** d + 1.0 / (n ** 2) * disc -cdef double wrap_around_loop(double[:, ::1] sample_view, +cdef double wrap_around_loop(const double[:, ::1] sample_view, Py_ssize_t istart, Py_ssize_t istop) noexcept nogil: cdef: @@ -140,7 +140,7 @@ cdef double wrap_around_loop(double[:, ::1] sample_view, return disc -cdef double mixture_discrepancy(double[:, ::1] sample_view, +cdef double mixture_discrepancy(const double[:, ::1] sample_view, bint iterative, unsigned int workers) noexcept nogil: cdef: Py_ssize_t n = sample_view.shape[0] @@ -169,7 +169,7 @@ cdef double mixture_discrepancy(double[:, ::1] sample_view, return disc - disc1 + disc2 -cdef double mixture_loop(double[:, ::1] sample_view, Py_ssize_t istart, +cdef double mixture_loop(const double[:, ::1] sample_view, Py_ssize_t istart, Py_ssize_t istop) noexcept nogil: cdef: @@ -192,7 +192,7 @@ cdef double mixture_loop(double[:, ::1] sample_view, Py_ssize_t istart, return disc2 -cdef double l2_star_discrepancy(double[:, ::1] sample_view, +cdef double l2_star_discrepancy(const double[:, ::1] sample_view, bint iterative, unsigned int workers) noexcept nogil: cdef: Py_ssize_t n = sample_view.shape[0] @@ -218,7 +218,7 @@ cdef double l2_star_discrepancy(double[:, ::1] sample_view, ) -cdef double l2_star_loop(double[:, ::1] sample_view, Py_ssize_t istart, +cdef double l2_star_loop(const double[:, ::1] sample_view, Py_ssize_t istart, Py_ssize_t istop) noexcept nogil: cdef: @@ -240,14 +240,14 @@ cdef double l2_star_loop(double[:, ::1] sample_view, Py_ssize_t istart, return disc2 -def _cy_wrapper_update_discrepancy(double[::1] x_new_view, - double[:, ::1] sample_view, +def _cy_wrapper_update_discrepancy(const double[::1] x_new_view, + const double[:, ::1] sample_view, double initial_disc): return c_update_discrepancy(x_new_view, sample_view, initial_disc) -cdef double c_update_discrepancy(double[::1] x_new_view, - double[:, ::1] sample_view, +cdef double c_update_discrepancy(const double[::1] x_new_view, + const double[:, ::1] sample_view, double initial_disc) noexcept: cdef: Py_ssize_t n = sample_view.shape[0] + 1 @@ -289,12 +289,12 @@ cdef double c_update_discrepancy(double[::1] x_new_view, return initial_disc + disc1 + disc2 + disc3 -ctypedef double (*func_type)(double[:, ::1], Py_ssize_t, +ctypedef double (*func_type)(const double[:, ::1], Py_ssize_t, Py_ssize_t) noexcept nogil cdef double threaded_loops(func_type loop_func, - double[:, ::1] sample_view, + const double[:, ::1] sample_view, unsigned int workers) noexcept nogil: cdef: Py_ssize_t n = sample_view.shape[0] @@ -325,7 +325,7 @@ cdef double threaded_loops(func_type loop_func, cdef void one_thread_loop(func_type loop_func, double& disc, - double[:, ::1] sample_view, + const double[:, ::1] sample_view, Py_ssize_t istart, Py_ssize_t istop, _) noexcept nogil: @@ -393,7 +393,7 @@ cdef _cy_van_der_corput_threaded_loop(Py_ssize_t istart, def _cy_van_der_corput_scrambled(Py_ssize_t n, long base, long start_index, - np.int64_t[:,::1] permutations, + const np.int64_t[:,::1] permutations, unsigned int workers): sequence = np.zeros(n) @@ -427,7 +427,7 @@ cdef _cy_van_der_corput_scrambled_loop(Py_ssize_t istart, Py_ssize_t istop, long base, long start_index, - np.int64_t[:,::1] permutations, + const np.int64_t[:,::1] permutations, double[::1] sequence_view): cdef: diff --git a/scipy/stats/_rcont/rcont.pyx b/scipy/stats/_rcont/rcont.pyx index c8cc9da7711e..c71f85818bda 100644 --- a/scipy/stats/_rcont/rcont.pyx +++ b/scipy/stats/_rcont/rcont.pyx @@ -41,7 +41,7 @@ cdef bitgen_t* get_bitgen(random_state): return PyCapsule_GetPointer(capsule, capsule_name) -def rvs_rcont1(tab_t[::1] row, tab_t[::1] col, tab_t ntot, +def rvs_rcont1(const tab_t[::1] row, const tab_t[::1] col, tab_t ntot, int size, random_state): cdef: @@ -69,7 +69,7 @@ def rvs_rcont1(tab_t[::1] row, tab_t[::1] col, tab_t ntot, return result -def rvs_rcont2(tab_t[::1] row, tab_t[::1] col, tab_t ntot, +def rvs_rcont2(const tab_t[::1] row, const tab_t[::1] col, tab_t ntot, int size, random_state): cdef: bitgen_t *rstate = get_bitgen(random_state) diff --git a/scipy/stats/_sobol.pyx b/scipy/stats/_sobol.pyx index 2b617270972d..a9e2599555b9 100644 --- a/scipy/stats/_sobol.pyx +++ b/scipy/stats/_sobol.pyx @@ -297,7 +297,7 @@ def _draw( num_gen, const int dim, const cnp.float64_t scale, - uint_32_64[:, ::1] sv, + const uint_32_64[:, ::1] sv, uint_32_64[::1] quasi, cnp.float64_t[:, ::1] sample ): @@ -314,7 +314,7 @@ cdef void draw( const uint_32_64 num_gen, const int dim, const cnp.float64_t scale, - uint_32_64[:, ::1] sv, + const uint_32_64[:, ::1] sv, uint_32_64[::1] quasi, cnp.float64_t[:, ::1] sample ) noexcept nogil: @@ -336,7 +336,7 @@ cdef void draw( cpdef void _fast_forward(const uint_32_64 n, const uint_32_64 num_gen, const int dim, - uint_32_64[:, ::1] sv, + const uint_32_64[:, ::1] sv, uint_32_64[::1] quasi) noexcept nogil: cdef int j, l cdef uint_32_64 num_gen_loc = num_gen @@ -350,7 +350,7 @@ cpdef void _fast_forward(const uint_32_64 n, @cython.boundscheck(False) @cython.wraparound(False) -cdef uint_32_64 cdot_pow2(uint_32_64[::1] a) noexcept nogil: +cdef uint_32_64 cdot_pow2(const uint_32_64[::1] a) noexcept nogil: cdef int i cdef int size = a.shape[0] cdef uint_32_64 z = 0 @@ -394,7 +394,7 @@ cpdef void _cscramble(const int dim, @cython.boundscheck(False) @cython.wraparound(False) -cpdef void _fill_p_cumulative(cnp.float_t[::1] p, +cpdef void _fill_p_cumulative(const cnp.float_t[::1] p, cnp.float_t[::1] p_cumulative) noexcept nogil: cdef int i cdef int len_p = p.shape[0] @@ -408,8 +408,8 @@ cpdef void _fill_p_cumulative(cnp.float_t[::1] p, @cython.boundscheck(False) @cython.wraparound(False) -cpdef void _categorize(cnp.float_t[::1] draws, - cnp.float_t[::1] p_cumulative, +cpdef void _categorize(const cnp.float_t[::1] draws, + const cnp.float_t[::1] p_cumulative, cnp.intp_t[::1] result) noexcept nogil: cdef int i cdef int n_p = p_cumulative.shape[0] @@ -420,7 +420,7 @@ cpdef void _categorize(cnp.float_t[::1] draws, @cython.boundscheck(False) @cython.wraparound(False) -cdef int _find_index(cnp.float_t[::1] p_cumulative, +cdef int _find_index(const cnp.float_t[::1] p_cumulative, const int size, const float value) noexcept nogil: cdef int l = 0 diff --git a/scipy/stats/_stats.pyx b/scipy/stats/_stats.pyx index 2274d114eb02..567a2bceadac 100644 --- a/scipy/stats/_stats.pyx +++ b/scipy/stats/_stats.pyx @@ -172,12 +172,12 @@ def _toint64(x): @cython.wraparound(False) @cython.boundscheck(False) -def _weightedrankedtau(ordered[:] x, ordered[:] y, intp_t[:] rank, weigher, bool additive): +def _weightedrankedtau(const ordered[:] x, const ordered[:] y, intp_t[:] rank, weigher, bool additive): # y_local and rank_local (declared below) are a work-around for a Cython # bug; see gh-16718. When we can require Cython 3.0, y_local and # rank_local can be removed, and the closure weigh() can refer directly # to y and rank. - cdef ordered[:] y_local = y + cdef const ordered[:] y_local = y cdef intp_t i, first cdef float64_t t, u, v, w, s, sq cdef int64_t n = np.int64(len(x)) @@ -377,8 +377,8 @@ def _transform_distance_matrix(distx, disty, global_corr='mgc', is_ranked=True): # MGC specific functions @cython.wraparound(False) @cython.boundscheck(False) -cdef _expected_covar(float64_t[:, :] distx, float64_t[:, :] disty, - int64_t[:, :] rank_distx, int64_t[:, :] rank_disty, +cdef _expected_covar(const float64_t[:, :] distx, const float64_t[:, :] disty, + const int64_t[:, :] rank_distx, const int64_t[:, :] rank_disty, float64_t[:, :] cov_xy, float64_t[:] expectx, float64_t[:] expecty): # summing up the element-wise product of A and B based on the ranks, @@ -712,8 +712,8 @@ ctypedef fused real: @cython.cdivision(True) @cython.boundscheck(False) cdef inline int gaussian_kernel_estimate_inner( - real[:, :] points_, real[:, :] values_, real[:, :] xi_, - real[:, :] estimate, real[:, :] cho_cov, + const real[:, :] points_, const real[:, :] values_, const real[:, :] xi_, + real[:, :] estimate, const real[:, :] cho_cov, int n, int m, int d, int p, ) noexcept nogil: cdef: From d0701172b95b7461d4b10c6ec155c4e61ca97545 Mon Sep 17 00:00:00 2001 From: Roman Nigmatullin Date: Tue, 4 Jun 2024 20:50:32 +0300 Subject: [PATCH 362/500] ENH: change Warning to ValueError --- scipy/sparse/_sputils.py | 10 ++++------ scipy/sparse/tests/test_sputils.py | 8 +++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/scipy/sparse/_sputils.py b/scipy/sparse/_sputils.py index b926d5dee449..d8b1f410652b 100644 --- a/scipy/sparse/_sputils.py +++ b/scipy/sparse/_sputils.py @@ -4,7 +4,6 @@ import sys from typing import Any, Literal, Optional, Union import operator -from warnings import warn import numpy as np from math import prod import scipy.sparse as sp @@ -127,12 +126,11 @@ def getdtype(dtype, a=None, default=None): raise TypeError("could not interpret data type") from e else: newdtype = np.dtype(dtype) - if newdtype == np.object_: - raise ValueError( - "object dtype is not supported by sparse matrices" - ) + if newdtype not in supported_dtypes: - warn(f"scipy.sparse does not support dtype {newdtype}", stacklevel=3) + supported_dtypes_fmt = ", ".join([t.__name__ for t in supported_dtypes]) + raise ValueError(f"scipy.sparse does not support dtype {newdtype.name}. " + f"The only supported types are: {supported_dtypes_fmt}.") return newdtype diff --git a/scipy/sparse/tests/test_sputils.py b/scipy/sparse/tests/test_sputils.py index 4545b49bea2c..e0e21b348a81 100644 --- a/scipy/sparse/tests/test_sputils.py +++ b/scipy/sparse/tests/test_sputils.py @@ -23,10 +23,16 @@ def test_getdtype(self): with assert_raises( ValueError, - match="object dtype is not supported by sparse matrices", + 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) From b536b40ef4b7f4884f6b75533d75843995054911 Mon Sep 17 00:00:00 2001 From: Roman Nigmatullin Date: Tue, 4 Jun 2024 21:59:44 +0300 Subject: [PATCH 363/500] FIX: removed unnecessary list construction --- scipy/sparse/_sputils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/sparse/_sputils.py b/scipy/sparse/_sputils.py index d8b1f410652b..48ab138244f5 100644 --- a/scipy/sparse/_sputils.py +++ b/scipy/sparse/_sputils.py @@ -128,7 +128,7 @@ def getdtype(dtype, a=None, default=None): newdtype = np.dtype(dtype) if newdtype not in supported_dtypes: - supported_dtypes_fmt = ", ".join([t.__name__ for t in supported_dtypes]) + supported_dtypes_fmt = ", ".join(t.__name__ for t in supported_dtypes) raise ValueError(f"scipy.sparse does not support dtype {newdtype.name}. " f"The only supported types are: {supported_dtypes_fmt}.") From 4f60de01c0de507838274589e4ecf5d772a1f54c Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 4 Jun 2024 12:50:23 -0700 Subject: [PATCH 364/500] ENH: stats: end-to-end array-API support for NHSTs with Student's t null distribution (#20884) --- scipy/special/__init__.py | 2 +- .../special/_support_alternative_backends.py | 19 +++++++++- .../test_support_alternative_backends.py | 3 +- scipy/stats/_stats_py.py | 35 +++++++++++++------ 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/scipy/special/__init__.py b/scipy/special/__init__.py index 46de4200e4e2..90c288585c8d 100644 --- a/scipy/special/__init__.py +++ b/scipy/special/__init__.py @@ -811,7 +811,7 @@ def _load_libsf_error_state(): from ._support_alternative_backends import ( log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy, - chdtr, chdtrc, betainc, betaincc) + chdtr, chdtrc, betainc, betaincc, stdtr) from . import _basic from ._basic import * diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 7a257aeb3cfc..dd5ee1fc755f 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -13,7 +13,7 @@ from ._ufuncs import ( log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, # noqa: F401 gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy, # noqa: F401 - chdtr, chdtrc, betainc, betaincc # noqa: F401 + chdtr, chdtrc, betainc, betaincc, stdtr # noqa: F401 ) _SCIPY_ARRAY_API = os.environ.get("SCIPY_ARRAY_API", False) @@ -132,11 +132,27 @@ def __betaincc(a, b, x): return __betaincc +def _stdtr(xp, spx): + betainc = getattr(spx, 'betainc', None) # noqa: F811 + if betainc is None and hasattr(xp, 'special'): + betainc = getattr(xp.special, 'betainc', None) + if betainc is None: + return None + + def __stdtr(df, t): + x = df / (t ** 2 + df) + tail = betainc(df / 2, xp.asarray(0.5), x) / 2 + return xp.where(x < 0, tail, 1 - tail) + + return __stdtr + + _generic_implementations = {'rel_entr': _rel_entr, 'xlogy': _xlogy, 'chdtr,': _chdtr, 'chdtrc': _chdtrc, 'betaincc': _betaincc, + 'stdtr': _stdtr, } @@ -176,6 +192,7 @@ def wrapped(*args, **kwargs): 'chdtrc': 2, 'betainc': 3, 'betaincc': 3, + 'stdtr': 2, } for f_name, n_array_args in array_special_func_map.items(): diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index fd5b16597da3..8485826f6668 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -50,7 +50,8 @@ def test_rel_entr_generic(dtype): @pytest.mark.fail_slow(5) @array_api_compatible @given(data=strategies.data()) -@pytest.mark.parametrize('f_name_n_args', array_special_func_map.items()) +# `reversed` is for developer convenience: test new function first = less waiting +@pytest.mark.parametrize('f_name_n_args', reversed(array_special_func_map.items())) def test_support_alternative_backends(xp, data, f_name_n_args): f_name, n_args = f_name_n_args diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index d322d99c340d..a8b06c5d6b3f 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -5540,7 +5540,8 @@ def spearmanr(a, b=None, axis=0, nan_policy='propagate', # errors before taking the square root t = rs * np.sqrt((dof/((rs+1.0)*(1.0-rs))).clip(0)) - prob = _get_pvalue(t, distributions.t(dof), alternative, xp=np) + 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): @@ -6457,12 +6458,9 @@ def ttest_1samp(a, popmean, axis=0, nan_policy="propagate", alternative="two-sid with np.errstate(divide='ignore', invalid='ignore'): t = xp.divide(d, denom) t = t[()] if t.ndim == 0 else t - # This will only work for CPU backends for now. That's OK. In time, - # `from_dlpack` will enable the transfer from other devices, and - # `_get_pvalue` will even be reworked to support the native backend. - t_np = np.asarray(t) - prob = _get_pvalue(t_np, distributions.t(df), alternative, xp=np) - prob = xp.asarray(prob, dtype=t.dtype) + + dist = _SimpleStudentT(xp.asarray(df, dtype=t.dtype)) + 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 @@ -6472,7 +6470,7 @@ def ttest_1samp(a, popmean, axis=0, nan_policy="propagate", alternative="two-sid 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=t_np, xp=xp) + statistic_np=xp.asarray(t), xp=xp) def _t_confidence_interval(df, t, confidence_level, alternative, dtype=None, xp=None): @@ -9112,7 +9110,7 @@ def brunnermunzel(x, y, alternative="two-sided", distribution="t", "(0/0). Try using `distribution='normal'") warnings.warn(message, RuntimeWarning, stacklevel=2) - distribution = distributions.t(df) + distribution = _SimpleStudentT(df) elif distribution == "normal": distribution = _SimpleNormal() else: @@ -10863,7 +10861,10 @@ def linregress(x, y=None, alternative='two-sided'): # n-2 degrees of freedom because 2 has been used up # to estimate the mean and standard deviation t = r * np.sqrt(df / ((1.0 - r + TINY)*(1.0 + r + TINY))) - prob = _get_pvalue(t, distributions.t(df), alternative) + + dist = _SimpleStudentT(df) + prob = _get_pvalue(t, dist, alternative, xp=np) + prob = prob[()] if prob.ndim == 0 else prob slope_stderr = np.sqrt((1 - r**2) * ssym / ssxm / df) @@ -10928,3 +10929,17 @@ def sf(self, x): 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) From bb43c8525579254d7996f2db3324ca63ff8ea709 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Wed, 5 Jun 2024 07:11:43 +1000 Subject: [PATCH 365/500] BLD: test delocate works by removing original lib (#20870) Closes gh-20852 Co-authored-by: Matti Picus --- .github/workflows/wheels.yml | 24 +++++++++++++----------- ci/cirrus_wheels.yml | 1 + tools/wheels/cibw_before_build_macos.sh | 11 +++++++++++ 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 5c0b253042f7..691c0a3963c8 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -152,17 +152,19 @@ jobs: echo "CIBW_ENVIRONMENT_MACOS=$CIBW" >> "$GITHUB_ENV" echo "REPAIR_PATH=$LIB_PATH" >> "$GITHUB_ENV" - if [[ ${{ matrix.buildplat[3] }} == 'accelerate' ]]; then - PREFIX=DYLD_LIBRARY_PATH=$(dirname $(gfortran --print-file-name libgfortran.dylib)) - else - # Use libgfortran and friends from the scipy-openblas wheel, - # which should be exactly the same as the ones from gfortran - # This will exclude the duplicates from gfortran in /opt/gfortran* - EXCLUDE="-e /gfortran" - fi - CIBW="$PREFIX delocate-listdeps {wheel} &&\ - $PREFIX delocate-wheel $EXCLUDE --require-archs \ - {delocate_archs} -w {dest_dir} {wheel}" + + PREFIX=DYLD_LIBRARY_PATH="\$(dirname \$(gfortran --print-file-name libgfortran.dylib))" + # remove libgfortran from location used for linking (if any), to + # check wheel has bundled things correctly and all tests pass without + # needing installed gfortran + POSTFIX=" sudo rm -rf /opt/gfortran-darwin-x86_64-native &&\ + sudo rm -rf /usr/local/gfortran/lib" + CIBW="$PREFIX delocate-listdeps -d {wheel} && echo "-----------" &&\ + $PREFIX delocate-wheel -v $EXCLUDE --require-archs \ + {delocate_archs} -w {dest_dir} {wheel} && echo "-----------" &&\ + delocate-listdeps -d {dest_dir}/*.whl && echo "-----------" &&\ + $POSTFIX" + # Rename x86 Accelerate wheel to test on macOS 13 runner if [[ ${{ matrix.buildplat[0] }} == 'macos-13' && ${{ matrix.buildplat[4] }} == '14.0' ]]; then CIBW+=" && mv {dest_dir}/\$(basename {wheel}) \ diff --git a/ci/cirrus_wheels.yml b/ci/cirrus_wheels.yml index ed7683f66daf..1dc44e153433 100644 --- a/ci/cirrus_wheels.yml +++ b/ci/cirrus_wheels.yml @@ -36,6 +36,7 @@ cirrus_wheels_linux_aarch64_task: PIP_PRE=1 PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple PIP_NO_BUILD_ISOLATION=false + PKG_CONFIG_PATH=/project build_script: | apt install -y python3-venv python-is-python3 diff --git a/tools/wheels/cibw_before_build_macos.sh b/tools/wheels/cibw_before_build_macos.sh index ec8df95cb53f..945ff19ba935 100644 --- a/tools/wheels/cibw_before_build_macos.sh +++ b/tools/wheels/cibw_before_build_macos.sh @@ -43,6 +43,7 @@ if [[ $PLATFORM == "x86_64" ]]; then export SDKROOT=${SDKROOT:-$(xcrun --show-sdk-path)} fi + if [[ $PLATFORM == "arm64" ]]; then curl -L https://github.com/fxcoudert/gfortran-for-macOS/releases/download/12.1-monterey/gfortran-ARM-12.1-Monterey.dmg -o gfortran.dmg GFORTRAN_SHA256=$(shasum -a 256 gfortran.dmg) @@ -62,3 +63,13 @@ fi # Install Openblas python -m pip install -r requirements/openblas.txt python -c "import scipy_openblas32; print(scipy_openblas32.get_pkg_config())" > $PROJECT_DIR/scipy-openblas.pc + +lib_loc=$(python -c"import scipy_openblas32; print(scipy_openblas32.get_lib_dir())") +# Use the libgfortran from gfortran rather than the one in the wheel +# since delocate gets confused if there is more than one +# https://github.com/scipy/scipy/issues/20852 +install_name_tool -change @loader_path/../.dylibs/libgfortran.5.dylib @rpath/libgfortran.5.dylib $lib_loc/libsci* +install_name_tool -change @loader_path/../.dylibs/libgcc_s.1.1.dylib @rpath/libgcc_s.1.1.dylib $lib_loc/libsci* +install_name_tool -change @loader_path/../.dylibs/libquadmath.0.dylib @rpath/libquadmath.0.dylib $lib_loc/libsci* + +codesign -s - -f $lib_loc/libsci* From 297b2c8796ef2fc98f5e097cac3dc170436e6442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Tue, 4 Jun 2024 18:04:14 -0700 Subject: [PATCH 366/500] DOC: stats: Convert sampling tutorial to MyST-md (#20303) Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Co-authored-by: Albert Steppi --- .gitignore | 1 + doc/Makefile | 11 +- doc/source/conf.py | 19 +- .../contributor/rendering_documentation.rst | 13 +- doc/source/dev/toolchain.rst | 24 +- doc/source/tutorial/index.rst | 15 - doc/source/tutorial/stats/sampling.md | 340 ++++++++++++++++++ doc/source/tutorial/stats/sampling.rst | 314 ---------------- environment.yml | 1 + 9 files changed, 390 insertions(+), 348 deletions(-) create mode 100644 doc/source/tutorial/stats/sampling.md delete mode 100644 doc/source/tutorial/stats/sampling.rst diff --git a/.gitignore b/.gitignore index 547eff51a193..5f9c22fbfca9 100644 --- a/.gitignore +++ b/.gitignore @@ -138,6 +138,7 @@ Thumbs.db doc/frontpage/build doc/source/reference/generated **/.ipynb_checkpoints +doc/source/_contents # Things specific to this project # ################################### diff --git a/doc/Makefile b/doc/Makefile index 0909ad02df7a..93a166c20dcc 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -108,11 +108,20 @@ upload: # Basic Sphinx generation rules for different formats #------------------------------------------------------------------------------ -html: version-check html-build +html: version-check convert-notebooks html-build html-build: mkdir -p build/html build/doctrees $(SPHINXBUILD) -WT --keep-going $(VERSIONWARNING) -b html $(ALLSPHINXOPTS) build/html $(FILES) +convert-notebooks: + mkdir -p source/_contents + for file in source/tutorial/stats/*.md; do \ + basename=$$(basename $$file .md); \ + output_name="source/_contents/$${basename}.ipynb"; \ + $(PYTHON) -m jupytext --output "$$output_name" $$file; \ + done + + coverage: build version-check mkdir -p build/coverage build/doctrees $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) build/coverage $(FILES) diff --git a/doc/source/conf.py b/doc/source/conf.py index 5753687b2562..ba383f566ec7 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -102,6 +102,9 @@ # List of directories, relative to source directories, that shouldn't be searched # for source files. exclude_dirs = [] +exclude_patterns = [ # glob-style + "**.ipynb", +] # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = False @@ -134,10 +137,6 @@ ("py:class", "v, remove specified key and return the corresponding value."), ] -exclude_patterns = [ # glob-style - -] - # be strict about warnings in our examples, we should write clean code # (exceptions permitted for pedagogical purposes below) warnings.resetwarnings() @@ -385,6 +384,18 @@ nb_execution_mode = "auto" # Ignore notebooks generated by jupyterlite-sphinx for interactive examples. nb_execution_excludepatterns = ["_contents/*.ipynb"] +# Prevent creation of transition syntax when adding footnotes +# See https://github.com/executablebooks/MyST-Parser/issues/352 +myst_footnote_transition = False +myst_enable_extensions = [ + "colon_fence", + "dollarmath", + "substitution", +] +nb_render_markdown_format = "myst" +render_markdown_format = "myst" +# Fix rendering of MathJax objects in Jupyter notebooks +myst_update_mathjax = False #------------------------------------------------------------------------------ # Interactive examples with jupyterlite-sphinx diff --git a/doc/source/dev/contributor/rendering_documentation.rst b/doc/source/dev/contributor/rendering_documentation.rst index c938f4df4c87..e712c715468c 100644 --- a/doc/source/dev/contributor/rendering_documentation.rst +++ b/doc/source/dev/contributor/rendering_documentation.rst @@ -109,6 +109,15 @@ for more information. This indicates that you're likely picking up the wrong SciPy install, check with ``python -c "import scipy; print(scipy.__file__)"``. + - The interactive examples are not available in CI builds. To see the + interactive examples locally in a JupyterLab environment, you can use + + .. code-block:: bash + + python -m http.server --directory doc/build/html + + The documentation pages should then be available at `http://localhost:8000/`. + .. _rendering-documentation-cloud: Checking Documentation on the Cloud @@ -127,11 +136,11 @@ on the cloud. Adding or editing tutorials as Jupyter notebooks ------------------------------------------------ -Under the ``doc/source/notebooks/`` folder of the SciPy tree you can find a few +Under the ``doc/source/`` folder of the SciPy tree you can find a few documents written in MyST-NB_ format. These files are executable, meaning that their content is executed when the SciPy documentation is built (locally or on CI) and any outputs generated by the execution are rendered in the final HTML -files, which you can see listed in :ref:`the user guide `. +files. If you have a document written in Jupyter notebook format (an ``.ipynb`` file) and would like to submit it as part of the SciPy documentation, there are two diff --git a/doc/source/dev/toolchain.rst b/doc/source/dev/toolchain.rst index b3f932c4e097..436f7f7b153a 100644 --- a/doc/source/dev/toolchain.rst +++ b/doc/source/dev/toolchain.rst @@ -467,18 +467,18 @@ asv (airspeed velocity) Recent https://asv.readthedocs.io/ Building the Documentation -------------------------- -==================== ================================================= - Tool Version -==================== ================================================= -Sphinx Whatever recent versions work. >= 5.0. -intersphinx_registry Whatever recent versions work. -PyData Sphinx theme Whatever recent versions work. >= 0.15.2. -Sphinx-Design Whatever recent versions work. >= 0.4.0. -numpydoc Whatever recent versions work. >= 1.5.0. -matplotlib Generally suggest >= 3.5. -MyST-NB Whatever recent versions work. >= 0.17.1 -jupyterlite-sphinx Whatever recent versions work. >= 0.12.0 -==================== ================================================= +============================ ================================================= + Tool Version +============================ ================================================= +Sphinx Whatever recent versions work. >= 5.0. +PyData Sphinx theme Whatever recent versions work. >= 0.15.2. +Sphinx-Design Whatever recent versions work. >= 0.4.0. +numpydoc Whatever recent versions work. >= 1.5.0. +matplotlib Generally suggest >= 3.5. +MyST-NB Whatever recent versions work. >= 0.17.1 +jupyterlite-sphinx Whatever recent versions work. >= 0.13.1 +jupyterlite-pyodide-kernel Whatever recent versions work. >= 0.1.0 +============================ ================================================= .. note:: diff --git a/doc/source/tutorial/index.rst b/doc/source/tutorial/index.rst index c20ab5da69c7..d089843ec190 100644 --- a/doc/source/tutorial/index.rst +++ b/doc/source/tutorial/index.rst @@ -68,21 +68,6 @@ Below, you can find the complete user guide organized by subpackages. ndimage io - -.. _executable-tutorials: - -Executable tutorials --------------------- - -Below you can also find tutorials in -`MyST Markdown `_ format. -These can be opened as Jupyter Notebooks with the help of the -`Jupytext `_ extension. - -.. toctree:: - :caption: Executable tutorials - :maxdepth: 1 - .. raw:: latex \addtocontents{toc}{\protect\setcounter{tocdepth}{1}} diff --git a/doc/source/tutorial/stats/sampling.md b/doc/source/tutorial/stats/sampling.md new file mode 100644 index 000000000000..62f614fb7965 --- /dev/null +++ b/doc/source/tutorial/stats/sampling.md @@ -0,0 +1,340 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +```{eval-rst} +.. jupyterlite:: ../../_contents/sampling.ipynb + :new_tab: True +``` +(non-uniform-random-number-sampling)= +# Universal Non-Uniform Random Number Sampling in SciPy + +SciPy provides an interface to many universal non-uniform random number +generators to sample random variates from a wide variety of univariate +continuous and discrete distributions. Implementations of a fast C library +called [UNU.RAN](http://statmath.wu.ac.at/software/unuran/) are used +for speed and performance. Please look at [UNU.RAN's +documentation](http://statmath.wu.ac.at/software/unuran/doc/unuran.html) +for an in-depth explanation of these methods. It is heavily referred to +for writing this tutorial and the documentation of all the generators. + +## Introduction + +Random variate generation is the small field of research that deals with +algorithms to generate random variates from various distributions. It is +common to assume that a uniform random number generator is available. +This is a program that produces a sequence of independent and identically +distributed continuous U(0,1) random variates (i.e. uniform random variates +on the interval (0,1)). Of course, real-world computers can never generate +ideal random numbers and they cannot produce numbers of arbitrary precision +but state-of-the-art uniform random number generators come close to this +aim. Thus random variate generation deals with the problem of transforming +such a sequence of U(0,1) random numbers into non-uniform random variates. +These methods are universal and work in a black-box fashion. + +Some methods to do that are: + +* The Inversion method: When the inverse $F^{-1}$ of the cumulative + distribution function is known, then random variate generation is easy. + We just generate a uniformly U(0,1) distributed random number U and + return $X = F^{-1}(U)$. As closed form solutions for the inverse + are rarely available, one usually needs to rely on approximations of + the inverse (e.g. {class}`scipy.special.ndtri`, + {class}`scipy.special.stdtrit`). In general, the implementation of special + functions is quite slow compared to the inversion methods in UNU.RAN. +* The Rejection Method: The rejection method, often called + acceptance-rejection method, has been suggested by John von Neumann in + 1951[^1]. It involves computing an upper bound to the PDF (also called the + hat function) and using the inversion method to generate a random + variate, say Y, from this bound. Then a uniform random number can be + drawn between 0 to the value of the upper bound at Y. If this number + is less than the PDF at Y, return the sample otherwise reject it. See + {class}`scipy.stats.sampling.TransformedDensityRejection`. +* The Ratio-of-Uniforms Method: This is a type of acceptance-rejection + method which is uses minimal bounding rectangles to construct the hat + function. See {class}`scipy.stats.sampling.RatioUniforms`. +* Inversion for Discrete Distributions: The difference compared to the + continuous case is that $F$ is now a step-function. To realize + this in a computer, a search algorithm is used, the simplest of which + is *sequential search*. A uniform random number is generated from + U(0, 1) and probabilities are summed until the cumulative probability + exceeds the uniform random number. The index at which this happens is + the required random variate and is returned. + + +More details on these algorithms can be found in the [appendix of the UNU.RAN +user manual](http://statmath.wu.ac.at/software/unuran/doc/unuran.html#RVG). + +When generating random variates of a distribution, two factors are important +to determine the speed of a generator: the setup step and the actual sampling. +Depending on the situation, different generators can be optimal. For example, +if one repeatedly needs to draw large samples from a given distribution with +a fixed shape parameter, a slow setup is acceptable if the sampling is fast. +This is called the fixed parameter case. If one aims to generate samples of +a distribution for different shape parameters (the varying parameter case), +an expensive setup that needs to be repeated for each parameter would lead +to very poor performance. In such a situation, a fast setup is crucial to +achieve good performance. An overview of the setup and sampling speed of the +different methods is shown in the table below. + +(unuran-methods-summary)= + +Methods for continuous distributions | Required Inputs | Optional Inputs | Setup Speed | Sampling Speed +------------------------------------- | --------------- | --------------- | ----------- | -------------- +{class}`~.stats.sampling.TransformedDensityRejection` | pdf, dpdf | none | slow | fast +{class}`scipy.stats.sampling.NumericalInverseHermite` | cdf | pdf, dpdf | (very) slow | (very) fast +{class}`scipy.stats.sampling.NumericalInversePolynomial` | pdf | cdf | (very) slow | (very) fast +{class}`scipy.stats.sampling.SimpleRatioUniforms` | pdf | none | fast | slow + +where + +- pdf: probability density function +- dpdf: derivative of the pdf +- cdf: cumulative distribution function + +To apply the numerical inversion method NumericalInversePolynomial to a large +number of continuous distributions in SciPy with minimal effort, take a look +at {class}`scipy.stats.sampling.FastGeneratorInversion`. + +Methods for discrete distributions | Required Inputs | Optional Inputs | Setup Speed | Sampling Speed +----------------------------------- | --------------- | --------------- | ----------- | -------------- +{class}`scipy.stats.sampling.DiscreteAliasUrn` | pv | pmf | slow | very fast +{class}`scipy.stats.sampling.DiscreteGuideTable` | pv | pmf | slow | very fast + + +where + +- pv: probability vector +- pmf: probability mass function + + +For more details on the generators implemented in UNU.RAN, please refer to [^2] and [^3]. + +## Basic concepts of the Interface + +Every generator needs to be set up before one can start sampling from it. +This can be done by instantiating an object of that class. Most of the +generators take a distribution object as input which contains the implementation +of required methods like PDF, CDF, etc. In addition to the distribution +object, one can also pass parameters used to set up the generator. It is also +possible to truncate the distributions using a `domain` parameter. All +generators need a stream of uniform random numbers that are transformed into +random variates of the given distribution. This is done by passing a `random_state` +parameter with a NumPy BitGenerator as the uniform random number generator. +`random_state` can either be a integer, {class}`numpy.random.Generator`, +or {class}`numpy.random.RandomState`. + +```{warning} + Use of NumPy < 1.19.0 is discouraged as it doesn't have a fast + Cython API for generating uniform random numbers and might be + too slow for practical use. +``` + +All the generators have a common `rvs` method that can be used to draw +samples from the given distribution. + +An example of this interface is shown below: + +```{code-cell} ipython3 +from scipy.stats.sampling import TransformedDensityRejection +from math import exp +import numpy as np + +class StandardNormal: + def pdf(self, x: float) -> float: + # note that the normalization constant isn't required + return exp(-0.5 * x*x) + def dpdf(self, x: float) -> float: + return -x * exp(-0.5 * x*x) + +dist = StandardNormal() +urng = np.random.default_rng() +rng = TransformedDensityRejection(dist, random_state=urng) +``` + +As shown in the example, we first initialize a distribution object that +contains an implementation of the methods required by the generator. In +our case, we use the {class}`~.stats.sampling.TransformedDensityRejection` (TDR) +method which requires a PDF and its derivative w.r.t. `x` (i.e. the variate). + +```{note} + + Note that the methods of the distribution (i.e. `pdf`, `dpdf`, etc) need not + be vectorized. They should accept and return floats. +``` + +```{note} + + One can also pass the SciPy distributions as arguments. However, note that the + object doesn't always have all the information required by some generators + like the derivative of PDF for the TDR method. Relying on SciPy distributions + might also reduce performance due to the vectorization of the methods like + `pdf` and `cdf`. In both cases, one can implement a custom distribution object + that contains all the required methods and that is not vectorized as shown in + the example above. If one wants to apply a numerical inversion method to a + distribution defined in SciPy, please also take a look at + {class}`scipy.stats.sampling.FastGeneratorInversion`. +``` + +In the above example, we have set up an object of the +{class}`~.stats.sampling.TransformedDensityRejection` method to sample from a +standard normal distribution. Now, we can start sampling from our +distribution by calling the `rvs` method: + +```{code-cell} ipython3 +rng.rvs() +``` + +```{code-cell} ipython3 +rng.rvs((5, 3)) +``` + +We can also check that the samples are drawn from the correct distribution +by visualizing the histogram of the samples: + +```{code-cell} ipython3 +--- +mystnb: + image: + alt: This code generates an X-Y plot with the probability distribution function + of X on the Y axis and values of X on the X axis. A red trace showing the true + distribution is a typical normal distribution with tails near zero at the edges + and a smooth peak around the center near 0.4. A blue bar graph of random variates + is shown below the red trace with a distribution similar to the truth, but with + clear imperfections. +--- +import numpy as np +import matplotlib.pyplot as plt +from scipy.stats import norm +from scipy.stats.sampling import TransformedDensityRejection +from math import exp + +class StandardNormal: + def pdf(self, x: float) -> float: + # note that the normalization constant isn't required + return exp(-0.5 * x*x) + def dpdf(self, x: float) -> float: + return -x * exp(-0.5 * x*x) + +dist = StandardNormal() +urng = np.random.default_rng() +rng = TransformedDensityRejection(dist, random_state=urng) +rvs = rng.rvs(size=1000) +x = np.linspace(rvs.min()-0.1, rvs.max()+0.1, num=1000) +fx = norm.pdf(x) +plt.plot(x, fx, 'r-', lw=2, label='true distribution') +plt.hist(rvs, bins=20, density=True, alpha=0.8, label='random variates') +plt.xlabel('x') +plt.ylabel('PDF(x)') +plt.title('Transformed Density Rejection Samples') +plt.legend() +plt.show() +``` + +````{note} + + Please note the difference between the `rvs` method of the distributions + present in {mod}`scipy.stats` and the one provided by these generators. + UNU.RAN generators must be considered independent in a sense that they will + generally produce a different stream of random numbers than the one produced + by the equivalent distribution in {mod}`scipy.stats` for any seed. The + implementation of `rvs` in {class}`scipy.stats.rv_continuous` usually relies + on the NumPy module {mod}`numpy.random` for well-known distributions (e.g., + for the normal distribution, the beta distribution) and transformations of + other distributions (e.g., normal inverse Gaussian + {class}`scipy.stats.norminvgauss` and the lognormal + {class}`scipy.stats.lognorm` distribution). If no specific method is + implemented, {class}`scipy.stats.rv_continuous` defaults to a numerical + inversion method of the CDF that is very slow. As UNU.RAN transforms uniform + random numbers differently than SciPy or NumPy, the resulting stream of RVs is + different even for the same stream of uniform random numbers. For example, the + random number stream of SciPy's {class}`scipy.stats.norm` and UNU.RAN's + {class}`~.stats.sampling.TransformedDensityRejection` would not be the same + even for the same `random_state`: + + ```python + from scipy.stats.sampling import norm, TransformedDensityRejection + from copy import copy + dist = StandardNormal() + urng1 = np.random.default_rng() + urng1_copy = copy(urng1) + rng = TransformedDensityRejection(dist, random_state=urng1) + rng.rvs() + # -1.526829048388144 + norm.rvs(random_state=urng1_copy) + # 1.3194816698862635 + ``` +```` + +We can pass a `domain` parameter to truncate the distribution: + +```{code-cell} ipython3 +rng = TransformedDensityRejection(dist, domain=(-1, 1), random_state=urng) +rng.rvs((5, 3)) +``` + +Invalid and bad arguments are handled either by SciPy or by UNU.RAN. The +latter throws a {class}`~.stats.sampling.UNURANError` that follows a common format: + +``` +UNURANError: [objid: ] : => +``` + +where: + +- `` is the ID of the object given by UNU.RAN +- `` is an error code representing a type of error. +- `` is the reason why the error occurred. +- `` is a short description of the type of error. + +The `` shows what caused the error. This, by itself, should contain +enough information to help debug the error. In addition, `` and +`` can be used to investigate different classes of error in +UNU.RAN. A complete list of all the error codes and their descriptions can be +found in the [Section 8.4 of the UNU.RAN user +manual](http://statmath.wu.ac.at/software/unuran/doc/unuran.html#Errno). + +An example of an error generated by UNU.RAN is shown below: + +``` +UNURANError: [objid: TDR.003] 50 : PDF(x) < 0.! => (generator) (possible) invalid data +``` + +This shows that UNU.RAN failed to initialize an object with ID `TDR.003` +because the PDF was < 0. i.e. negative. This falls under the type +"possible invalid data for the generator" and has error code 50. + +Warnings thrown by UNU.RAN also follow the same format. + +## Generators in {mod}`scipy.stats.sampling` + +```{toctree} +:maxdepth: 1 + +sampling_tdr +sampling_dau +sampling_pinv +sampling_dgt +sampling_hinv +sampling_srou +``` + +## References + +[^1]: Von Neumann, John. "13. various techniques used in connection with + random digits." Appl. Math Ser 12.36-38 (1951): 3. + +[^2]: UNU.RAN User Manual, + +[^3]: Leydold, Josef, Wolfgang Hörmann, and Halis Sak. "An R Interface to + the UNU.RAN Library for Universal Random Variate Generators.", + diff --git a/doc/source/tutorial/stats/sampling.rst b/doc/source/tutorial/stats/sampling.rst deleted file mode 100644 index b406133f689a..000000000000 --- a/doc/source/tutorial/stats/sampling.rst +++ /dev/null @@ -1,314 +0,0 @@ -.. _non-uniform-random-number-sampling: - -===================================================== -Universal Non-Uniform Random Number Sampling in SciPy -===================================================== - -.. currentmodule:: scipy.stats.sampling - -SciPy provides an interface to many universal non-uniform random number -generators to sample random variates from a wide variety of univariate -continuous and discrete distributions. Implementations of a fast C library -called `UNU.RAN `__ are used -for speed and performance. Please look at -`UNU.RAN's documentation `__ -for an in-depth explanation of these methods. It is heavily referred to -for writing this tutorial and the documentation of all the generators. - - -Introduction ------------- - -Random variate generation is the small field of research that deals with -algorithms to generate random variates from various distributions. It is -common to assume that a uniform random number generator is available. -This is a program that produces a sequence of independent and identically -distributed continuous U(0,1) random variates (i.e. uniform random variates -on the interval (0,1)). Of course, real-world computers can never generate -ideal random numbers and they cannot produce numbers of arbitrary precision -but state-of-the-art uniform random number generators come close to this -aim. Thus random variate generation deals with the problem of transforming -such a sequence of U(0,1) random numbers into non-uniform random variates. -These methods are universal and work in a black-box fashion. - -Some methods to do that are: - -* The Inversion method: When the inverse :math:`F^{-1}` of the cumulative - distribution function is known, then random variate generation is easy. - We just generate a uniformly U(0,1) distributed random number U and - return :math:`X = F^{-1}(U)`. As closed form solutions for the inverse - are rarely available, one usually needs to rely on approximations of - the inverse (e.g. :class:`~scipy.special.ndtri`, - :class:`~scipy.special.stdtrit`). In general, the implementation of special - functions is quite slow compared to the inversion methods in UNU.RAN. -* The Rejection Method: The rejection method, often called - acceptance-rejection method, has been suggested by John von Neumann in - 1951 [1]_. It involves computing an upper bound to the PDF (also called the - hat function) and using the inversion method to generate a random - variate, say Y, from this bound. Then a uniform random number can be - drawn between 0 to the value of the upper bound at Y. If this number - is less than the PDF at Y, return the sample otherwise reject it. See - :class:`~TransformedDensityRejection`. -* Inversion for Discrete Distributions: The difference compared to the - continuous case is that :math:`F` is now a step-function. To realize - this in a computer, a search algorithm is used, the simplest of which - is *sequential search*. A uniform random number is generated from - U(0, 1) and probabilities are summed until the cumulative probability - exceeds the uniform random number. The index at which this happens is - the required random variate and is returned. - - -More details on these algorithms can be found in the `appendix of the UNU.RAN -user manual `__. - -When generating random variates of a distribution, two factors are important -to determine the speed of a generator: the setup step and the actual sampling. -Depending on the situation, different generators can be optimal. For example, -if one repeatedly needs to draw large samples from a given distribution with -a fixed shape parameter, a slow setup is acceptable if the sampling is fast. -This is called the fixed parameter case. If one aims to generate samples of -a distribution for different shape parameters (the varying parameter case), -an expensive setup that needs to be repeated for each parameter would lead -to very poor performance. In such a situation, a fast setup is crucial to -achieve good performance. An overview of the setup and sampling speed of the -different methods is shown in the table below. - -.. _unuran-methods-summary: - -===================================== =============== =============== =========== ============== -Methods for continuous distributions Required Inputs Optional Inputs Setup Speed Sampling Speed -===================================== =============== =============== =========== ============== -:class:`~TransformedDensityRejection` pdf, dpdf none slow fast -:class:`~NumericalInverseHermite` cdf pdf, dpdf (very) slow (very) fast -:class:`~NumericalInversePolynomial` pdf cdf (very) slow (very) fast -:class:`~SimpleRatioUniforms` pdf none fast slow -===================================== =============== =============== =========== ============== - -where - -* pdf: probability density function -* dpdf: derivative of the pdf -* cdf: cumulative distribution function - -To apply the numerical inversion method NumericalInversePolynomial to a large -number of continuous distributions in SciPy with minimal effort, take a look -at `scipy.stats.sampling.FastGeneratorInversion`. - -===================================== =============== =============== =========== ============== -Methods for discrete distributions Required Inputs Optional Inputs Setup Speed Sampling Speed -===================================== =============== =============== =========== ============== -:class:`~DiscreteAliasUrn` pv pmf slow very fast -:class:`~DiscreteGuideTable` pv pmf slow very fast -===================================== =============== =============== =========== ============== - -where - -* pv: probability vector -* pmf: probability mass function - - -For more details on the generators implemented in UNU.RAN, please refer to [2]_ and [3]_. - -Basic concepts of the Interface -------------------------------- - -Every generator needs to be set up before one can start sampling from it. -This can be done by instantiating an object of that class. Most of the -generators take a distribution object as input which contains the implementation -of required methods like PDF, CDF, etc. In addition to the distribution -object, one can also pass parameters used to set up the generator. It is also -possible to truncate the distributions using a ``domain`` parameter. All -generators need a stream of uniform random numbers that are transformed into -random variates of the given distribution. This is done by passing a ``random_state`` -parameter with a NumPy BitGenerator as the uniform random number generator. -``random_state`` can either be a integer, `numpy.random.Generator`, -or `numpy.random.RandomState`. - -.. warning:: Use of NumPy < 1.19.0 is discouraged as it doesn't have a fast - Cython API for generating uniform random numbers and might be - too slow for practical use. - -All the generators have a common ``rvs`` method that can be used to draw -samples from the given distribution. - -An example of this interface is shown below: - - >>> from scipy.stats.sampling import TransformedDensityRejection - >>> from math import exp - >>> - >>> class StandardNormal: - ... def pdf(self, x: float) -> float: - ... # note that the normalization constant isn't required - ... return exp(-0.5 * x*x) - ... def dpdf(self, x: float) -> float: - ... return -x * exp(-0.5 * x*x) - ... - >>> dist = StandardNormal() - >>> - >>> import numpy as np - >>> urng = np.random.default_rng() - >>> rng = TransformedDensityRejection(dist, random_state=urng) - -As shown in the example, we first initialize a distribution object that -contains an implementation of the methods required by the generator. In -our case, we use the :class:`~TransformedDensityRejection` (TDR) method -which requires a PDF and its derivative w.r.t. ``x`` (i.e. the variate). - -.. note:: Note that the methods of the distribution (i.e. ``pdf``, - ``dpdf``, etc) need not be vectorized. They should - accept and return floats. - -.. note:: One can also pass the SciPy distributions as arguments. However, - note that the object doesn't always have all the information - required by some generators like the derivative of PDF for the - TDR method. Relying on SciPy distributions might also reduce - performance due to the vectorization of the methods like - ``pdf`` and ``cdf``. In both cases, one can implement a - custom distribution object that contains all the required - methods and that is not vectorized as shown in the example - above. If one wants to apply a numerical inversion method to - a distribution defined in SciPy, please also take a look at - `scipy.stats.sampling.FastGeneratorInversion`. - -In the above example, we have set up an object of the -:class:`~TransformedDensityRejection` method to sample from a -standard normal distribution. Now, we can start sampling from our -distribution by calling the ``rvs`` method: - - >>> rng.rvs() - -1.526829048388144 - >>> rng.rvs((5, 3)) - array([[ 2.06206883, 0.15205036, 1.11587367], - [-0.30775562, 0.29879802, -0.61858268], - [-1.01049115, 0.78853694, -0.23060766], - [-0.60954752, 0.29071797, -0.57167182], - [ 0.9331694 , -0.95605208, 1.72195199]]) - -We can also check that the samples are drawn from the correct distribution -by visualizing the histogram of the samples: - -.. plot:: - :alt: "This code generates an X-Y plot with the probability distribution function of X on the Y axis and values of X on the X axis. A red trace showing the true distribution is a typical normal distribution with tails near zero at the edges and a smooth peak around the center near 0.4. A blue bar graph of random variates is shown below the red trace with a distribution similar to the truth, but with clear imperfections." - - >>> import matplotlib.pyplot as plt - >>> from scipy.stats import norm - >>> from scipy.stats.sampling import TransformedDensityRejection - >>> from math import exp - >>> - >>> class StandardNormal: - ... def pdf(self, x: float) -> float: - ... # note that the normalization constant isn't required - ... return exp(-0.5 * x*x) - ... def dpdf(self, x: float) -> float: - ... return -x * exp(-0.5 * x*x) - ... - >>> - >>> dist = StandardNormal() - >>> urng = np.random.default_rng() - >>> rng = TransformedDensityRejection(dist, random_state=urng) - >>> rvs = rng.rvs(size=1000) - >>> x = np.linspace(rvs.min()-0.1, rvs.max()+0.1, num=1000) - >>> fx = norm.pdf(x) - >>> plt.plot(x, fx, 'r-', lw=2, label='true distribution') - >>> plt.hist(rvs, bins=20, density=True, alpha=0.8, label='random variates') - >>> plt.xlabel('x') - >>> plt.ylabel('PDF(x)') - >>> plt.title('Transformed Density Rejection Samples') - >>> plt.legend() - >>> plt.show() - -.. note:: Please note the difference between the `rvs` method of the - distributions present in :mod:`scipy.stats` and the one provided - by these generators. UNU.RAN generators must be considered - independent in a sense that they will generally produce a different - stream of random numbers than the one produced by the equivalent - distribution in :mod:`scipy.stats` for any seed. The implementation - of `rvs` in `scipy.stats.rv_continuous` usually relies on the NumPy - module `numpy.random` for well-known distributions (e.g., for the normal - distribution, the beta distribution) and transformations of other - distributions (e.g., normal inverse Gaussian `scipy.stats.norminvgauss` and the - lognormal `scipy.stats.lognorm` distribution). If no specific method is implemented, - `scipy.stats.rv_continuous` defaults to a numerical inversion method of the CDF - that is very slow. As UNU.RAN transforms uniform random numbers - differently than SciPy or NumPy, the resulting stream of RVs is - different even for the same stream of uniform random numbers. For - example, the random number stream of SciPy's ``scipy.stats.norm`` and UNU.RAN's - :class:`~TransformedDensityRejection` would not be the same even for - the same ``random_state``: - - >>> from scipy.stats import norm - >>> from scipy.stats.sampling import TransformedDensityRejection - >>> from copy import copy - >>> dist = StandardNormal() - >>> urng1 = np.random.default_rng() - >>> urng1_copy = copy(urng1) - >>> rng = TransformedDensityRejection(dist, random_state=urng1) - >>> rng.rvs() - -1.526829048388144 - >>> norm.rvs(random_state=urng1_copy) - 1.3194816698862635 - -We can pass a ``domain`` parameter to truncate the distribution: - - >>> rng = TransformedDensityRejection(dist, domain=(-1, 1), random_state=urng) - >>> rng.rvs((5, 3)) - array([[-0.99865691, 0.38104014, 0.31633526], # may vary - [ 0.88433909, -0.45181849, 0.78574461], - [ 0.3337244 , 0.12924307, 0.40499404], - [-0.51865761, 0.43252222, -0.6514866 ], - [-0.82666174, 0.71525582, 0.49006743]]) - -Invalid and bad arguments are handled either by SciPy or by UNU.RAN. The -latter throws a :class:`~UNURANError` that follows a common format: - -``UNURANError: [objid: ] : => `` - -where: - -* ```` is the ID of the object given by UNU.RAN -* ```` is an error code representing a type of error. -* ```` is the reason why the error occurred. -* ```` is a short description of the type of error. - -The ```` shows what caused the error. This, by itself, should contain -enough information to help debug the error. In addition, ```` and -```` can be used to investigate different classes of error in -UNU.RAN. A complete list of all the error codes and their descriptions can be -found in the `Section 8.4 of the UNU.RAN user manual -`__. - -An example of an error generated by UNU.RAN is shown below: - -``UNURANError: [objid: TDR.003] 50 : PDF(x) < 0.! => (generator) (possible) invalid data`` - -This shows that UNU.RAN failed to initialize an object with ID ``TDR.003`` -because the PDF was < 0. i.e. negative. This falls under the type -"possible invalid data for the generator" and has error code 50. - -Warnings thrown by UNU.RAN also follow the same format. - - -Generators in :mod:`scipy.stats.sampling` ------------------------------------------ -.. toctree:: - :maxdepth: 1 - - sampling_tdr - sampling_dau - sampling_pinv - sampling_dgt - sampling_hinv - sampling_srou - - -References -~~~~~~~~~~ - -.. [1] Von Neumann, John. "13. various techniques used in connection with - random digits." Appl. Math Ser 12.36-38 (1951): 3. - -.. [2] UNU.RAN User Manual, https://statmath.wu.ac.at/unuran/doc/unuran.html - -.. [3] Leydold, Josef, Wolfgang Hörmann, and Halis Sak. "An R Interface to - the UNU.RAN Library for Universal Random Variate Generators.", - https://cran.r-project.org/web/packages/Runuran/vignettes/Runuran.pdf diff --git a/environment.yml b/environment.yml index 05bba0f3fc53..0d3b7eeaad0e 100644 --- a/environment.yml +++ b/environment.yml @@ -46,6 +46,7 @@ dependencies: - jupytext - myst-nb - jupyterlite-sphinx>=0.13.1 + - jupyterlite-pyodide-kernel # Some optional test dependencies - mpmath - gmpy2 From f928e648fe9493596cc68f2a877e15ca28ac40b3 Mon Sep 17 00:00:00 2001 From: Henry Lunn Date: Wed, 5 Jun 2024 05:18:04 +0100 Subject: [PATCH 367/500] DOC: sparse: Correct `todense` documentation (#20242) * Correct todense documentation * Fix docstring for sparse matrices * Added optional parameters to todense method * Fix infinite recursion Co-authored-by: Dan Schult * Remove array/matrix from array-only docstring --------- Co-authored-by: Dan Schult Co-authored-by: CJ Carey --- scipy/sparse/_base.py | 24 +++++++++++------------- scipy/sparse/_matrix.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 7a451505cfd2..3f6531e6f4b1 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -947,7 +947,7 @@ def _getrow(self, i): def todense(self, order=None, out=None): """ - Return a dense representation of this sparse array/matrix. + Return a dense representation of this sparse array. Parameters ---------- @@ -959,21 +959,19 @@ def todense(self, order=None, out=None): 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 array/matrix on which you are calling the - method. + 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 : numpy.matrix, 2-D - A NumPy matrix object with the same shape and containing - the same data represented by the sparse array/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. + 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)) diff --git a/scipy/sparse/_matrix.py b/scipy/sparse/_matrix.py index 1ab874942383..5350b2a767c8 100644 --- a/scipy/sparse/_matrix.py +++ b/scipy/sparse/_matrix.py @@ -111,3 +111,35 @@ def getrow(self, i): 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) From 554b1c0a13eac5c6bb6b836c9ea467b35e0e8e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 5 Jun 2024 02:59:43 -0500 Subject: [PATCH 368/500] CI: Add workflow to build and upload free-threaded wheels (#20882) --- .github/workflows/free_threaded_wheels.yml | 160 +++++++++++++++++++++ pyproject.toml | 1 + tools/wheels/cibw_before_build_linux.sh | 12 ++ tools/wheels/cibw_before_test.sh | 15 ++ 4 files changed, 188 insertions(+) create mode 100644 .github/workflows/free_threaded_wheels.yml create mode 100644 tools/wheels/cibw_before_test.sh diff --git a/.github/workflows/free_threaded_wheels.yml b/.github/workflows/free_threaded_wheels.yml new file mode 100644 index 000000000000..ecb0800dc8ad --- /dev/null +++ b/.github/workflows/free_threaded_wheels.yml @@ -0,0 +1,160 @@ +# Workflow to build and test wheels for the free-threaded Python build. +# +# This should be merged back into wheels.yml when free-threaded wheel +# builds can be uploaded to pypi along with the rest of scipy's release +# artifacts. +# +# To work on the wheel building infrastructure on a fork, comment out: +# +# if: github.repository == 'scipy/scipy' +# +# in the get_commit_message job. Be sure to include [wheel build] in your commit +# message to trigger the build. All files related to wheel building are located +# at tools/wheels/ +name: Free-Threaded Wheel Builder + +on: + schedule: + # ┌───────────── minute (0 - 59) + # │ ┌───────────── hour (0 - 23) + # │ │ ┌───────────── day of the month (1 - 31) + # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) + # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) + # │ │ │ │ │ + - cron: "9 9 * * *" + push: + branches: + - maintenance/** + pull_request: + branches: + - main + - maintenance/** + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + get_commit_message: + name: Get commit message + runs-on: ubuntu-latest + if: github.repository == 'scipy/scipy' + outputs: + message: ${{ steps.commit_message.outputs.message }} + steps: + - name: Checkout scipy + uses: actions/checkout@v4.1.1 + # Gets the correct commit message for pull request + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Get commit message + id: commit_message + run: | + set -xe + COMMIT_MSG=$(git log --no-merges -1) + RUN="0" + if [[ "$COMMIT_MSG" == *"[wheel build]"* ]]; then + RUN="1" + fi + echo "message=$RUN" >> $GITHUB_OUTPUT + echo github.ref ${{ github.ref }} + + build_wheels: + name: Wheel, ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }} + ${{ matrix.buildplat[2] }} ${{ matrix.buildplat[3] }} + ${{ matrix.buildplat[4] }} + needs: get_commit_message + if: >- + contains(needs.get_commit_message.outputs.message, '1') || + github.event_name == 'schedule' || + github.event_name == 'workflow_dispatch' + runs-on: ${{ matrix.buildplat[0] }} + strategy: + # Ensure that a wheel builder finishes even if another fails + fail-fast: false + matrix: + # Github Actions doesn't support pairing matrix values together, let's improvise + # https://github.com/github/feedback/discussions/7835#discussioncomment-1769026 + buildplat: + - [ubuntu-22.04, manylinux, x86_64, "", ""] + - [ubuntu-22.04, musllinux, x86_64, "", ""] + # TODO: build scipy and set up Windows and MacOS + # cibuildwheel does not yet support Mac for free-threaded python + # windows is supported but numpy doesn't build on the image yet + python: [["cp313t", '3.13']] + env: + IS_32_BIT: ${{ matrix.buildplat[2] == 'x86' }} + # upload to staging if it's a push to a maintenance branch and the last + # commit message contains '[wheel build]' + IS_PUSH: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/heads/maintenance') && contains(needs.get_commit_message.outputs.message, '1') }} + IS_SCHEDULE_DISPATCH: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} + steps: + - name: Checkout scipy + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + submodules: true + + # Used to push the built wheels + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: "3.x" + + - name: Build wheels + uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + env: + CIBW_PRERELEASE_PYTHONS: True + CIBW_FREE_THREADED_SUPPORT: True + CIBW_BUILD: ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }}* + CIBW_ARCHS: ${{ matrix.buildplat[2] }} + # TODO: remove along with installing build deps in + # cibw_before_build.sh when a released cython can build numpy + CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" + + - uses: actions/upload-artifact@v4 + with: + path: ./wheelhouse/*.whl + name: ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }} + ${{ matrix.buildplat[2] }} ${{ matrix.buildplat[3] }} + ${{ matrix.buildplat[4] }} + + - uses: mamba-org/setup-micromamba@422500192359a097648154e8db4e39bdb6c6eed7 + with: + # for installation of anaconda-client, required for upload to + # anaconda.org + # Note that this step is *after* specific pythons have been used to + # build and test the wheel + # for installation of anaconda-client, for upload to anaconda.org + # environment will be activated after creation, and in future bash steps + init-shell: bash + environment-name: upload-env + create-args: >- + anaconda-client + + - name: Upload wheels + if: success() + shell: bash -el {0} + # see https://github.com/marketplace/actions/setup-miniconda for why + # `-el {0}` is required. + env: + SCIPY_STAGING_UPLOAD_TOKEN: ${{ secrets.SCIPY_STAGING_UPLOAD_TOKEN }} + SCIPY_NIGHTLY_UPLOAD_TOKEN: ${{ secrets.SCIPY_NIGHTLY_UPLOAD_TOKEN }} + run: | + conda install -y anaconda-client + source tools/wheels/upload_wheels.sh + set_upload_vars + # For cron jobs (restricted to main branch) or "Run workflow" trigger + # an upload to: + # + # https://anaconda.org/scientific-python-nightly-wheels/scipy + # + # Pushes to a maintenance branch that contain '[wheel build]' will + # cause wheels to be built and uploaded to: + # + # https://anaconda.org/multibuild-wheels-staging/scipy + # + # The tokens were originally generated at anaconda.org + upload_wheels diff --git a/pyproject.toml b/pyproject.toml index 6a0873770e57..d4e47db6f403 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,6 +134,7 @@ test-requires = [ "pooch", "hypothesis", ] +before-test = "bash {project}/tools/wheels/cibw_before_test.sh {project}" test-command = "bash {project}/tools/wheels/cibw_test_command.sh {project}" [tool.cibuildwheel.linux] diff --git a/tools/wheels/cibw_before_build_linux.sh b/tools/wheels/cibw_before_build_linux.sh index 1b98aeb2d858..66e8c2265142 100755 --- a/tools/wheels/cibw_before_build_linux.sh +++ b/tools/wheels/cibw_before_build_linux.sh @@ -17,6 +17,18 @@ printenv # Update license cat $PROJECT_DIR/tools/wheels/LICENSE_linux.txt >> $PROJECT_DIR/LICENSE.txt +# TODO: delete along with enabling build isolation by unsetting +# CIBW_BUILD_FRONTEND when scipy is buildable under free-threaded +# python with a released version of cython +FREE_THREADED_BUILD="$(python -c"import sysconfig; print(bool(sysconfig.get_config_var('Py_GIL_DISABLED')))")" +if [[ $FREE_THREADED_BUILD == "True" ]]; then + python -m pip install -U --pre pip + python -m pip install git+https://github.com/cython/cython + python -m pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + # python -m pip install git+https://github.com/serge-sans-paille/pythran + python -m pip install ninja meson-python pybind11 pythran +fi + # Install Openblas python -m pip install -r requirements/openblas.txt python -c "import scipy_openblas32; print(scipy_openblas32.get_pkg_config())" > $PROJECT_DIR/scipy-openblas.pc diff --git a/tools/wheels/cibw_before_test.sh b/tools/wheels/cibw_before_test.sh new file mode 100644 index 000000000000..e7f586e81255 --- /dev/null +++ b/tools/wheels/cibw_before_test.sh @@ -0,0 +1,15 @@ +set -ex + +FREE_THREADED_BUILD="$(python -c"import sysconfig; print(bool(sysconfig.get_config_var('Py_GIL_DISABLED')))")" +if [[ $FREE_THREADED_BUILD == "True" ]]; then + # TODO: delete when numpy is buildable under free-threaded python + # with a released version of cython + python -m pip install -U --pre pip + python -m pip install git+https://github.com/cython/cython + python -m pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + + # TODO: delete when importing numpy no longer enables the GIL + # setting to zero ensures the GIL is disabled while running the + # tests under free-threaded python + export PYTHON_GIL=0 +fi From 0a434830b8ace4f3db6dc98e6453762de3b8db79 Mon Sep 17 00:00:00 2001 From: m-maggi <124086916+m-maggi@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:53:12 +0200 Subject: [PATCH 369/500] DOC: ndimage.convolve: modify `origin` param description (#20408) Co-authored-by: Gregory Lee --- scipy/ndimage/_filters.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scipy/ndimage/_filters.py b/scipy/ndimage/_filters.py index 635b2d336b34..ed9687c5c14d 100644 --- a/scipy/ndimage/_filters.py +++ b/scipy/ndimage/_filters.py @@ -883,11 +883,13 @@ def convolve(input, weights, output=None, mode='reflect', cval=0.0, cval : scalar, optional Value to fill past edges of input if `mode` is 'constant'. Default is 0.0 - origin : int, optional - Controls the origin of the input signal, which is where the - filter is centered to produce the first element of the output. - Positive values shift the filter to the right, and negative values - shift the filter to the left. Default is 0. + origin : int or sequence, optional + Controls the placement of the filter on the input array's pixels. + A value of 0 (the default) centers the filter over the pixel, with + positive values shifting the filter to the right, and negative ones + to the left. By passing a sequence of origins with length equal to + the number of dimensions of the input array, different shifts can + be specified along each axis. Returns ------- From d8f96860461718355249fe9db66e2f6d16b4a480 Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Wed, 5 Jun 2024 11:05:26 -0700 Subject: [PATCH 370/500] MAINT: sparse: Align matmul tests in `test_base.py` for spmatrix and sparray (#20889) * make test_base uniformly use @ over * for matmul unless testing it * flesh out inplace matmul tests * fix test of dense @= due to numpy change in 1.25 * include review suggestions --- scipy/sparse/_data.py | 3 +- scipy/sparse/tests/test_base.py | 78 ++++++++++++++++++++++----------- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/scipy/sparse/_data.py b/scipy/sparse/_data.py index 7cf95becb5bc..9c4a2957e237 100644 --- a/scipy/sparse/_data.py +++ b/scipy/sparse/_data.py @@ -56,8 +56,7 @@ def __imul__(self, other): # self *= other if isscalarlike(other): self.data *= other return self - else: - return NotImplemented + return NotImplemented def __itruediv__(self, other): # self /= other if isscalarlike(other): diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index ea7b2636c7d4..4cffa3feab35 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -1685,7 +1685,7 @@ def test_small_multiplication(self): assert_equal(A @ np.ones((1, 1)), array([[1], [2], [3]])) assert_equal(A @ np.ones((1, 0)), np.ones((3, 0))) - def test_start_vs_at_sign_for_sparray_and_spmatrix(self): + def test_star_vs_at_sign_for_sparray_and_spmatrix(self): # test that * is matmul for spmatrix and mul for sparray A = self.spcreator([[1],[2],[3]]) @@ -1775,10 +1775,6 @@ def test_matmul(self): 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()) - if not isinstance(M, sparray): - 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) @@ -1803,7 +1799,7 @@ def test_matvec(self): 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.__mul__, x) + assert_raises(ValueError, M.__matmul__, x) # The current relationship between sparse matrix products and array # products is as follows: @@ -2272,21 +2268,45 @@ def test_inplace_dense(self): y -= b assert_array_equal(x, y) - x = a.copy() - y = a.copy() if isinstance(b, sparray): - assert_raises(ValueError, operator.imul, x, b.T) + # Elementwise multiply from __rmul__ + x = a.copy() + y = a.copy() + with assert_raises(ValueError, match="dimension mismatch"): + x *= b.T x = x * a y *= b + assert_array_equal(x, y) else: - # This is matrix product, from __rmul__ - assert_raises(ValueError, operator.imul, x, b) + # Matrix Product from __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) + assert_array_equal(x, y) - # Matrix (non-elementwise) floor division is not defined - assert_raises(TypeError, operator.ifloordiv, x, b) + # Now matrix product, from __rmatmul__ + y = a.copy() + # skip this test if numpy doesn't support __imatmul__ yet. + # move out of the try/except once numpy 1.24 is no longer supported. + try: + y @= b.T + except TypeError: + pass + else: + 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): @@ -2347,15 +2367,21 @@ def test_inplace_success(self): bp = bp + a assert_allclose(b.toarray(), bp.toarray()) - b *= a - bp = bp * a + if isinstance(b, sparray): + 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()) - assert_raises(TypeError, operator.ifloordiv, a, b) + with assert_raises(TypeError, match="unsupported operand"): + a //= b class _TestGetSet: @@ -4217,11 +4243,11 @@ class TestDOK(sparse_test_class(minmax=False, nnz_axis=False)): math_dtypes = [np.int_, np.float64, np.complex128] def test_mult(self): - A = dok_matrix((10,10)) - A[0,3] = 10 - A[5,6] = 20 - D = A*A.T - E = A*A.T.conjugate() + A = dok_matrix((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): @@ -4328,9 +4354,9 @@ def test_dot(self): # 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.T, (B @ B.T).toarray()) - assert_array_equal(A @ A.conjugate().T, (B * B.conjugate().T).toarray()) + assert_array_equal(A @ A.conjugate().T, (B @ B.conjugate().T).toarray()) def test_scalar_mul(self): x = lil_matrix((3, 3)) @@ -4804,12 +4830,12 @@ def test_eliminate_zeros_all_zero(self): def test_bsr_matvec(self): A = bsr_matrix(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) + assert_equal(A @ x, A.toarray() @ x) def test_bsr_matvecs(self): A = bsr_matrix(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) + assert_equal(A @ x, A.toarray() @ x) @pytest.mark.xfail(run=False, reason='BSR does not have a __getitem__') def test_iterator(self): From 6836e4f58ccc191d1c1f485df7ad8147ac38138a Mon Sep 17 00:00:00 2001 From: Albert Steppi Date: Mon, 3 Jun 2024 12:42:49 -0400 Subject: [PATCH 371/500] Add test for clang-17 build Remove inadvertently copy and pasted duplicate lines Fix non-working clang-17 install Add missing sudo in clang-17 install Add missing requirement Fix invocation of python -m build Remove incorrect use of false in Clang-17 workflow Remove unnecessary parts of clang-17 CI job --- .github/workflows/linux.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 3e02ce7fa1fa..27676a8253be 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -497,3 +497,40 @@ jobs: PKG_CONFIG_PATH="$PWD" pip install . -vv --no-build-isolation pushd $RUNNER_TEMP PYTHON_GIL=0 python -m pytest --pyargs scipy -n2 --durations=10 + ################################################################################# + clang-17-build-only: + # Purpose is to check for warnings in builds with latest clang. + # We do not run the test suite here. + name: Clang-17 build-only (-Werror) + needs: get_commit_message + if: > + needs.get_commit_message.outputs.message == 1 + && (github.repository == 'scipy/scipy' || github.repository == '') + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4.1.1 + with: + submodules: recursive + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Setup system dependencies + run: | + sudo apt-get -y update + wget https://apt.llvm.org/llvm.sh + chmod u+x llvm.sh + sudo ./llvm.sh 17 + sudo apt install -y libopenblas-dev liblapack-dev + + - name: Setup Python build deps + run: | + pip install -r requirements/build.txt + pip install build + + - name: Build wheel, check for compiler warnings + run: | + # specify which compilers to use using environment variables + CC=clang-17 CXX=clang++-17 FC=gfortran python -m build -wnx -Csetup-args=--werror From b91c7fb01c7b2375efac61f8ca6edf08301987b0 Mon Sep 17 00:00:00 2001 From: Albert Steppi Date: Mon, 3 Jun 2024 12:03:16 -0400 Subject: [PATCH 372/500] Don't mix designated and non-designated initializers --- scipy/special/_gufuncs.cpp | 2 +- scipy/special/_special_ufuncs.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/special/_gufuncs.cpp b/scipy/special/_gufuncs.cpp index 90ee4d69baf7..d35edf3ab096 100644 --- a/scipy/special/_gufuncs.cpp +++ b/scipy/special/_gufuncs.cpp @@ -53,7 +53,7 @@ extern const char *sph_harm_all_doc; extern "C" int wrap_PyUFunc_getfperr() { return PyUFunc_getfperr(); } static PyModuleDef _gufuncs_def = { - PyModuleDef_HEAD_INIT, + .m_base = PyModuleDef_HEAD_INIT, .m_name = "_gufuncs", .m_size = -1, }; diff --git a/scipy/special/_special_ufuncs.cpp b/scipy/special/_special_ufuncs.cpp index b9c2dfc0672b..0ce889b9c43d 100644 --- a/scipy/special/_special_ufuncs.cpp +++ b/scipy/special/_special_ufuncs.cpp @@ -206,7 +206,7 @@ extern const char *yve_doc; extern "C" int wrap_PyUFunc_getfperr() { return PyUFunc_getfperr(); } static PyModuleDef _special_ufuncs_def = { - PyModuleDef_HEAD_INIT, + .m_base = PyModuleDef_HEAD_INIT, .m_name = "_special_ufuncs", .m_size = -1, }; From 6e57035202be3483bcbfc2153c10231f6e76889f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 5 Jun 2024 15:33:17 -0500 Subject: [PATCH 373/500] CI: Make sure nightly free-threaded wheels are tested with GIL disabled (#20907) --- tools/wheels/cibw_before_test.sh | 5 ----- tools/wheels/cibw_test_command.sh | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/wheels/cibw_before_test.sh b/tools/wheels/cibw_before_test.sh index e7f586e81255..d18f920bd2c4 100644 --- a/tools/wheels/cibw_before_test.sh +++ b/tools/wheels/cibw_before_test.sh @@ -7,9 +7,4 @@ if [[ $FREE_THREADED_BUILD == "True" ]]; then python -m pip install -U --pre pip python -m pip install git+https://github.com/cython/cython python -m pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - - # TODO: delete when importing numpy no longer enables the GIL - # setting to zero ensures the GIL is disabled while running the - # tests under free-threaded python - export PYTHON_GIL=0 fi diff --git a/tools/wheels/cibw_test_command.sh b/tools/wheels/cibw_test_command.sh index 5216423ed658..165be7f3624e 100644 --- a/tools/wheels/cibw_test_command.sh +++ b/tools/wheels/cibw_test_command.sh @@ -1,3 +1,11 @@ set -xe +FREE_THREADED_BUILD="$(python -c"import sysconfig; print(bool(sysconfig.get_config_var('Py_GIL_DISABLED')))")" +if [[ $FREE_THREADED_BUILD == "True" ]]; then + # TODO: delete when importing numpy no longer enables the GIL + # setting to zero ensures the GIL is disabled while running the + # tests under free-threaded python + export PYTHON_GIL=0 +fi + python -c "import sys; import scipy; sys.exit(not scipy.test())" From de85b05990a475bfe04bd5f113511c4e9c2e2c8f Mon Sep 17 00:00:00 2001 From: m-maggi <124086916+m-maggi@users.noreply.github.com> Date: Thu, 6 Jun 2024 02:57:40 +0200 Subject: [PATCH 374/500] DOC Update doc for the argument `sort`in `linalg.schur`. (#20906) --- scipy/linalg/_decomp_schur.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scipy/linalg/_decomp_schur.py b/scipy/linalg/_decomp_schur.py index 73bcaffeb700..54a5ce92dd58 100644 --- a/scipy/linalg/_decomp_schur.py +++ b/scipy/linalg/_decomp_schur.py @@ -42,6 +42,9 @@ def schur(a, output='real', lwork=None, overwrite_a=False, sort=None, Specifies whether the upper eigenvalues should be sorted. A callable may be passed that, given a eigenvalue, returns a boolean denoting whether the eigenvalue should be sorted to the top-left (True). + If output='real', the callable should have two arguments, the first + one being the real part of the eigenvalue, the second one being + the imaginary part. Alternatively, string parameters may be used:: 'lhp' Left-hand plane (x.real < 0.0) From 2f930ed7d579837423cf58f30d25d4922e4cef7c Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Thu, 6 Jun 2024 10:01:58 +0300 Subject: [PATCH 375/500] TST: linalg: bump tolerance in TestEig::test_singular Some assertions have atol/rtol configurable, and one assertion had them hardcoded, and that was causing tolerance problems in a Debian build with reference LAPACK. closes https://github.com/scipy/scipy/issues/20911 --- scipy/linalg/tests/test_decomp.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scipy/linalg/tests/test_decomp.py b/scipy/linalg/tests/test_decomp.py index 5e171965a4bd..2c4033360d16 100644 --- a/scipy/linalg/tests/test_decomp.py +++ b/scipy/linalg/tests/test_decomp.py @@ -181,7 +181,8 @@ def test_gh_3054(self): assert_equal(w, np.inf) assert_allclose(vr, 1) - def _check_gen_eig(self, A, B, atol_homog=1e-13, rtol_homog=1e-13): + def _check_gen_eig(self, A, B, atol_homog=1e-13, rtol_homog=1e-13, + atol=1e-13, rtol=1e-13): if B is not None: A, B = asarray(A), asarray(B) B0 = B @@ -230,7 +231,7 @@ def _check_gen_eig(self, A, B, atol_homog=1e-13, rtol_homog=1e-13): for i in range(res.shape[1]): if np.all(isfinite(res[:, i])): assert_allclose(res[:, i], 0, - rtol=1e-13, atol=1e-13, err_msg=msg) + rtol=rtol, atol=atol, err_msg=msg) # try to consistently order eigenvalues, including complex conjugate pairs w_fin = w[isfinite(w)] @@ -269,7 +270,7 @@ def test_singular(self): [24, 35, 18, 21, 22]]) with np.errstate(all='ignore'): - self._check_gen_eig(A, B, atol_homog=5e-13) + self._check_gen_eig(A, B, atol_homog=5e-13, atol=5e-13) def test_falker(self): # Test matrices giving some Nan generalized eigenvalues. From 0ea108b77dfdb83862ec37c1aa964be191e7d068 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Fri, 7 Jun 2024 10:55:41 +0200 Subject: [PATCH 376/500] BLD: optimize: use hidden visibility for static HiGHS libraries This is a follow-up to gh-20477, where HiGHS wasn't touched on purpose to avoid a merge conflict in another PR. Minor useful side benefit: it shrinks the size of `_highs_wrapper.so` by 0.4% Closes gh-20256 --- scipy/optimize/_highs/meson.build | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index 7ef1cefc65dd..5dbd8b72fdca 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -51,7 +51,8 @@ basiclu_lib = static_library('basiclu', '../../_lib/highs/src', '../../_lib/highs/src/ipm/basiclu/include' ], - c_args: [Wno_unused_variable, highs_define_macros] + c_args: [Wno_unused_variable, highs_define_macros], + gnu_symbol_visibility: 'inlineshidden', ) highs_flags = [ @@ -109,7 +110,8 @@ ipx_lib = static_library('ipx', 'cython/src/' ], dependencies: thread_dep, - cpp_args: [highs_flags, highs_define_macros] + cpp_args: [highs_flags, highs_define_macros], + gnu_symbol_visibility: 'inlineshidden', ) highs_lib = static_library('highs', @@ -226,7 +228,8 @@ highs_lib = static_library('highs', '../../_lib/highs/src/util/', ], dependencies: thread_dep, - cpp_args: [highs_flags, highs_define_macros] + cpp_args: [highs_flags, highs_define_macros], + gnu_symbol_visibility: 'inlineshidden', ) _highs_wrapper = py3.extension_module('_highs_wrapper', From 4977ec5a859739f0d0744a9a7270dfc31b3038c7 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 7 Jun 2024 09:00:34 -0700 Subject: [PATCH 377/500] BUG: sparse.csgraph.dijkstra: fix dtype and shape bugs (#20913) --- scipy/sparse/csgraph/_shortest_path.pyx | 23 +++++++--- .../csgraph/tests/test_shortest_path.py | 46 +++++++++++++++---- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/scipy/sparse/csgraph/_shortest_path.pyx b/scipy/sparse/csgraph/_shortest_path.pyx index 0ae423ae135d..4054ca2e7f89 100644 --- a/scipy/sparse/csgraph/_shortest_path.pyx +++ b/scipy/sparse/csgraph/_shortest_path.pyx @@ -550,7 +550,10 @@ def dijkstra(csgraph, directed=True, indices=None, # initialize/validate indices if indices is None: indices = np.arange(N, dtype=ITYPE) - return_shape = indices.shape + (N,) + if min_only: + return_shape = (N,) + else: + return_shape = indices.shape + (N,) else: indices = np.array(indices, order='C', dtype=ITYPE, copy=True) if min_only: @@ -598,16 +601,22 @@ def dijkstra(csgraph, directed=True, indices=None, else: csr_data = csgraph.data + csgraph_indices = csgraph.indices + csgraph_indptr = csgraph.indptr + if csgraph_indices.dtype != ITYPE: + csgraph_indices = csgraph_indices.astype(ITYPE) + if csgraph_indptr.dtype != ITYPE: + csgraph_indptr = csgraph_indptr.astype(ITYPE) if directed: if min_only: _dijkstra_directed_multi(indices, - csr_data, csgraph.indices, - csgraph.indptr, + csr_data, csgraph_indices, + csgraph_indptr, dist_matrix, predecessor_matrix, source_matrix, limitf) else: _dijkstra_directed(indices, - csr_data, csgraph.indices, csgraph.indptr, + csr_data, csgraph_indices, csgraph_indptr, dist_matrix, predecessor_matrix, limitf) else: csgraphT = csgraph.T.tocsr() @@ -617,15 +626,15 @@ def dijkstra(csgraph, directed=True, indices=None, csrT_data = csgraphT.data if min_only: _dijkstra_undirected_multi(indices, - csr_data, csgraph.indices, - csgraph.indptr, + csr_data, csgraph_indices, + csgraph_indptr, csrT_data, csgraphT.indices, csgraphT.indptr, dist_matrix, predecessor_matrix, source_matrix, limitf) else: _dijkstra_undirected(indices, - csr_data, csgraph.indices, csgraph.indptr, + csr_data, csgraph_indices, csgraph_indptr, csrT_data, csgraphT.indices, csgraphT.indptr, dist_matrix, predecessor_matrix, limitf) diff --git a/scipy/sparse/csgraph/tests/test_shortest_path.py b/scipy/sparse/csgraph/tests/test_shortest_path.py index 046df0186948..45600352e8a7 100644 --- a/scipy/sparse/csgraph/tests/test_shortest_path.py +++ b/scipy/sparse/csgraph/tests/test_shortest_path.py @@ -33,10 +33,13 @@ directed_2SP_0_to_3 = [[-9999, 0, -9999, 1, -9999], [-9999, 0, -9999, 4, 1]] -directed_sparse_zero_G = scipy.sparse.csr_matrix(([0, 1, 2, 3, 1], - ([0, 1, 2, 3, 4], - [1, 2, 0, 4, 3])), - shape = (5, 5)) +directed_sparse_zero_G = scipy.sparse.csr_matrix( + ( + [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], @@ -44,10 +47,13 @@ [np.inf, np.inf, np.inf, 0, 3], [np.inf, np.inf, np.inf, 1, 0]] -undirected_sparse_zero_G = scipy.sparse.csr_matrix(([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_G = scipy.sparse.csr_matrix( + ( + [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], @@ -452,3 +458,27 @@ def test_yen_negative_weights(): K=1, ) assert_allclose(distances, [-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(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, + ) From 491f1d7ba83e187e1066496fc3d5c05facd5139f Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Fri, 7 Jun 2024 20:05:24 +0100 Subject: [PATCH 378/500] BUG: stats.mstats: fix mstats.{ttest_rel, ttest_1samp} when array api is enable --- scipy/stats/_mstats_basic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/stats/_mstats_basic.py b/scipy/stats/_mstats_basic.py index 9d07c7f27428..f2ab57298830 100644 --- a/scipy/stats/_mstats_basic.py +++ b/scipy/stats/_mstats_basic.py @@ -92,11 +92,11 @@ def _ttest_finish(df, t, alternative): # We use ``stdtr`` directly here to preserve masked arrays if alternative == 'less': - pval = special.stdtr(df, t) + pval = special._ufuncs.stdtr(df, t) elif alternative == 'greater': - pval = special.stdtr(df, -t) + pval = special._ufuncs.stdtr(df, -t) elif alternative == 'two-sided': - pval = special.stdtr(df, -np.abs(t))*2 + pval = special._ufuncs.stdtr(df, -np.abs(t))*2 else: raise ValueError("alternative must be " "'less', 'greater' or 'two-sided'") From abfd2774d4fbfea8b4ebed2bef9d12e160a72c47 Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Fri, 7 Jun 2024 20:07:23 +0100 Subject: [PATCH 379/500] CI: run array API tests for all of stats --- .github/workflows/array_api.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 8d784adac15f..75289af89abc 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -100,8 +100,4 @@ jobs: python dev.py --no-build test -b all -t scipy.special.tests.test_support_alternative_backends -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy._lib.tests.test_array_api -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy._lib.tests.test__util -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -t scipy.stats.tests.test_stats -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -t scipy.stats.tests.test_morestats -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -t scipy.stats.tests.test_variation -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -t scipy.stats.tests.test_resampling -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -t scipy.optimize.tests.test_chandrupatla -- --durations 3 --timeout=60 + python dev.py --no-build test -b all -s stats -- --durations 3 --timeout=60 From 1e72dea01741ca7cf31ef8ad3565c26765abd48b Mon Sep 17 00:00:00 2001 From: Luiz Eduardo Amaral Date: Fri, 7 Jun 2024 16:14:18 -0300 Subject: [PATCH 380/500] DOC: update doctests to satisfy scipy-doctests==1.2.0 --- scipy/stats/_fit.py | 5 +- scipy/stats/_mannwhitneyu.py | 4 +- scipy/stats/_stats_py.py | 106 +++++++++++++++++++++++++++-------- 3 files changed, 89 insertions(+), 26 deletions(-) diff --git a/scipy/stats/_fit.py b/scipy/stats/_fit.py index b23e33d74a2c..9b3b17b757bc 100644 --- a/scipy/stats/_fit.py +++ b/scipy/stats/_fit.py @@ -983,7 +983,10 @@ def goodness_of_fit(dist, data, *, known_params=None, fit_params=None, >>> 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) + 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 diff --git a/scipy/stats/_mannwhitneyu.py b/scipy/stats/_mannwhitneyu.py index bfc291560fde..19b7ce3883bd 100644 --- a/scipy/stats/_mannwhitneyu.py +++ b/scipy/stats/_mannwhitneyu.py @@ -436,7 +436,9 @@ def mannwhitneyu(x, y, use_continuity=True, alternative="two-sided", >>> from scipy.stats import ttest_ind >>> res = ttest_ind(females, males, alternative="less") >>> print(res) - Ttest_indResult(statistic=-2.239334696520584, pvalue=0.030068441095757924) + 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. diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index a8b06c5d6b3f..8b71228c7048 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -6653,7 +6653,9 @@ def ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2, >>> b = np.array([2, 4, 6, 9, 11, 13, 14, 15, 18, 19, 21]) >>> from scipy.stats import ttest_ind >>> ttest_ind(a, b) - Ttest_indResult(statistic=0.905135809331027, pvalue=0.3751996797581486) + 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:: @@ -6677,7 +6679,9 @@ def ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2, >>> group1 = np.array([1]*30 + [0]*(150-30)) >>> group2 = np.array([1]*45 + [0]*(200-45)) >>> ttest_ind(group1, group2) - Ttest_indResult(statistic=-0.5627179589855622, pvalue=0.573989277115258) + TtestResult(statistic=-0.5627179589855622, + pvalue=0.573989277115258, + df=348.0) """ xp = array_namespace(mean1, std1, mean2, std2) @@ -6894,34 +6898,50 @@ def ttest_ind(a, b, axis=0, equal_var=True, nan_policy='propagate', >>> 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) - Ttest_indResult(statistic=-0.4390847099199348, pvalue=0.6606952038870015) + TtestResult(statistic=-0.4390847099199348, + pvalue=0.6606952038870015, + df=998.0) >>> stats.ttest_ind(rvs1, rvs2, equal_var=False) - Ttest_indResult(statistic=-0.4390847099199348, pvalue=0.6606952553131064) + 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) - Ttest_indResult(statistic=-1.6370984482905417, pvalue=0.1019251574705033) + TtestResult(statistic=-1.6370984482905417, + pvalue=0.1019251574705033, + df=998.0) >>> stats.ttest_ind(rvs1, rvs3, equal_var=False) - Ttest_indResult(statistic=-1.637098448290542, pvalue=0.10202110497954867) + 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) - Ttest_indResult(statistic=-1.9481646859513422, pvalue=0.05186270935842703) + TtestResult(statistic=-1.9481646859513422, + pvalue=0.05186270935842703, + df=598.0) >>> stats.ttest_ind(rvs1, rvs4, equal_var=False) - Ttest_indResult(statistic=-1.3146566100751664, pvalue=0.1913495266513811) + 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) - Ttest_indResult(statistic=-2.8415950600298774, pvalue=0.0046418707568707885) + TtestResult(statistic=-2.8415950600298774, + pvalue=0.0046418707568707885, + df=598.0) >>> stats.ttest_ind(rvs1, rvs5, equal_var=False) - Ttest_indResult(statistic=-1.8686598649188084, pvalue=0.06434714193919686) + TtestResult(statistic=-1.8686598649188084, + pvalue=0.06434714193919686, + df=109.32167496550137) When performing a permutation test, more permutations typically yields more accurate results. Use a ``np.random.Generator`` to ensure @@ -6929,7 +6949,9 @@ def ttest_ind(a, b, axis=0, equal_var=True, nan_policy='propagate', >>> stats.ttest_ind(rvs1, rvs5, permutations=10000, ... random_state=rng) - Ttest_indResult(statistic=-2.8415950600298774, pvalue=0.0052994700529947) + TtestResult(statistic=-2.8415950600298774, + pvalue=0.0052994700529947, + df=nan) Take these two samples, one of which has an extreme tail. @@ -6942,8 +6964,9 @@ def ttest_ind(a, b, axis=0, equal_var=True, nan_policy='propagate', have no effect on sample `b` because ``np.floor(trim*len(b))`` is 0. >>> stats.ttest_ind(a, b, trim=.2) - Ttest_indResult(statistic=3.4463884028073513, - pvalue=0.01369338726499547) + TtestResult(statistic=3.4463884028073513, + pvalue=0.01369338726499547, + df=6.0) """ xp = array_namespace(a, b) @@ -7968,7 +7991,10 @@ def ks_1samp(x, cdf, args=(), alternative='two-sided', method='auto'): >>> 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) + 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 @@ -7979,7 +8005,10 @@ def ks_1samp(x, cdf, args=(), alternative='two-sided', method='auto'): >>> x = stats.norm.rvs(size=100, random_state=rng) >>> stats.ks_1samp(x, stats.norm.cdf) - KstestResult(statistic=0.05345882212970396, pvalue=0.9227159037744717) + 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. @@ -7992,7 +8021,10 @@ def ks_1samp(x, cdf, args=(), alternative='two-sided', method='auto'): >>> 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) + 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. @@ -8330,7 +8362,11 @@ def ks_2samp(data1, data2, alternative='two-sided', method='auto'): >>> 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) + 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 @@ -8342,7 +8378,10 @@ def ks_2samp(data1, data2, alternative='two-sided', method='auto'): >>> 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) + 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. @@ -8355,7 +8394,10 @@ def ks_2samp(data1, data2, alternative='two-sided', method='auto'): >>> 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) + 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. @@ -8602,7 +8644,10 @@ def kstest(rvs, cdf, args=(), N=20, alternative='two-sided', method='auto'): >>> 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) + 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 @@ -8613,7 +8658,11 @@ def kstest(rvs, cdf, args=(), N=20, alternative='two-sided', method='auto'): >>> x = stats.norm.rvs(size=100, random_state=rng) >>> stats.kstest(x, stats.norm.cdf) - KstestResult(statistic=0.05345882212970396, pvalue=0.9227159037744717) + 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. @@ -8626,7 +8675,10 @@ def kstest(rvs, cdf, args=(), N=20, alternative='two-sided', method='auto'): >>> 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) + 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. @@ -8635,7 +8687,10 @@ def kstest(rvs, cdf, args=(), N=20, alternative='two-sided', method='auto'): distribution as the second argument. >>> stats.kstest(x, "norm", alternative='less') - KstestResult(statistic=0.17482387821055168, pvalue=0.001913921057766743) + 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 @@ -8646,7 +8701,10 @@ def kstest(rvs, cdf, args=(), N=20, alternative='two-sided', method='auto'): >>> 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) + 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. From b4c57df3996bb834c77f0d6841efa3775f945d8e Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Fri, 7 Jun 2024 20:23:51 +0100 Subject: [PATCH 381/500] ENH: stats: add array API support to combine_pvalues (#20900) * ENH: stats: add array API support to combine_pvalues --- scipy/stats/_stats_py.py | 55 +++++++++------- scipy/stats/tests/test_stats.py | 111 +++++++++++++++++--------------- 2 files changed, 91 insertions(+), 75 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index a8b06c5d6b3f..7372c9b93109 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -9244,41 +9244,47 @@ def combine_pvalues(pvalues, method='fisher', weights=None): .. [8] https://en.wikipedia.org/wiki/Extensions_of_Fisher%27s_method """ - if pvalues.size == 0: + xp = array_namespace(pvalues) + pvalues = xp.asarray(pvalues) + if xp_size(pvalues) == 0: NaN = _get_nan(pvalues) return SignificanceResult(NaN, NaN) + + n = pvalues.shape[0] + # used to convert Python scalar to the right dtype + one = xp.asarray(1, dtype=pvalues.dtype) if method == 'fisher': - statistic = -2 * np.sum(np.log(pvalues)) - chi2 = _SimpleChi2(2 * len(pvalues)) + statistic = -2 * xp.sum(xp.log(pvalues)) + chi2 = _SimpleChi2(2*n*one) pval = _get_pvalue(statistic, chi2, alternative='greater', - symmetric=False, xp=np) + symmetric=False, xp=xp) elif method == 'pearson': - statistic = 2 * np.sum(np.log1p(-pvalues)) - chi2 = _SimpleChi2(2 * len(pvalues)) - pval = _get_pvalue(-statistic, chi2, alternative='less', - symmetric=False, xp=np) + statistic = 2 * xp.sum(xp.log1p(-pvalues)) + chi2 = _SimpleChi2(2*n*one) + pval = _get_pvalue(-statistic, chi2, alternative='less', symmetric=False, xp=xp) elif method == 'mudholkar_george': - normalizing_factor = np.sqrt(3/len(pvalues))/np.pi - statistic = -np.sum(np.log(pvalues)) + np.sum(np.log1p(-pvalues)) - nu = 5 * len(pvalues) + 4 - approx_factor = np.sqrt(nu / (nu - 2)) - pval = distributions.t.sf(statistic * normalizing_factor - * approx_factor, nu) + normalizing_factor = math.sqrt(3/n)/xp.pi + statistic = -xp.sum(xp.log(pvalues)) + xp.sum(xp.log1p(-pvalues)) + nu = 5*n + 4 + approx_factor = math.sqrt(nu / (nu - 2)) + t = _SimpleStudentT(nu*one) + pval = _get_pvalue(statistic * normalizing_factor * approx_factor, t, + alternative="greater", xp=xp) elif method == 'tippett': - statistic = np.min(pvalues) - beta = _SimpleBeta(1, len(pvalues)) - pval = _get_pvalue(statistic, beta, alternative='less', - symmetric=False, xp=np) + statistic = xp.min(pvalues) + beta = _SimpleBeta(one, n*one) + pval = _get_pvalue(statistic, beta, alternative='less', symmetric=False, xp=xp) elif method == 'stouffer': if weights is None: - weights = np.ones_like(pvalues) - elif len(weights) != len(pvalues): + weights = xp.ones_like(pvalues, dtype=pvalues.dtype) + elif weights.shape[0] != n: raise ValueError("pvalues and weights must be of the same size.") - Zi = distributions.norm.isf(pvalues) - statistic = np.dot(weights, Zi) / np.linalg.norm(weights) - pval = distributions.norm.sf(statistic) + norm = _SimpleNormal() + Zi = norm.isf(pvalues) + statistic = weights @ Zi / xp.linalg.vector_norm(weights) + pval = _get_pvalue(statistic, norm, alternative="greater", xp=xp) else: raise ValueError( @@ -10890,6 +10896,9 @@ def cdf(self, x): def sf(self, x): return special.ndtr(-x) + + def isf(self, x): + return -special.ndtri(x) class _SimpleChi2: diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index e0044802e3be..a5232339e853 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -7947,54 +7947,59 @@ def test_no_args_gh20661(self): stats.kruskal() +@array_api_compatible class TestCombinePvalues: - def test_fisher(self): + def test_fisher(self, xp): # Example taken from https://en.wikipedia.org/wiki/Fisher%27s_exact_test#Example - xsq, p = stats.combine_pvalues([.01, .2, .3], method='fisher') - assert_approx_equal(p, 0.02156, significant=4) - - def test_stouffer(self): - Z, p = stats.combine_pvalues([.01, .2, .3], method='stouffer') - assert_approx_equal(p, 0.01651, significant=4) - - def test_stouffer2(self): - Z, p = stats.combine_pvalues([.5, .5, .5], method='stouffer') - assert_approx_equal(p, 0.5, significant=4) - - def test_weighted_stouffer(self): - Z, p = stats.combine_pvalues([.01, .2, .3], method='stouffer', - weights=np.ones(3)) - assert_approx_equal(p, 0.01651, significant=4) - - def test_weighted_stouffer2(self): - Z, p = stats.combine_pvalues([.01, .2, .3], method='stouffer', - weights=np.array((1, 4, 9))) - assert_approx_equal(p, 0.1464, significant=4) - - def test_pearson(self): - Z, p = stats.combine_pvalues([.01, .2, .3], method='pearson') - assert_approx_equal(p, 0.02213, significant=4) - - def test_tippett(self): - Z, p = stats.combine_pvalues([.01, .2, .3], method='tippett') - assert_approx_equal(p, 0.0297, significant=4) - - def test_mudholkar_george(self): - Z, p = stats.combine_pvalues([.1, .1, .1], method='mudholkar_george') - assert_approx_equal(p, 0.019462, significant=4) - - def test_mudholkar_george_equal_fisher_pearson_average(self): - Z, p = stats.combine_pvalues([.01, .2, .3], method='mudholkar_george') - Z_f, p_f = stats.combine_pvalues([.01, .2, .3], method='fisher') - Z_p, p_p = stats.combine_pvalues([.01, .2, .3], method='pearson') - assert_approx_equal(0.5 * (Z_f+Z_p), Z, significant=4) + xsq, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='fisher') + xp_assert_close(p, xp.asarray(0.02156), rtol=1e-4) + + def test_stouffer(self, xp): + Z, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='stouffer') + xp_assert_close(p, xp.asarray(0.01651), rtol=1e-3) + + def test_stouffer2(self, xp): + Z, p = stats.combine_pvalues(xp.asarray([.5, .5, .5]), method='stouffer') + xp_assert_close(p, xp.asarray(0.5), rtol=1e-4) + + def test_weighted_stouffer(self, xp): + pvalues = xp.asarray([.01, .2, .3]) + Z, p = stats.combine_pvalues(pvalues, method='stouffer', + weights=xp.ones(3, dtype=pvalues.dtype)) + xp_assert_close(p, xp.asarray(0.01651), rtol=1e-3) + + def test_weighted_stouffer2(self, xp): + Z, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='stouffer', + weights=xp.asarray([1., 4., 9.])) + xp_assert_close(p, xp.asarray(0.1464), rtol=1e-3) + + def test_pearson(self, xp): + Z, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='pearson') + xp_assert_close(p, xp.asarray(0.02213), rtol=1e-3) + + def test_tippett(self, xp): + Z, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='tippett') + xp_assert_close(p, xp.asarray(0.0297), rtol=1e-4) + + def test_mudholkar_george(self, xp): + Z, p = stats.combine_pvalues(xp.asarray([.1, .1, .1]), + method='mudholkar_george') + xp_assert_close(p, xp.asarray(0.019462), rtol=1e-4) + + def test_mudholkar_george_equal_fisher_pearson_average(self, xp): + Z, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), + method='mudholkar_george') + Z_f, p_f = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='fisher') + Z_p, p_p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='pearson') + xp_assert_close(0.5 * (Z_f+Z_p), Z, rtol=1e-4) 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): + def test_monotonicity(self, variant, method, xp): + xp_test = array_namespace(xp.asarray(1)) # Test that result increases monotonically with respect to input. m, n = 10, 7 rng = np.random.default_rng(278448169958891062669391462690811630763) @@ -8004,23 +8009,25 @@ def test_monotonicity(self, variant, method): # monotonically down one column (single), simultaneously down each # column (all), or independently down each column (random). if variant == "single": - pvaluess = np.full((m, n), rng.random(n)) - pvaluess[:, 0] = np.linspace(0.1, 0.9, m) + pvaluess = xp.broadcast_to(xp.asarray(rng.random(n)), (m, n)) + pvaluess = xp_test.concat([xp.reshape(xp.linspace(0.1, 0.9, m), (-1, 1)), + pvaluess[:, 1:]], axis=1) elif variant == "all": - pvaluess = np.full((n, m), np.linspace(0.1, 0.9, m)).T + pvaluess = xp.broadcast_to(xp.linspace(0.1, 0.9, m), (n, m)).T elif variant == "random": - pvaluess = np.sort(rng.uniform(0, 1, size=(m, n)), axis=0) + pvaluess = xp_test.sort(xp.asarray(rng.uniform(0, 1, size=(m, n))), axis=0) - combined_pvalues = [ - stats.combine_pvalues(pvalues, method=method)[1] - for pvalues in pvaluess - ] - assert np.all(np.diff(combined_pvalues) >= 0) + combined_pvalues = xp.asarray([ + 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): - res = stats.combine_pvalues([.01, .2, .3], method=method) - assert_equal((res.statistic, res.pvalue), res) + 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]) class TestCdfDistanceValidation: From 7292d587b399c421392e9512bb4664a39214d32b Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 7 Jun 2024 21:45:57 -0700 Subject: [PATCH 382/500] BUG: ensure reasonable length _deprecate_positional_args messages --- scipy/_lib/deprecation.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/scipy/_lib/deprecation.py b/scipy/_lib/deprecation.py index 01a1dfa73695..0823c78c579a 100644 --- a/scipy/_lib/deprecation.py +++ b/scipy/_lib/deprecation.py @@ -213,14 +213,10 @@ def inner_f(*args, **kwargs): return f(*args, **kwargs) # extra_args > 0 - args_msg = [ - f"{name}={arg}" - for name, arg in zip(kwonly_args[:extra_args], args[-extra_args:]) - ] - args_msg = ", ".join(args_msg) + args_msg = ", ".join(kwonly_args[:extra_args]) warnings.warn( ( - f"You are passing {args_msg} as a positional argument. " + f"You are passing as positional arguments: {args_msg}. " "Please change your invocation to use keyword arguments. " f"From SciPy {version}, passing these as positional " "arguments will result in an error." From a5a9b4c1d40a75b25956699c88596c2c27f3f328 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 8 Jun 2024 10:31:44 +0300 Subject: [PATCH 383/500] MAINT: adapt to a scipy-doctests change Allow the filter-warnings context manager to not receive a (doc)test. This is useful to filter out DeprecationWarnings which are emitted during test discovery. Which is needed, e.g. for numpy 1/2 transition where things deprecated in numpy 2 emit warnings on import. --- scipy/conftest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/conftest.py b/scipy/conftest.py index 1f17618ec551..c9bd9bf99afb 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -267,7 +267,7 @@ def skip_xp_backends(xp, request): # FIXME: populate the dict once @contextmanager - def warnings_errors_and_rng(test): + def warnings_errors_and_rng(test=None): """Temporarily turn (almost) all warnings to errors. Filter out known warnings which we allow. @@ -337,11 +337,11 @@ def warnings_errors_and_rng(test): with _fixed_default_rng(): np.random.seed(None) with warnings.catch_warnings(): - if test.name in known_warnings: + if test and test.name in known_warnings: warnings.filterwarnings('ignore', **known_warnings[test.name]) yield - elif test.name in legit: + elif test and test.name in legit: yield else: warnings.simplefilter('error', Warning) From 7df6e32d61c1510fa19995da05e49ba338ed445b Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 8 Jun 2024 10:44:02 +0300 Subject: [PATCH 384/500] DOC: interpolate: fix the example in the tutorial --- doc/source/tutorial/interpolate/smoothing_splines.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/tutorial/interpolate/smoothing_splines.rst b/doc/source/tutorial/interpolate/smoothing_splines.rst index 4e5d6e4a42b7..7bd2f5dad85d 100644 --- a/doc/source/tutorial/interpolate/smoothing_splines.rst +++ b/doc/source/tutorial/interpolate/smoothing_splines.rst @@ -231,9 +231,9 @@ example that follows. Notice that `sproot` may fail to find an obvious solution at the edge of the approximation interval, :math:`x = 0`. If we define the spline on a slightly - larger interval, we recover both roots :math:`x = 0` and :math:`x = 2\pi`: + larger interval, we recover both roots :math:`x = 0` and :math:`x = \pi`: - >>> x = np.linspace(-np.pi/4, 2.*np.pi + np.pi/4, 21) + >>> x = np.linspace(-np.pi/4, np.pi + np.pi/4, 51) >>> y = np.sin(x) >>> tck = interpolate.splrep(x, y, s=0) >>> interpolate.sproot(tck) From 9b1cdc247ca1efeac21b78663e0cfdd7a10c3890 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 8 Jun 2024 17:06:33 -0700 Subject: [PATCH 385/500] ENH: optimize._differentiate: add array API support [skip ci] --- scipy/optimize/_differentiate.py | 98 +++++++++++----------- scipy/optimize/tests/test_differentiate.py | 87 ++++++++++--------- 2 files changed, 97 insertions(+), 88 deletions(-) diff --git a/scipy/optimize/_differentiate.py b/scipy/optimize/_differentiate.py index 959c17e3ffae..3558a5dc0076 100644 --- a/scipy/optimize/_differentiate.py +++ b/scipy/optimize/_differentiate.py @@ -2,6 +2,7 @@ import numpy as np import scipy._lib._elementwise_iterative_method as eim from scipy._lib._util import _RichResult +from scipy._lib._array_api import array_namespace _EERRORINCREASE = -1 # used in _differentiate @@ -12,26 +13,19 @@ def _differentiate_iv(func, x, args, atol, rtol, maxiter, order, initial_step, if not callable(func): raise ValueError('`func` must be callable.') - # x has more complex IV that is taken care of during initialization - x = np.asarray(x) - dtype = x.dtype if np.issubdtype(x.dtype, np.inexact) else np.float64 - if not np.iterable(args): args = (args,) - if atol is None: - atol = np.finfo(dtype).tiny - - if rtol is None: - rtol = np.sqrt(np.finfo(dtype).eps) - + # tolerances are floats, not arrays; OK to use NumPy message = 'Tolerances and step parameters must be non-negative scalars.' - tols = np.asarray([atol, rtol, initial_step, step_factor]) - if (not np.issubdtype(tols.dtype, np.number) - or np.any(tols < 0) - or tols.shape != (4,)): + tols = np.asarray([atol if atol is not None else 1, + rtol if rtol is not None else 1, + initial_step, step_factor]) + if (not np.issubdtype(tols.dtype, np.number) or np.any(tols < 0) + or np.any(np.isnan(tols)) or tols.shape != (4,)): raise ValueError(message) - initial_step, step_factor = tols[2:].astype(dtype) + initial_step = float(tols[2]) + step_factor = float(tols[3]) maxiter_int = int(maxiter) if maxiter != maxiter_int or maxiter <= 0: @@ -41,9 +35,8 @@ def _differentiate_iv(func, x, args, atol, rtol, maxiter, order, initial_step, if order_int != order or order <= 0: raise ValueError('`order` must be a positive integer.') - step_direction = np.sign(step_direction).astype(dtype) - x, step_direction = np.broadcast_arrays(x, step_direction) - x, step_direction = x[()], step_direction[()] + xp_temp = array_namespace(x) + x, step_direction = xp_temp.broadcast_arrays(x, xp_temp.asarray(step_direction)) message = '`preserve_shape` must be True or False.' if preserve_shape not in {True, False}: @@ -364,15 +357,23 @@ def _differentiate(func, x, *, args=(), atol=None, rtol=None, maxiter=10, # reduce function calls, so let's keep it simple. temp = eim._initialize(func, (x,), args, preserve_shape=preserve_shape) func, xs, fs, args, shape, dtype, xp = temp + + finfo = xp.finfo(dtype) + atol = finfo.smallest_normal if atol is None else atol + rtol = finfo.eps**0.5 if rtol is None else rtol + x, f = xs[0], fs[0] - df = np.full_like(f, np.nan) + df = xp.full_like(f, xp.nan) + # Ideally we'd broadcast the shape of `hdir` in `_elementwise_algo_init`, but # it's simpler to do it here than to generalize `_elementwise_algo_init` further. # `hdir` and `x` are already broadcasted in `_differentiate_iv`, so we know # that `hdir` can be broadcasted to the final shape. - hdir = np.broadcast_to(hdir, shape).flatten() + hdir = xp.astype(xp.sign(hdir), dtype) + hdir = xp.broadcast_to(hdir, shape) + hdir = xp.reshape(hdir, (-1,)) - status = np.full_like(x, eim._EINPROGRESS, dtype=int) # in progress + status = xp.full_like(x, eim._EINPROGRESS, dtype=xp.int32) # in progress nit, nfev = 0, 1 # one function evaluations performed above # Boolean indices of left, central, right, and (all) one-sided steps il = hdir < 0 @@ -387,8 +388,8 @@ def _differentiate(func, x, *, args=(), atol=None, rtol=None, maxiter=10, # `_differentiate_weights`). # - `terms` (which could probably use a better name) is half the `order`, # which is always even. - work = _RichResult(x=x, df=df, fs=f[:, np.newaxis], error=np.nan, h=h0, - df_last=np.nan, error_last=np.nan, h0=h0, fac=fac, + work = _RichResult(x=x, df=df, fs=f[:, xp.newaxis], error=xp.nan, h=h0, + df_last=xp.nan, error_last=xp.nan, h0=h0, fac=fac, atol=atol, rtol=rtol, nit=nit, nfev=nfev, status=status, dtype=dtype, terms=(order+1)//2, hdir=hdir, il=il, ic=ic, ir=ir, io=io) @@ -426,22 +427,22 @@ def pre_func_eval(work): # Note - no need to be careful about dtypes until we allocate `x_eval` if work.nit == 0: - hc = h / c**np.arange(n) - hc = np.concatenate((-hc[::-1], hc)) + hc = h / c**xp.arange(n, dtype=work.dtype) + hc = xp.concat((-xp.flip(hc), hc)) else: - hc = np.asarray([-h, h]) / c**(n-1) + hc = xp.asarray([-h, h]) / c**(n-1) if work.nit == 0: - hr = h / d**np.arange(2*n) + hr = h / d**xp.arange(2*n, dtype=work.dtype) else: - hr = np.asarray([h, h/d]) / c**(n-1) + hr = xp.asarray([h, h/d]) / c**(n-1) n_new = 2*n if work.nit == 0 else 2 # number of new abscissae - x_eval = np.zeros((len(work.hdir), n_new), dtype=work.dtype) + x_eval = xp.zeros((work.hdir.shape[0], n_new), dtype=work.dtype) il, ic, ir = work.il, work.ic, work.ir - x_eval[ir] = work.x[ir, np.newaxis] + hr - x_eval[ic] = work.x[ic, np.newaxis] + hc - x_eval[il] = work.x[il, np.newaxis] - hr + x_eval[ir] = work.x[ir][:, xp.newaxis] + hr + x_eval[ic] = work.x[ic][:, xp.newaxis] + hc + x_eval[il] = work.x[il][:, xp.newaxis] - hr return x_eval def post_func_eval(x, f, work): @@ -475,27 +476,27 @@ def post_func_eval(x, f, work): # Central difference # `work_fc` is *all* the points at which the function has been evaluated # `fc` is the points we're using *this iteration* to produce the estimate - work_fc = (f[ic, :n_new], work.fs[ic, :], f[ic, -n_new:]) - work_fc = np.concatenate(work_fc, axis=-1) + work_fc = (f[ic][:, :n_new], work.fs[ic], f[ic][:, -n_new:]) + work_fc = xp.concat(work_fc, axis=-1) if work.nit == 0: fc = work_fc else: fc = (work_fc[:, :n], work_fc[:, n:n+1], work_fc[:, -n:]) - fc = np.concatenate(fc, axis=-1) + fc = xp.concat(fc, axis=-1) # One-sided difference - work_fo = np.concatenate((work.fs[io, :], f[io, :]), axis=-1) + work_fo = xp.concat((work.fs[io], f[io]), axis=-1) if work.nit == 0: fo = work_fo else: - fo = np.concatenate((work_fo[:, 0:1], work_fo[:, -2*n:]), axis=-1) + fo = xp.concat((work_fo[:, 0:1], work_fo[:, -2*n:]), axis=-1) - work.fs = np.zeros((len(ic), work.fs.shape[-1] + 2*n_new)) + work.fs = xp.zeros((ic.shape[0], work.fs.shape[-1] + 2*n_new), dtype=work.dtype) work.fs[ic] = work_fc work.fs[io] = work_fo - wc, wo = _differentiate_weights(work, n) - work.df_last = work.df.copy() + wc, wo = _differentiate_weights(work, n, xp) + work.df_last = xp.asarray(work.df, copy=True) work.df[ic] = fc @ wc / work.h work.df[io] = fo @ wo / work.h work.df[il] *= -1 @@ -509,19 +510,19 @@ def post_func_eval(x, f, work): # we could use Richarson extrapolation to produce an error estimate that # is one order higher, and take the difference between that and # `work.df` (which would just be constant factor that depends on `fac`.) - work.error = abs(work.df - work.df_last) + work.error = xp.abs(work.df - work.df_last) def check_termination(work): """Terminate due to convergence, non-finite values, or error increase""" - stop = np.zeros_like(work.df).astype(bool) + stop = xp.astype(xp.zeros_like(work.df), xp.bool) i = work.error < work.atol + work.rtol*abs(work.df) work.status[i] = eim._ECONVERGED stop[i] = True if work.nit > 0: - i = ~((np.isfinite(work.x) & np.isfinite(work.df)) | stop) - work.df[i], work.status[i] = np.nan, eim._EVALUEERR + i = ~((xp.isfinite(work.x) & xp.isfinite(work.df)) | stop) + work.df[i], work.status[i] = xp.nan, eim._EVALUEERR stop[i] = True # With infinite precision, there is a step size below which @@ -549,7 +550,7 @@ def customize_result(res, shape): xp, preserve_shape) -def _differentiate_weights(work, n): +def _differentiate_weights(work, n, xp): # This produces the weights of the finite difference formula for a given # stencil. In experiments, use of a second-order central difference formula # with Richardson extrapolation was more accurate numerically, but it was @@ -613,7 +614,7 @@ def _differentiate_weights(work, n): # `x` and `args` in single precision. `fac` gets converted to single # precision, but we should always use double precision for the intermediate # calculations here to avoid additional error in the weights. - fac = work.fac.astype(np.float64) + fac = float(work.fac) # Note that if the user switches back to floating point precision with # `x` and `args`, then `fac` will not necessarily equal the (lower @@ -628,6 +629,7 @@ def _differentiate_weights(work, n): if len(_differentiate_weights.central) != 2*n + 1: # Central difference weights. Consider refactoring this; it could # probably be more compact. + # Note: `n` and `fac` are NumPy scalars; we convert to xp-type at the end i = np.arange(-n, n + 1) p = np.abs(i) - 1. # center point has power `p` -1, but sign `s` is 0 s = np.sign(i) @@ -662,8 +664,8 @@ def _differentiate_weights(work, n): _differentiate_weights.right = weights - return (_differentiate_weights.central.astype(work.dtype, copy=False), - _differentiate_weights.right.astype(work.dtype, copy=False)) + return (xp.asarray(_differentiate_weights.central, dtype=work.dtype), + xp.asarray(_differentiate_weights.right, dtype=work.dtype)) _differentiate_weights.central = [] _differentiate_weights.right = [] _differentiate_weights.fac = None diff --git a/scipy/optimize/tests/test_differentiate.py b/scipy/optimize/tests/test_differentiate.py index ad9407675b46..7c116e48e4df 100644 --- a/scipy/optimize/tests/test_differentiate.py +++ b/scipy/optimize/tests/test_differentiate.py @@ -3,26 +3,36 @@ import numpy as np from numpy.testing import assert_array_less, assert_allclose, assert_equal +from scipy.conftest import array_api_compatible import scipy._lib._elementwise_iterative_method as eim -from scipy import stats, optimize +from scipy._lib._array_api import (xp_assert_close, xp_assert_equal, xp_assert_less, + is_numpy, is_torch) + +from scipy import stats, optimize, special from scipy.optimize._differentiate import (_differentiate as differentiate, _jacobian as jacobian, _EERRORINCREASE) class TestDifferentiate: def f(self, x): - return stats.norm().cdf(x) + return special.ndtr(x) + @array_api_compatible @pytest.mark.parametrize('x', [0.6, np.linspace(-0.05, 1.05, 10)]) - def test_basic(self, x): + @pytest.mark.usefixtures("skip_xp_backends") + @pytest.mark.skip_xp_backends('array_api_strict', 'jax.numpy', + reasons=['Currently uses fancy indexing assignment.', + 'JAX does not support item assignment.']) + def test_basic(self, x, xp): # Invert distribution CDF and compare against distribution `ppf` - res = differentiate(self.f, x) - ref = stats.norm().pdf(x) - np.testing.assert_allclose(res.df, ref) - # This would be nice, but doesn't always work out. `error` is an - # estimate, not a bound. - assert_array_less(abs(res.df - ref), res.error) - assert res.x.shape == ref.shape + default_dtype = xp.asarray(1.).dtype + res = differentiate(self.f, xp.asarray(x, dtype=default_dtype)) + ref = xp.asarray(stats.norm().pdf(x), dtype=default_dtype) + xp_assert_close(res.df, ref) + # # This would be nice, but doesn't always work out. `error` is an + # # estimate, not a bound. + if not is_torch: + xp_assert_less(xp.abs(res.df - ref), res.error) @pytest.mark.parametrize('case', stats._distr_params.distcont) def test_accuracy(self, case): @@ -33,9 +43,14 @@ def test_accuracy(self, case): ref = dist.pdf(x) assert_allclose(res.df, ref, atol=1e-10) + @array_api_compatible @pytest.mark.parametrize('order', [1, 6]) @pytest.mark.parametrize('shape', [tuple(), (12,), (3, 4), (3, 2, 2)]) - def test_vectorization(self, order, shape): + @pytest.mark.usefixtures("skip_xp_backends") + @pytest.mark.skip_xp_backends('array_api_strict', 'jax.numpy', + reasons=['Currently uses fancy indexing assignment.', + 'JAX does not support item assignment.']) + def test_vectorization(self, order, shape, xp): # Test for correct functionality, output shapes, and dtypes for various # input shapes. x = np.linspace(-0.05, 1.05, 12).reshape(shape) if shape else 0.6 @@ -52,42 +67,34 @@ def f(x, *args, **kwargs): f.nit = -1 f.feval = 0 - res = differentiate(f, x, order=order) + res = differentiate(f, xp.asarray(x, dtype=xp.float64), order=order) refs = _differentiate_single(x).ravel() ref_x = [ref.x for ref in refs] - assert_allclose(res.x.ravel(), ref_x) - assert_equal(res.x.shape, shape) + xp_assert_close(xp.reshape(res.x, (-1,)), xp.asarray(ref_x)) ref_df = [ref.df for ref in refs] - assert_allclose(res.df.ravel(), ref_df) - assert_equal(res.df.shape, shape) + xp_assert_close(xp.reshape(res.df, (-1,)), xp.asarray(ref_df)) ref_error = [ref.error for ref in refs] - assert_allclose(res.error.ravel(), ref_error, atol=5e-15) - assert_equal(res.error.shape, shape) - - ref_success = [ref.success for ref in refs] - assert_equal(res.success.ravel(), ref_success) - assert_equal(res.success.shape, shape) - assert np.issubdtype(res.success.dtype, np.bool_) - - ref_flag = [ref.status for ref in refs] - assert_equal(res.status.ravel(), ref_flag) - assert_equal(res.status.shape, shape) - assert np.issubdtype(res.status.dtype, np.integer) - - ref_nfev = [ref.nfev for ref in refs] - assert_equal(res.nfev.ravel(), ref_nfev) - assert_equal(np.max(res.nfev), f.feval) - assert_equal(res.nfev.shape, res.x.shape) - assert np.issubdtype(res.nfev.dtype, np.integer) - - ref_nit = [ref.nit for ref in refs] - assert_equal(res.nit.ravel(), ref_nit) - assert_equal(np.max(res.nit), f.nit) - assert_equal(res.nit.shape, res.x.shape) - assert np.issubdtype(res.nit.dtype, np.integer) + xp_assert_close(xp.reshape(res.error, (-1,)), xp.asarray(ref_error), + atol=1e-13) + + ref_success = [bool(ref.success) for ref in refs] + xp_assert_equal(xp.reshape(res.success, (-1,)), xp.asarray(ref_success)) + + ref_flag = [np.int32(ref.status) for ref in refs] + xp_assert_equal(xp.reshape(res.status, (-1,)), xp.asarray(ref_flag)) + + ref_nfev = [np.int32(ref.nfev) for ref in refs] + xp_assert_equal(xp.reshape(res.nfev, (-1,)), xp.asarray(ref_nfev)) + if not is_numpy(xp): # can't expect other backends to be exactly the same + xp.max(res.nfev) == f.feval + + ref_nit = [np.int32(ref.nit) for ref in refs] + xp_assert_equal(xp.reshape(res.nit, (-1,)), xp.asarray(ref_nit)) + if not is_numpy(xp): # can't expect other backends to be exactly the same + xp.max(res.nit) == f.nit def test_flags(self): # Test cases that should produce different status flags; show that all From 283bb6b2285bad82e3d5f919aede8988ea319a50 Mon Sep 17 00:00:00 2001 From: Pavadol Yamsiri Date: Sun, 9 Jun 2024 20:46:25 +1000 Subject: [PATCH 386/500] MAINT: Defer setting title and key in HBInfo to after checking if they are `None`. --- scipy/io/_harwell_boeing/hb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/io/_harwell_boeing/hb.py b/scipy/io/_harwell_boeing/hb.py index 9d28e3849ff5..534e0a4b7db6 100644 --- a/scipy/io/_harwell_boeing/hb.py +++ b/scipy/io/_harwell_boeing/hb.py @@ -211,8 +211,6 @@ def __init__(self, title, key, pointer_format_str, indices_format_str, values_format_str, right_hand_sides_nlines=0, nelementals=0): """Do not use this directly, but the class ctrs (from_* functions).""" - self.title = title - self.key = key if title is None: title = "No Title" if len(title) > 72: @@ -223,6 +221,8 @@ def __init__(self, title, key, if len(key) > 8: warnings.warn("key is > 8 characters (key is %s)" % key, LineOverflow, stacklevel=3) + self.title = title + self.key = key self.total_nlines = total_nlines self.pointer_nlines = pointer_nlines From 72bb98ea0e26ff837c619d12de3316dc421e99f5 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 9 Jun 2024 10:51:52 -0700 Subject: [PATCH 387/500] TST: optimize._differentiate: continue test conversions --- .github/workflows/array_api.yml | 1 + scipy/optimize/tests/test_differentiate.py | 226 +++++++++++---------- 2 files changed, 116 insertions(+), 111 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 1166702a5662..cc1af051301d 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -105,3 +105,4 @@ jobs: python dev.py --no-build test -b all -t scipy.stats.tests.test_variation -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.stats.tests.test_resampling -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.optimize.tests.test_chandrupatla -- --durations 3 --timeout=60 + python dev.py --no-build test -b all -t scipy.optimize.tests.test_differentiate -- --durations 3 --timeout=60 diff --git a/scipy/optimize/tests/test_differentiate.py b/scipy/optimize/tests/test_differentiate.py index 7c116e48e4df..2b96212682a5 100644 --- a/scipy/optimize/tests/test_differentiate.py +++ b/scipy/optimize/tests/test_differentiate.py @@ -6,23 +6,25 @@ from scipy.conftest import array_api_compatible import scipy._lib._elementwise_iterative_method as eim from scipy._lib._array_api import (xp_assert_close, xp_assert_equal, xp_assert_less, - is_numpy, is_torch) + is_numpy, is_torch, array_namespace) from scipy import stats, optimize, special from scipy.optimize._differentiate import (_differentiate as differentiate, _jacobian as jacobian, _EERRORINCREASE) + +@array_api_compatible +@pytest.mark.usefixtures("skip_xp_backends") +@pytest.mark.skip_xp_backends('array_api_strict', 'jax.numpy', + reasons=[ + 'Currently uses fancy indexing assignment.', + 'JAX arrays do not support item assignment.']) class TestDifferentiate: def f(self, x): return special.ndtr(x) - @array_api_compatible @pytest.mark.parametrize('x', [0.6, np.linspace(-0.05, 1.05, 10)]) - @pytest.mark.usefixtures("skip_xp_backends") - @pytest.mark.skip_xp_backends('array_api_strict', 'jax.numpy', - reasons=['Currently uses fancy indexing assignment.', - 'JAX does not support item assignment.']) def test_basic(self, x, xp): # Invert distribution CDF and compare against distribution `ppf` default_dtype = xp.asarray(1.).dtype @@ -34,6 +36,7 @@ def test_basic(self, x, xp): if not is_torch: xp_assert_less(xp.abs(res.df - ref), res.error) + @pytest.mark.skip_xp_backends(np_only=True) @pytest.mark.parametrize('case', stats._distr_params.distcont) def test_accuracy(self, case): distname, params = case @@ -43,13 +46,8 @@ def test_accuracy(self, case): ref = dist.pdf(x) assert_allclose(res.df, ref, atol=1e-10) - @array_api_compatible @pytest.mark.parametrize('order', [1, 6]) @pytest.mark.parametrize('shape', [tuple(), (12,), (3, 4), (3, 2, 2)]) - @pytest.mark.usefixtures("skip_xp_backends") - @pytest.mark.skip_xp_backends('array_api_strict', 'jax.numpy', - reasons=['Currently uses fancy indexing assignment.', - 'JAX does not support item assignment.']) def test_vectorization(self, order, shape, xp): # Test for correct functionality, output shapes, and dtypes for various # input shapes. @@ -78,7 +76,7 @@ def f(x, *args, **kwargs): ref_error = [ref.error for ref in refs] xp_assert_close(xp.reshape(res.error, (-1,)), xp.asarray(ref_error), - atol=1e-13) + atol=1e-12) ref_success = [bool(ref.success) for ref in refs] xp_assert_equal(xp.reshape(res.success, (-1,)), xp.asarray(ref_success)) @@ -96,89 +94,89 @@ def f(x, *args, **kwargs): if not is_numpy(xp): # can't expect other backends to be exactly the same xp.max(res.nit) == f.nit - def test_flags(self): + def test_flags(self, xp): # Test cases that should produce different status flags; show that all # can be produced simultaneously. rng = np.random.default_rng(5651219684984213) def f(xs, js): f.nit += 1 funcs = [lambda x: x - 2.5, # converges - lambda x: np.exp(x)*rng.random(), # error increases - lambda x: np.exp(x), # reaches maxiter due to order=2 - lambda x: np.full_like(x, np.nan)[()]] # stops due to NaN - res = [funcs[j](x) for x, j in zip(xs, js.ravel())] + lambda x: xp.exp(x)*rng.random(), # error increases + lambda x: xp.exp(x), # reaches maxiter due to order=2 + lambda x: xp.full_like(x, xp.nan)[()]] # stops due to NaN + res = [funcs[int(j)](x) for x, j in zip(xs, xp.reshape(js, (-1,)))] return res f.nit = 0 - args = (np.arange(4, dtype=np.int64),) - res = differentiate(f, [1]*4, rtol=1e-14, order=2, args=args) + args = (xp.arange(4, dtype=xp.int64),) + res = differentiate(f, xp.ones(4, dtype=xp.int32), rtol=1e-14, + order=2, args=args) - ref_flags = np.array([eim._ECONVERGED, - _EERRORINCREASE, - eim._ECONVERR, - eim._EVALUEERR]) - assert_equal(res.status, ref_flags) + ref_flags = xp.asarray([eim._ECONVERGED, + _EERRORINCREASE, + eim._ECONVERR, + eim._EVALUEERR]) + xp_assert_equal(res.status, ref_flags) - def test_flags_preserve_shape(self): + def test_flags_preserve_shape(self, xp): # Same test as above but using `preserve_shape` option to simplify. rng = np.random.default_rng(5651219684984213) def f(x): return [x - 2.5, # converges - np.exp(x)*rng.random(), # error increases + xp.exp(x)*rng.random(), # error increases np.exp(x), # reaches maxiter due to order=2 - np.full_like(x, np.nan)[()]] # stops due to NaN + np.full_like(x, xp.nan)[()]] # stops due to NaN - res = differentiate(f, 1, rtol=1e-14, order=2, preserve_shape=True) + res = differentiate(f, xp.asarray(1), rtol=1e-14, + order=2, preserve_shape=True) - ref_flags = np.array([eim._ECONVERGED, - _EERRORINCREASE, - eim._ECONVERR, - eim._EVALUEERR]) - assert_equal(res.status, ref_flags) + ref_flags = xp.asarray([eim._ECONVERGED, + _EERRORINCREASE, + eim._ECONVERR, + eim._EVALUEERR]) + xp_assert_equal(res.status, ref_flags) - def test_preserve_shape(self): + def test_preserve_shape(self, xp): # Test `preserve_shape` option def f(x): - return [x, np.sin(3*x), x+np.sin(10*x), np.sin(20*x)*(x-1)**2] + return [x, xp.sin(3*x), x+xp.sin(10*x), xp.sin(20*x)*(x-1)**2] - x = 0 - ref = [1, 3*np.cos(3*x), 1+10*np.cos(10*x), - 20*np.cos(20*x)*(x-1)**2 + 2*np.sin(20*x)*(x-1)] + x = xp.asarray(0.) + ref = xp.asarray([xp.asarray(1), 3*xp.cos(3*x), 1+10*xp.cos(10*x), + 20*xp.cos(20*x)*(x-1)**2 + 2*xp.sin(20*x)*(x-1)]) res = differentiate(f, x, preserve_shape=True) - assert_allclose(res.df, ref) + xp_assert_close(res.df, ref) - def test_convergence(self): + def test_convergence(self, xp): # Test that the convergence tolerances behave as expected - dist = stats.norm() - x = 1 - f = dist.cdf - ref = dist.pdf(x) + x = xp.asarray(1.) + f = special.ndtr + ref = float(stats.norm.pdf(1.)) kwargs0 = dict(atol=0, rtol=0, order=4) kwargs = kwargs0.copy() kwargs['atol'] = 1e-3 res1 = differentiate(f, x, **kwargs) - assert_array_less(abs(res1.df - ref), 1e-3) + assert abs(res1.df - ref) < 1e-3 kwargs['atol'] = 1e-6 res2 = differentiate(f, x, **kwargs) - assert_array_less(abs(res2.df - ref), 1e-6) - assert_array_less(abs(res2.df - ref), abs(res1.df - ref)) + assert abs(res2.df - ref) < 1e-6 + assert abs(res2.df - ref) < abs(res1.df - ref) kwargs = kwargs0.copy() kwargs['rtol'] = 1e-3 res1 = differentiate(f, x, **kwargs) - assert_array_less(abs(res1.df - ref), 1e-3 * np.abs(ref)) + assert abs(res1.df - ref) < 1e-3 * ref kwargs['rtol'] = 1e-6 res2 = differentiate(f, x, **kwargs) - assert_array_less(abs(res2.df - ref), 1e-6 * np.abs(ref)) - assert_array_less(abs(res2.df - ref), abs(res1.df - ref)) + assert abs(res2.df - ref) < 1e-6 * ref + assert abs(res2.df - ref) < abs(res1.df - ref) - def test_step_parameters(self): + def test_step_parameters(self, xp): # Test that step factors have the expected effect on accuracy - dist = stats.norm() - x = 1 - f = dist.cdf - ref = dist.pdf(x) + x = xp.asarray(1.) + f = special.ndtr + ref = float(stats.norm.pdf(1.)) res1 = differentiate(f, x, initial_step=0.5, maxiter=1) res2 = differentiate(f, x, initial_step=0.05, maxiter=1) @@ -192,36 +190,36 @@ def test_step_parameters(self): kwargs = dict(order=4, maxiter=1, step_direction=0) res = differentiate(f, x, initial_step=0.5, step_factor=0.5, **kwargs) ref = differentiate(f, x, initial_step=1, step_factor=2, **kwargs) - assert_allclose(res.df, ref.df, rtol=5e-15) + xp_assert_close(res.df, ref.df, rtol=5e-15) # This is a similar test for one-sided difference kwargs = dict(order=2, maxiter=1, step_direction=1) res = differentiate(f, x, initial_step=1, step_factor=2, **kwargs) ref = differentiate(f, x, initial_step=1/np.sqrt(2), step_factor=0.5, **kwargs) - assert_allclose(res.df, ref.df, rtol=5e-15) + xp_assert_close(res.df, ref.df, rtol=5e-15) kwargs['step_direction'] = -1 res = differentiate(f, x, initial_step=1, step_factor=2, **kwargs) ref = differentiate(f, x, initial_step=1/np.sqrt(2), step_factor=0.5, **kwargs) - assert_allclose(res.df, ref.df, rtol=5e-15) + xp_assert_close(res.df, ref.df, rtol=5e-15) - def test_step_direction(self): + def test_step_direction(self, xp): # test that `step_direction` works as expected def f(x): - y = np.exp(x) - y[(x < 0) + (x > 2)] = np.nan + y = xp.exp(x) + y[(x < 0) + (x > 2)] = xp.nan return y - x = np.linspace(0, 2, 10) - step_direction = np.zeros_like(x) + x = xp.linspace(0, 2, 10) + step_direction = xp.zeros_like(x) step_direction[x < 0.6], step_direction[x > 1.4] = 1, -1 res = differentiate(f, x, step_direction=step_direction) - assert_allclose(res.df, np.exp(x)) - assert np.all(res.success) + xp_assert_close(res.df, xp.exp(x)) + assert xp.all(res.success) - def test_vectorized_step_direction_args(self): + def test_vectorized_step_direction_args(self, xp): # test that `step_direction` and `args` are vectorized properly def f(x, p): return x ** p @@ -229,35 +227,35 @@ def f(x, p): def df(x, p): return p * x ** (p - 1) - x = np.array([1, 2, 3, 4]).reshape(-1, 1, 1) - hdir = np.array([-1, 0, 1]).reshape(1, -1, 1) - p = np.array([2, 3]).reshape(1, 1, -1) + x = xp.reshape(xp.asarray([1, 2, 3, 4]), (-1, 1, 1)) + hdir = xp.reshape(xp.asarray([-1, 0, 1]), (1, -1, 1)) + p = xp.reshape(xp.asarray([2, 3]), (1, 1, -1)) res = differentiate(f, x, step_direction=hdir, args=(p,)) - ref = np.broadcast_to(df(x, p), res.df.shape) - assert_allclose(res.df, ref) + ref = xp.broadcast_to(df(x, p), res.df.shape) + ref = xp.asarray(ref, dtype=xp.asarray(1.).dtype) + xp_assert_close(res.df, ref) - def test_maxiter_callback(self): + def test_maxiter_callback(self, xp): # Test behavior of `maxiter` parameter and `callback` interface - x = 0.612814 - dist = stats.norm() + x = xp.asarray(0.612814) maxiter = 3 def f(x): - res = dist.cdf(x) + res = special.ndtr(x) return res default_order = 8 res = differentiate(f, x, maxiter=maxiter, rtol=1e-15) - assert not np.any(res.success) - assert np.all(res.nfev == default_order + 1 + (maxiter - 1)*2) - assert np.all(res.nit == maxiter) + assert not xp.any(res.success) + assert xp.all(res.nfev == default_order + 1 + (maxiter - 1)*2) + assert xp.all(res.nit == maxiter) def callback(res): callback.iter += 1 callback.res = res assert hasattr(res, 'x') - assert res.df not in callback.dfs - callback.dfs.add(res.df) + assert float(res.df) not in callback.dfs + callback.dfs.add(float(res.df)) assert res.status == eim._EINPROGRESS if callback.iter == maxiter: raise StopIteration @@ -271,21 +269,24 @@ def callback(res): for key in res.keys(): if key == 'status': assert res[key] == eim._ECONVERR - assert callback.res[key] == eim._EINPROGRESS assert res2[key] == eim._ECALLBACK else: assert res2[key] == callback.res[key] == res[key] @pytest.mark.parametrize("hdir", (-1, 0, 1)) @pytest.mark.parametrize("x", (0.65, [0.65, 0.7])) - @pytest.mark.parametrize("dtype", (np.float16, np.float32, np.float64)) - def test_dtype(self, hdir, x, dtype): + @pytest.mark.parametrize("dtype", ('float16', 'float32', 'float64')) + def test_dtype(self, hdir, x, dtype, xp): + if dtype == 'float16' and not is_numpy(xp): + pytest.skip('float16 not tested for alternative backends') + # Test that dtypes are preserved - x = np.asarray(x, dtype=dtype)[()] + dtype = getattr(xp, dtype) + x = xp.asarray(x, dtype=dtype)[()] def f(x): assert x.dtype == dtype - return np.exp(x) + return xp.exp(x) def callback(res): assert res.x.dtype == dtype @@ -297,66 +298,68 @@ def callback(res): assert res.x.dtype == dtype assert res.df.dtype == dtype assert res.error.dtype == dtype - eps = np.finfo(dtype).eps - assert_allclose(res.df, np.exp(res.x), rtol=np.sqrt(eps)) + eps = xp.finfo(dtype).eps + xp_assert_close(res.df, xp.exp(res.x), rtol=eps**0.5) - def test_input_validation(self): + def test_input_validation(self, xp): # Test input validation for appropriate error messages + one = xp.asarray(1) message = '`func` must be callable.' with pytest.raises(ValueError, match=message): - differentiate(None, 1) + differentiate(None, one) message = 'Abscissae and function output must be real numbers.' with pytest.raises(ValueError, match=message): - differentiate(lambda x: x, -4+1j) + differentiate(lambda x: x, xp.asarray(-4+1j)) message = "When `preserve_shape=False`, the shape of the array..." with pytest.raises(ValueError, match=message): - differentiate(lambda x: [1, 2, 3], [-2, -3]) + differentiate(lambda x: [1, 2, 3], xp.asarray([-2, -3])) message = 'Tolerances and step parameters must be non-negative...' with pytest.raises(ValueError, match=message): - differentiate(lambda x: x, 1, atol=-1) + differentiate(lambda x: x, one, atol=-1) with pytest.raises(ValueError, match=message): - differentiate(lambda x: x, 1, rtol='ekki') + differentiate(lambda x: x, one, rtol='ekki') with pytest.raises(ValueError, match=message): - differentiate(lambda x: x, 1, initial_step=None) + differentiate(lambda x: x, one, initial_step=None) with pytest.raises(ValueError, match=message): - differentiate(lambda x: x, 1, step_factor=object()) + differentiate(lambda x: x, one, step_factor=object()) message = '`maxiter` must be a positive integer.' with pytest.raises(ValueError, match=message): - differentiate(lambda x: x, 1, maxiter=1.5) + differentiate(lambda x: x, one, maxiter=1.5) with pytest.raises(ValueError, match=message): - differentiate(lambda x: x, 1, maxiter=0) + differentiate(lambda x: x, one, maxiter=0) message = '`order` must be a positive integer' with pytest.raises(ValueError, match=message): - differentiate(lambda x: x, 1, order=1.5) + differentiate(lambda x: x, one, order=1.5) with pytest.raises(ValueError, match=message): - differentiate(lambda x: x, 1, order=0) + differentiate(lambda x: x, one, order=0) message = '`preserve_shape` must be True or False.' with pytest.raises(ValueError, match=message): - differentiate(lambda x: x, 1, preserve_shape='herring') + differentiate(lambda x: x, one, preserve_shape='herring') message = '`callback` must be callable.' with pytest.raises(ValueError, match=message): - differentiate(lambda x: x, 1, callback='shrubbery') + differentiate(lambda x: x, one, callback='shrubbery') - def test_special_cases(self): + def test_special_cases(self, xp): # Test edge cases and other special cases # Test that integers are not passed to `f` # (otherwise this would overflow) def f(x): - assert np.issubdtype(x.dtype, np.floating) + xp_test = array_namespace(x) # needs `isdtype` + assert xp_test.isdtype(x.dtype, 'real floating') return x ** 99 - 1 - res = differentiate(f, 7, rtol=1e-10) + res = differentiate(f, xp.asarray(7), rtol=1e-10) assert res.success - assert_allclose(res.df, 99*7.**98) + xp_assert_close(res.df, xp.asarray(99*7.**98)) # Test that if success is achieved in the correct number # of iterations if function is a polynomial. Ideally, all polynomials @@ -366,28 +369,29 @@ def f(x): # extra iteration to detect convergence based on the error estimate. for n in range(6): - x = 1.5 + x = xp.asarray(1.5) def f(x): return 2*x**n ref = 2*n*x**(n-1) res = differentiate(f, x, maxiter=1, order=max(1, n)) - assert_allclose(res.df, ref, rtol=1e-15) - assert_equal(res.error, np.nan) + xp_assert_close(res.df, ref, rtol=1e-15) + xp_assert_equal(res.error, xp.asarray(xp.nan)) res = differentiate(f, x, order=max(1, n)) assert res.success assert res.nit == 2 - assert_allclose(res.df, ref, rtol=1e-15) + xp_assert_close(res.df, ref, rtol=1e-15) # Test scalar `args` (not in tuple) def f(x, c): return c*x - 1 - res = differentiate(f, 2, args=3) - assert_allclose(res.df, 3) + res = differentiate(f, xp.asarray(2), args=xp.asarray(3)) + xp_assert_close(res.df, xp.asarray(3.)) + @pytest.mark.skip_xp_backends(np_only=True) @pytest.mark.xfail @pytest.mark.parametrize("case", ( # function, evaluation point (lambda x: (x - 1) ** 3, 1), From c9044eac54d18db7f99e9ad09174ab148f085a82 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 9 Jun 2024 14:50:28 -0700 Subject: [PATCH 388/500] TST: optimize._differentiate: fix tests for PyTorch [skip ci] --- .github/workflows/array_api.yml | 6 +-- scipy/optimize/_differentiate.py | 2 +- scipy/optimize/tests/test_differentiate.py | 49 ++++++++++++---------- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 28e110724f87..769943bae7a1 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -101,9 +101,5 @@ jobs: python dev.py --no-build test -b all -t scipy._lib.tests.test_array_api -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy._lib.tests.test__util -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.optimize.tests.test_chandrupatla -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -s stats -- --durations 3 --timeout=60 python dev.py --no-build test -b all -t scipy.optimize.tests.test_differentiate -- --durations 3 --timeout=60 - - - - + python dev.py --no-build test -b all -s stats -- --durations 3 --timeout=60 diff --git a/scipy/optimize/_differentiate.py b/scipy/optimize/_differentiate.py index 3558a5dc0076..ecb96863f4df 100644 --- a/scipy/optimize/_differentiate.py +++ b/scipy/optimize/_differentiate.py @@ -629,7 +629,7 @@ def _differentiate_weights(work, n, xp): if len(_differentiate_weights.central) != 2*n + 1: # Central difference weights. Consider refactoring this; it could # probably be more compact. - # Note: `n` and `fac` are NumPy scalars; we convert to xp-type at the end + # Note: Using NumPy here is OK; we convert to xp-type at the end i = np.arange(-n, n + 1) p = np.abs(i) - 1. # center point has power `p` -1, but sign `s` is 0 s = np.sign(i) diff --git a/scipy/optimize/tests/test_differentiate.py b/scipy/optimize/tests/test_differentiate.py index 2b96212682a5..11485ac47ab0 100644 --- a/scipy/optimize/tests/test_differentiate.py +++ b/scipy/optimize/tests/test_differentiate.py @@ -1,7 +1,7 @@ import pytest import numpy as np -from numpy.testing import assert_array_less, assert_allclose, assert_equal +from numpy.testing import assert_allclose from scipy.conftest import array_api_compatible import scipy._lib._elementwise_iterative_method as eim @@ -31,9 +31,9 @@ def test_basic(self, x, xp): res = differentiate(self.f, xp.asarray(x, dtype=default_dtype)) ref = xp.asarray(stats.norm().pdf(x), dtype=default_dtype) xp_assert_close(res.df, ref) - # # This would be nice, but doesn't always work out. `error` is an - # # estimate, not a bound. - if not is_torch: + # This would be nice, but doesn't always work out. `error` is an + # estimate, not a bound. + if not is_torch(xp): xp_assert_less(xp.abs(res.df - ref), res.error) @pytest.mark.skip_xp_backends(np_only=True) @@ -105,41 +105,43 @@ def f(xs, js): lambda x: xp.exp(x), # reaches maxiter due to order=2 lambda x: xp.full_like(x, xp.nan)[()]] # stops due to NaN res = [funcs[int(j)](x) for x, j in zip(xs, xp.reshape(js, (-1,)))] - return res + return xp.stack(res) f.nit = 0 args = (xp.arange(4, dtype=xp.int64),) - res = differentiate(f, xp.ones(4, dtype=xp.int32), rtol=1e-14, + res = differentiate(f, xp.ones(4, dtype=xp.float64), rtol=1e-14, order=2, args=args) ref_flags = xp.asarray([eim._ECONVERGED, _EERRORINCREASE, eim._ECONVERR, - eim._EVALUEERR]) + eim._EVALUEERR], dtype=xp.int32) xp_assert_equal(res.status, ref_flags) def test_flags_preserve_shape(self, xp): # Same test as above but using `preserve_shape` option to simplify. rng = np.random.default_rng(5651219684984213) def f(x): - return [x - 2.5, # converges - xp.exp(x)*rng.random(), # error increases - np.exp(x), # reaches maxiter due to order=2 - np.full_like(x, xp.nan)[()]] # stops due to NaN + out = [x - 2.5, # converges + xp.exp(x)*rng.random(), # error increases + xp.exp(x), # reaches maxiter due to order=2 + xp.full_like(x, xp.nan)[()]] # stops due to NaN + return xp.stack(out) - res = differentiate(f, xp.asarray(1), rtol=1e-14, + res = differentiate(f, xp.asarray(1, dtype=xp.float64), rtol=1e-14, order=2, preserve_shape=True) ref_flags = xp.asarray([eim._ECONVERGED, _EERRORINCREASE, eim._ECONVERR, - eim._EVALUEERR]) + eim._EVALUEERR], dtype=xp.int32) xp_assert_equal(res.status, ref_flags) def test_preserve_shape(self, xp): # Test `preserve_shape` option def f(x): - return [x, xp.sin(3*x), x+xp.sin(10*x), xp.sin(20*x)*(x-1)**2] + out = [x, xp.sin(3*x), x+xp.sin(10*x), xp.sin(20*x)*(x-1)**2] + return xp.stack(out) x = xp.asarray(0.) ref = xp.asarray([xp.asarray(1), 3*xp.cos(3*x), 1+10*xp.cos(10*x), @@ -149,7 +151,7 @@ def f(x): def test_convergence(self, xp): # Test that the convergence tolerances behave as expected - x = xp.asarray(1.) + x = xp.asarray(1., dtype=xp.float64) f = special.ndtr ref = float(stats.norm.pdf(1.)) kwargs0 = dict(atol=0, rtol=0, order=4) @@ -174,7 +176,7 @@ def test_convergence(self, xp): def test_step_parameters(self, xp): # Test that step factors have the expected effect on accuracy - x = xp.asarray(1.) + x = xp.asarray(1., dtype=xp.float64) f = special.ndtr ref = float(stats.norm.pdf(1.)) @@ -299,7 +301,9 @@ def callback(res): assert res.df.dtype == dtype assert res.error.dtype == dtype eps = xp.finfo(dtype).eps - xp_assert_close(res.df, xp.exp(res.x), rtol=eps**0.5) + # not sure why torch is less accurate here; might be worth investigating + rtol = eps**0.5 * 50 if is_torch(xp) else eps**0.5 + xp_assert_close(res.df, xp.exp(res.x), rtol=rtol) def test_input_validation(self, xp): # Test input validation for appropriate error messages @@ -357,9 +361,10 @@ def f(x): assert xp_test.isdtype(x.dtype, 'real floating') return x ** 99 - 1 - res = differentiate(f, xp.asarray(7), rtol=1e-10) - assert res.success - xp_assert_close(res.df, xp.asarray(99*7.**98)) + if not is_torch(xp): # torch defaults to float32 + res = differentiate(f, xp.asarray(7), rtol=1e-10) + assert res.success + xp_assert_close(res.df, xp.asarray(99*7.**98)) # Test that if success is achieved in the correct number # of iterations if function is a polynomial. Ideally, all polynomials @@ -369,7 +374,7 @@ def f(x): # extra iteration to detect convergence based on the error estimate. for n in range(6): - x = xp.asarray(1.5) + x = xp.asarray(1.5, dtype=xp.float64) def f(x): return 2*x**n @@ -377,7 +382,7 @@ def f(x): res = differentiate(f, x, maxiter=1, order=max(1, n)) xp_assert_close(res.df, ref, rtol=1e-15) - xp_assert_equal(res.error, xp.asarray(xp.nan)) + xp_assert_equal(res.error, xp.asarray(xp.nan, dtype=xp.float64)) res = differentiate(f, x, order=max(1, n)) assert res.success From 2e6ad87e455fab7ed776d5640a7dfe998d164fe8 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 9 Jun 2024 15:02:46 -0700 Subject: [PATCH 389/500] TST: optimize._differentiate: commit again to run CI --- scipy/optimize/tests/test_differentiate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/optimize/tests/test_differentiate.py b/scipy/optimize/tests/test_differentiate.py index 11485ac47ab0..cee63f8a3351 100644 --- a/scipy/optimize/tests/test_differentiate.py +++ b/scipy/optimize/tests/test_differentiate.py @@ -16,9 +16,8 @@ @array_api_compatible @pytest.mark.usefixtures("skip_xp_backends") @pytest.mark.skip_xp_backends('array_api_strict', 'jax.numpy', - reasons=[ - 'Currently uses fancy indexing assignment.', - 'JAX arrays do not support item assignment.']) + reasons=['Currently uses fancy indexing assignment.', + 'JAX arrays do not support item assignment.']) class TestDifferentiate: def f(self, x): @@ -396,6 +395,7 @@ def f(x, c): res = differentiate(f, xp.asarray(2), args=xp.asarray(3)) xp_assert_close(res.df, xp.asarray(3.)) + # no need to run a test on multiple backends if it's xfailed @pytest.mark.skip_xp_backends(np_only=True) @pytest.mark.xfail @pytest.mark.parametrize("case", ( # function, evaluation point From 9e1d08cd6fd8638049d393f0d4352698ce8a0f2b Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 9 Jun 2024 15:47:41 -0700 Subject: [PATCH 390/500] TST: optimize._differentiate: set dtype to pass test [skip cirrus] [skip circle] --- scipy/optimize/tests/test_differentiate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/tests/test_differentiate.py b/scipy/optimize/tests/test_differentiate.py index cee63f8a3351..daf0daeabd00 100644 --- a/scipy/optimize/tests/test_differentiate.py +++ b/scipy/optimize/tests/test_differentiate.py @@ -238,7 +238,7 @@ def df(x, p): def test_maxiter_callback(self, xp): # Test behavior of `maxiter` parameter and `callback` interface - x = xp.asarray(0.612814) + x = xp.asarray(0.612814, dtype=xp.float64) maxiter = 3 def f(x): From e1fecdfb0eb4f1bc01345b0a39930ab1cc94e1da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A8=97=E0=A8=97=E0=A8=A8=E0=A8=A6=E0=A9=80=E0=A8=AA=20?= =?UTF-8?q?=E0=A8=B8=E0=A8=BF=E0=A9=B0=E0=A8=98=20=28Gagandeep=20Singh=29?= Date: Tue, 11 Jun 2024 00:37:57 +0530 Subject: [PATCH 391/500] MAINT: special: fix msvc build by using new and delete in AMOS (#20920) --- scipy/special/special/amos/amos.h | 100 +++++++++++++++--------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/scipy/special/special/amos/amos.h b/scipy/special/special/amos/amos.h index b5fcfe244573..fdf891517f9c 100644 --- a/scipy/special/special/amos/amos.h +++ b/scipy/special/special/amos/amos.h @@ -152,10 +152,10 @@ constexpr double i1mach[16] = { }; constexpr double zunhj_ar[14] = { - 1.00000000000000000e+00, 1.04166666666666667e-01, 8.35503472222222222e-02, 1.28226574556327160e-01, // 0 + 1.00000000000000000e+00, 1.04166666666666667e-01, 8.35503472222222222e-02, 1.28226574556327160e-01, // 0 2.91849026464140464e-01, 8.81627267443757652e-01, 3.32140828186276754e+00, 1.49957629868625547e+01, // 4 7.89230130115865181e+01, 4.74451538868264323e+02, 3.20749009089066193e+03, 2.40865496408740049e+04, // 8 - 1.98923119169509794e+05, 1.79190200777534383e+06 // 12 + 1.98923119169509794e+05, 1.79190200777534383e+06 // 12 }; constexpr double zunhj_br[14] = { @@ -166,7 +166,7 @@ constexpr double zunhj_br[14] = { }; constexpr double zunhj_c[105] = { - 1.00000000000000000e+00, -2.08333333333333333e-01, 1.25000000000000000e-01, 3.34201388888888889e-01, // 0 + 1.00000000000000000e+00, -2.08333333333333333e-01, 1.25000000000000000e-01, 3.34201388888888889e-01, // 0 -4.01041666666666667e-01, 7.03125000000000000e-02, -1.02581259645061728e+00, 1.84646267361111111e+00, // 4 -8.91210937500000000e-01, 7.32421875000000000e-02, 4.66958442342624743e+00, -1.12070026162229938e+01, // 8 8.78912353515625000e+00, -2.36408691406250000e+00, 1.12152099609375000e-01, -2.82120725582002449e+01, // 12 @@ -191,7 +191,7 @@ constexpr double zunhj_c[105] = { 1.73951075539781645e+07, -5.49842327572288687e+05, 3.03809051092238427e+03, -1.46792612476956167e+10, // 88 1.14498237732025810e+11, -3.99096175224466498e+11, 8.19218669548577329e+11, -1.09837515608122331e+12, // 92 1.00815810686538209e+12, -6.45364869245376503e+11, 2.87900649906150589e+11, -8.78670721780232657e+10, // 96 - 1.76347306068349694e+10, -2.16716498322379509e+09, 1.43157876718888981e+08, -3.87183344257261262e+06, // 100 + 1.76347306068349694e+10, -2.16716498322379509e+09, 1.43157876718888981e+08, -3.87183344257261262e+06, // 100 1.82577554742931747e+04 // 104 }; @@ -200,49 +200,49 @@ constexpr double zunhj_alfa[180] = { 2.46691372741792910e-04, 2.65995589346254780e-04, 2.61824297061500945e-04, 2.48730437344655609e-04, // 4 2.32721040083232098e-04, 2.16362485712365082e-04, 2.00738858762752355e-04, 1.86267636637545172e-04, // 8 1.73060775917876493e-04, 1.61091705929015752e-04, 1.50274774160908134e-04, 1.40503497391269794e-04, // 12 - 1.31668816545922806e-04, 1.23667445598253261e-04, 1.16405271474737902e-04, 1.09798298372713369e-04, // 16 - 1.03772410422992823e-04, 9.82626078369363448e-05, 9.32120517249503256e-05, 8.85710852478711718e-05, // 20 - 8.42963105715700223e-05, 8.03497548407791151e-05, 7.66981345359207388e-05, 7.33122157481777809e-05, // 24 - 7.01662625163141333e-05, 6.72375633790160292e-05, 6.93735541354588974e-04, 2.32241745182921654e-04, // 28 - -1.41986273556691197e-05, -1.16444931672048640e-04, -1.50803558053048762e-04, -1.55121924918096223e-04, // 32 - -1.46809756646465549e-04, -1.33815503867491367e-04, -1.19744975684254051e-04, -1.06184319207974020e-04, // 36 - -9.37699549891194492e-05, -8.26923045588193274e-05, -7.29374348155221211e-05, -6.44042357721016283e-05, // 40 - -5.69611566009369048e-05, -5.04731044303561628e-05, -4.48134868008882786e-05, -3.98688727717598864e-05, // 44 - -3.55400532972042498e-05, -3.17414256609022480e-05, -2.83996793904174811e-05, -2.54522720634870566e-05, // 48 - -2.28459297164724555e-05, -2.05352753106480604e-05, -1.84816217627666085e-05, -1.66519330021393806e-05, // 52 - -1.50179412980119482e-05, -1.35554031379040526e-05, -1.22434746473858131e-05, -1.10641884811308169e-05, // 56 - -3.54211971457743841e-04, -1.56161263945159416e-04, 3.04465503594936410e-05, 1.30198655773242693e-04, // 60 - 1.67471106699712269e-04, 1.70222587683592569e-04, 1.56501427608594704e-04, 1.36339170977445120e-04, // 64 - 1.14886692029825128e-04, 9.45869093034688111e-05, 7.64498419250898258e-05, 6.07570334965197354e-05, // 68 - 4.74394299290508799e-05, 3.62757512005344297e-05, 2.69939714979224901e-05, 1.93210938247939253e-05, // 72 - 1.30056674793963203e-05, 7.82620866744496661e-06, 3.59257485819351583e-06, 1.44040049814251817e-07, // 76 - -2.65396769697939116e-06, -4.91346867098485910e-06, -6.72739296091248287e-06, -8.17269379678657923e-06, // 80 - -9.31304715093561232e-06, -1.02011418798016441e-05, -1.08805962510592880e-05, -1.13875481509603555e-05, // 84 - -1.17519675674556414e-05, -1.19987364870944141e-05, 3.78194199201772914e-04, 2.02471952761816167e-04, // 88 - -6.37938506318862408e-05, -2.38598230603005903e-04, -3.10916256027361568e-04, -3.13680115247576316e-04, // 92 - -2.78950273791323387e-04, -2.28564082619141374e-04, -1.75245280340846749e-04, -1.25544063060690348e-04, // 96 - -8.22982872820208365e-05, -4.62860730588116458e-05, -1.72334302366962267e-05, 5.60690482304602267e-06, // 100 - 2.31395443148286800e-05, 3.62642745856793957e-05, 4.58006124490188752e-05, 5.24595294959114050e-05, // 104 - 5.68396208545815266e-05, 5.94349820393104052e-05, 6.06478527578421742e-05, 6.08023907788436497e-05, // 108 - 6.01577894539460388e-05, 5.89199657344698500e-05, 5.72515823777593053e-05, 5.52804375585852577e-05, // 112 - 5.31063773802880170e-05, 5.08069302012325706e-05, 4.84418647620094842e-05, 4.60568581607475370e-05, // 116 - -6.91141397288294174e-04, -4.29976633058871912e-04, 1.83067735980039018e-04, 6.60088147542014144e-04, // 120 - 8.75964969951185931e-04, 8.77335235958235514e-04, 7.49369585378990637e-04, 5.63832329756980918e-04, // 124 - 3.68059319971443156e-04, 1.88464535514455599e-04, 3.70663057664904149e-05, -8.28520220232137023e-05, // 128 - -1.72751952869172998e-04, -2.36314873605872983e-04, -2.77966150694906658e-04, -3.02079514155456919e-04, // 132 - -3.12594712643820127e-04, -3.12872558758067163e-04, -3.05678038466324377e-04, -2.93226470614557331e-04, // 136 - -2.77255655582934777e-04, -2.59103928467031709e-04, -2.39784014396480342e-04, -2.20048260045422848e-04, // 140 - -2.00443911094971498e-04, -1.81358692210970687e-04, -1.63057674478657464e-04, -1.45712672175205844e-04, // 144 - -1.29425421983924587e-04, -1.14245691942445952e-04, 1.92821964248775885e-03, 1.35592576302022234e-03, // 148 - -7.17858090421302995e-04, -2.58084802575270346e-03, -3.49271130826168475e-03, -3.46986299340960628e-03, // 152 - -2.82285233351310182e-03, -1.88103076404891354e-03, -8.89531718383947600e-04, 3.87912102631035228e-06, // 156 - 7.28688540119691412e-04, 1.26566373053457758e-03, 1.62518158372674427e-03, 1.83203153216373172e-03, // 160 - 1.91588388990527909e-03, 1.90588846755546138e-03, 1.82798982421825727e-03, 1.70389506421121530e-03, // 164 - 1.55097127171097686e-03, 1.38261421852276159e-03, 1.20881424230064774e-03, 1.03676532638344962e-03, // 168 - 8.71437918068619115e-04, 7.16080155297701002e-04, 5.72637002558129372e-04, 4.42089819465802277e-04, // 172 - 3.24724948503090564e-04, 2.20342042730246599e-04, 1.28412898401353882e-04, 4.82005924552095464e-05 // 176 -}; - + 1.31668816545922806e-04, 1.23667445598253261e-04, 1.16405271474737902e-04, 1.09798298372713369e-04, // 16 + 1.03772410422992823e-04, 9.82626078369363448e-05, 9.32120517249503256e-05, 8.85710852478711718e-05, // 20 + 8.42963105715700223e-05, 8.03497548407791151e-05, 7.66981345359207388e-05, 7.33122157481777809e-05, // 24 + 7.01662625163141333e-05, 6.72375633790160292e-05, 6.93735541354588974e-04, 2.32241745182921654e-04, // 28 + -1.41986273556691197e-05, -1.16444931672048640e-04, -1.50803558053048762e-04, -1.55121924918096223e-04, // 32 + -1.46809756646465549e-04, -1.33815503867491367e-04, -1.19744975684254051e-04, -1.06184319207974020e-04, // 36 + -9.37699549891194492e-05, -8.26923045588193274e-05, -7.29374348155221211e-05, -6.44042357721016283e-05, // 40 + -5.69611566009369048e-05, -5.04731044303561628e-05, -4.48134868008882786e-05, -3.98688727717598864e-05, // 44 + -3.55400532972042498e-05, -3.17414256609022480e-05, -2.83996793904174811e-05, -2.54522720634870566e-05, // 48 + -2.28459297164724555e-05, -2.05352753106480604e-05, -1.84816217627666085e-05, -1.66519330021393806e-05, // 52 + -1.50179412980119482e-05, -1.35554031379040526e-05, -1.22434746473858131e-05, -1.10641884811308169e-05, // 56 + -3.54211971457743841e-04, -1.56161263945159416e-04, 3.04465503594936410e-05, 1.30198655773242693e-04, // 60 + 1.67471106699712269e-04, 1.70222587683592569e-04, 1.56501427608594704e-04, 1.36339170977445120e-04, // 64 + 1.14886692029825128e-04, 9.45869093034688111e-05, 7.64498419250898258e-05, 6.07570334965197354e-05, // 68 + 4.74394299290508799e-05, 3.62757512005344297e-05, 2.69939714979224901e-05, 1.93210938247939253e-05, // 72 + 1.30056674793963203e-05, 7.82620866744496661e-06, 3.59257485819351583e-06, 1.44040049814251817e-07, // 76 + -2.65396769697939116e-06, -4.91346867098485910e-06, -6.72739296091248287e-06, -8.17269379678657923e-06, // 80 + -9.31304715093561232e-06, -1.02011418798016441e-05, -1.08805962510592880e-05, -1.13875481509603555e-05, // 84 + -1.17519675674556414e-05, -1.19987364870944141e-05, 3.78194199201772914e-04, 2.02471952761816167e-04, // 88 + -6.37938506318862408e-05, -2.38598230603005903e-04, -3.10916256027361568e-04, -3.13680115247576316e-04, // 92 + -2.78950273791323387e-04, -2.28564082619141374e-04, -1.75245280340846749e-04, -1.25544063060690348e-04, // 96 + -8.22982872820208365e-05, -4.62860730588116458e-05, -1.72334302366962267e-05, 5.60690482304602267e-06, // 100 + 2.31395443148286800e-05, 3.62642745856793957e-05, 4.58006124490188752e-05, 5.24595294959114050e-05, // 104 + 5.68396208545815266e-05, 5.94349820393104052e-05, 6.06478527578421742e-05, 6.08023907788436497e-05, // 108 + 6.01577894539460388e-05, 5.89199657344698500e-05, 5.72515823777593053e-05, 5.52804375585852577e-05, // 112 + 5.31063773802880170e-05, 5.08069302012325706e-05, 4.84418647620094842e-05, 4.60568581607475370e-05, // 116 + -6.91141397288294174e-04, -4.29976633058871912e-04, 1.83067735980039018e-04, 6.60088147542014144e-04, // 120 + 8.75964969951185931e-04, 8.77335235958235514e-04, 7.49369585378990637e-04, 5.63832329756980918e-04, // 124 + 3.68059319971443156e-04, 1.88464535514455599e-04, 3.70663057664904149e-05, -8.28520220232137023e-05, // 128 + -1.72751952869172998e-04, -2.36314873605872983e-04, -2.77966150694906658e-04, -3.02079514155456919e-04, // 132 + -3.12594712643820127e-04, -3.12872558758067163e-04, -3.05678038466324377e-04, -2.93226470614557331e-04, // 136 + -2.77255655582934777e-04, -2.59103928467031709e-04, -2.39784014396480342e-04, -2.20048260045422848e-04, // 140 + -2.00443911094971498e-04, -1.81358692210970687e-04, -1.63057674478657464e-04, -1.45712672175205844e-04, // 144 + -1.29425421983924587e-04, -1.14245691942445952e-04, 1.92821964248775885e-03, 1.35592576302022234e-03, // 148 + -7.17858090421302995e-04, -2.58084802575270346e-03, -3.49271130826168475e-03, -3.46986299340960628e-03, // 152 + -2.82285233351310182e-03, -1.88103076404891354e-03, -8.89531718383947600e-04, 3.87912102631035228e-06, // 156 + 7.28688540119691412e-04, 1.26566373053457758e-03, 1.62518158372674427e-03, 1.83203153216373172e-03, // 160 + 1.91588388990527909e-03, 1.90588846755546138e-03, 1.82798982421825727e-03, 1.70389506421121530e-03, // 164 + 1.55097127171097686e-03, 1.38261421852276159e-03, 1.20881424230064774e-03, 1.03676532638344962e-03, // 168 + 8.71437918068619115e-04, 7.16080155297701002e-04, 5.72637002558129372e-04, 4.42089819465802277e-04, // 172 + 3.24724948503090564e-04, 2.20342042730246599e-04, 1.28412898401353882e-04, 4.82005924552095464e-05 // 176 +}; + constexpr double zunhj_beta[210] = { 1.79988721413553309e-02, 5.59964911064388073e-03, 2.88501402231132779e-03, 1.80096606761053941e-03, // 0 1.24753110589199202e-03, 9.22878876572938311e-04, 7.14430421727287357e-04, 5.71787281789704872e-04, // 4 @@ -372,7 +372,7 @@ constexpr double dgamln_gln[100] = { }; constexpr double dgamln_cf[22] = { - 8.33333333333333333e-02, -2.77777777777777778e-03, 7.93650793650793651e-04, -5.95238095238095238e-04, // 0 + 8.33333333333333333e-02, -2.77777777777777778e-03, 7.93650793650793651e-04, -5.95238095238095238e-04, // 0 8.41750841750841751e-04, -1.91752691752691753e-03, 6.41025641025641026e-03, -2.95506535947712418e-02, // 4 1.79644372368830573e-01, -1.39243221690590112e+00, 1.34028640441683920e+01, -1.56848284626002017e+02, // 8 2.19310333333333333e+03, -3.61087712537249894e+04, 6.91472268851313067e+05, -1.52382215394074162e+07, // 12 @@ -2450,7 +2450,7 @@ inline int besy( std::complex c1, c2, hci, st; double elim, exr, exi, ey, tay, xx, yy, ascle, rtol, atol, tol, aa, bb, r1m5; int i, k, k1, k2, nz, nz1, nz2; - std::complex cwrk[n]; + std::complex* cwrk = new std::complex[n]; xx = std::real(z); yy = std::imag(z); @@ -2530,6 +2530,8 @@ inline int besy( cy[i-1] = st*hci; if ((st == 0.0) && (ey == 0.0)) { nz += 1; } } + + delete [] cwrk; return nz; } From d5e1e03ac12fb5ad7fa4949e7ffea2137e780b15 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 11 Jun 2024 03:42:14 -0600 Subject: [PATCH 392/500] ENH: `stats._xp_mean`, an array API compatible `mean` with `weights` and `nan_policy` (#20743) --- scipy/_lib/_util.py | 10 +- scipy/_lib/tests/test_warnings.py | 3 +- scipy/stats/_stats_py.py | 166 +++++++++++++++++++++- scipy/stats/tests/test_axis_nan_policy.py | 26 +++- scipy/stats/tests/test_stats.py | 121 +++++++++++++++- 5 files changed, 314 insertions(+), 12 deletions(-) diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py index d964f6fdba1a..ee55282dd532 100644 --- a/scipy/_lib/_util.py +++ b/scipy/_lib/_util.py @@ -707,7 +707,13 @@ def _nan_allsame(a, axis, keepdims=False): return ((a0 == a) | np.isnan(a)).all(axis=axis, keepdims=keepdims) -def _contains_nan(a, nan_policy='propagate', policies=None, *, xp=None): +def _contains_nan(a, nan_policy='propagate', policies=None, *, + xp_omit_okay=False, xp=None): + # Regarding `xp_omit_okay`: Temporarily, while `_axis_nan_policy` does not + # handle non-NumPy arrays, most functions that call `_contains_nan` want + # it to raise an error if `nan_policy='omit'` and `xp` is not `np`. + # Some functions support `nan_policy='omit'` natively, so setting this to + # `True` prevents the error from being raised. if xp is None: xp = array_namespace(a) not_numpy = not is_numpy(xp) @@ -738,7 +744,7 @@ def _contains_nan(a, nan_policy='propagate', policies=None, *, xp=None): if contains_nan and nan_policy == 'raise': raise ValueError("The input contains nan values") - if not_numpy and contains_nan and nan_policy=='omit': + if not xp_omit_okay and not_numpy and contains_nan and nan_policy=='omit': message = "`nan_policy='omit' is incompatible with non-NumPy arrays." raise ValueError(message) diff --git a/scipy/_lib/tests/test_warnings.py b/scipy/_lib/tests/test_warnings.py index b76971868f47..f18bc20cb033 100644 --- a/scipy/_lib/tests/test_warnings.py +++ b/scipy/_lib/tests/test_warnings.py @@ -118,9 +118,10 @@ def test_warning_calls_filters(warning_calls): os.path.join('stats', '_discrete_distns.py'), # gh-14901 os.path.join('stats', '_continuous_distns.py'), os.path.join('stats', '_binned_statistic.py'), # gh-19345 + os.path.join('stats', '_stats_py.py'), # gh-20743 os.path.join('stats', 'tests', 'test_axis_nan_policy.py'), # gh-20694 os.path.join('_lib', '_util.py'), # gh-19341 - os.path.join('sparse', 'linalg', '_dsolve', 'linsolve.py'), # gh-17924 + os.path.join('sparse', 'linalg', '_dsolve', 'linsolve.py'), # gh-17924 "conftest.py", ) bad_filters = [item for item in bad_filters if item.split(':')[0] not in diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index d0fd9250afe7..4409cc632cea 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -30,6 +30,7 @@ import math from math import gcd from collections import namedtuple +from collections.abc import Sequence import numpy as np from numpy import array, asarray, ma @@ -58,9 +59,11 @@ from ._resampling import (MonteCarloMethod, PermutationMethod, BootstrapMethod, monte_carlo_test, permutation_test, bootstrap, _batch_generator) -from ._axis_nan_policy import (_axis_nan_policy_factory, +from ._axis_nan_policy import (_axis_nan_policy_factory, _broadcast_arrays, _broadcast_concatenate, _broadcast_shapes, - _broadcast_array_shapes_remove_axis, SmallSampleWarning) + _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 from scipy._lib._bunch import _make_tuple_bunch from scipy import stats @@ -9307,7 +9310,7 @@ def combine_pvalues(pvalues, method='fisher', weights=None): if xp_size(pvalues) == 0: NaN = _get_nan(pvalues) return SignificanceResult(NaN, NaN) - + n = pvalues.shape[0] # used to convert Python scalar to the right dtype one = xp.asarray(1, dtype=pvalues.dtype) @@ -10944,6 +10947,161 @@ def linregress(x, y=None, alternative='two-sided'): intercept_stderr=intercept_stderr) +def _xp_mean(x, /, *, axis=None, weights=None, keepdims=False, nan_policy='propagate', + dtype=None, 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 : boolean, 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 = xp.asarray(x, dtype=dtype) + 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) + + # handle non-broadcastable inputs + if weights is not None and x.shape != weights.shape: + try: + x, weights = _broadcast_arrays((x, weights), xp=xp) + except (ValueError, RuntimeError) as e: + message = "Array shapes are incompatible for broadcasting." + raise ValueError(message) from e + + # convert integers to the default float of the array library + if not xp.isdtype(x.dtype, 'real floating'): + dtype = xp.asarray(1.).dtype + x = xp.asarray(x, dtype=dtype) + if weights is not None and not xp.isdtype(weights.dtype, 'real floating'): + dtype = xp.asarray(1.).dtype + weights = xp.asarray(weights, dtype=dtype) + + # 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 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. + message = (too_small_1d_omit if (x.ndim == 1 or axis is None) + else too_small_nd_omit) + if contains_nan and nan_policy == 'omit': + nan_mask = xp.isnan(x) + if weights is not None: + nan_mask |= xp.isnan(weights) + if xp.any(xp.all(nan_mask, axis=axis)): + warnings.warn(message, SmallSampleWarning, stacklevel=2) + weights = xp.ones_like(x) if weights is None else weights + x = xp.where(nan_mask, xp.asarray(0, dtype=x.dtype), x) + weights = xp.where(nan_mask, xp.asarray(0, dtype=x.dtype), weights) + + # Perform the mean calculation itself + if weights is None: + return xp.mean(x, axis=axis, keepdims=keepdims) + + 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, final_shape) + + return res[()] if res.ndim == 0 else res + + class _SimpleNormal: # A very simple, array-API compatible normal distribution for use in # hypothesis tests. May be replaced by new infrastructure Normal @@ -10954,7 +11112,7 @@ def cdf(self, x): def sf(self, x): return special.ndtr(-x) - + def isf(self, x): return -special.ndtri(x) diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index bf01307a14ae..786cde6362e1 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -40,6 +40,17 @@ def ttest_ci(*args, **kwargs): 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) + + axis_nan_policy_cases = [ # function, args, kwds, number of samples, number of outputs, # ... paired, unpacker function @@ -115,6 +126,8 @@ def ttest_ci(*args, **kwargs): (stats.alexandergovern, tuple(), {}, 2, 2, False, lambda res: (res.statistic, res.pvalue)), (stats.combine_pvalues, tuple(), {}, 1, 2, False, None), + (xp_mean_1samp, tuple(), dict(), 1, 1, False, lambda x: (x,)), + (xp_mean_2samp, tuple(), dict(), 2, 1, True, lambda x: (x,)), ] # If the message is one of those expected, put nans in @@ -143,8 +156,11 @@ def ttest_ci(*args, **kwargs): "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",} + "divide by zero encountered", +} # If the message is one of these, results of the function may be inaccurate, # but NaNs are not to be placed @@ -406,7 +422,7 @@ def unpacker(res): res = hypotest(*data1d, *args, nan_policy=nan_policy, **kwds) res_1db = unpacker(res) - assert_equal(res_1db, res_1da) + assert_allclose(res_1db, res_1da, rtol=1e-15) res_1d[i] = res_1db res_1d = np.moveaxis(res_1d, -1, 0) @@ -531,7 +547,7 @@ def unpacker(res): all_results = list(res1db) + list(res1dc) if res1da is not None: - assert_equal(res1db, res1da) + assert_allclose(res1db, res1da, rtol=1e-15) all_results += list(res1da) for item in all_results: @@ -555,7 +571,9 @@ def unpacker(res): ("hypotest", "args", "kwds", "n_samples", "unpacker"), ((stats.gmean, tuple(), dict(), 1, lambda x: (x,)), (stats.mannwhitneyu, tuple(), {'method': 'asymptotic'}, 2, None), - (stats.ttest_1samp, (0,), dict(), 1, unpack_ttest_result)) + (stats.ttest_1samp, (0,), dict(), 1, unpack_ttest_result), + (xp_mean_1samp, tuple(), dict(), 1, lambda x: (x,)), + (xp_mean_2samp, tuple(), dict(), 2, lambda x: (x,))), ) @pytest.mark.parametrize( ("sample_shape", "axis_cases"), diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index a5232339e853..02e68c76ee6a 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -38,7 +38,7 @@ too_small_nd_omit, too_small_nd_not_omit, too_small_1d_omit, too_small_1d_not_omit) from scipy.stats._stats_py import (_permutation_distribution_t, _chk_asarray, _moment, - LinregressResult) + LinregressResult, _xp_mean) from scipy._lib._util import AxisError from scipy.conftest import array_api_compatible, skip_xp_invalid_arg from scipy._lib._array_api import (xp_assert_close, xp_assert_equal, array_namespace, @@ -9082,6 +9082,125 @@ def test_monotonicity_in_alpha(self, n): assert np.all(np.diff(e_list) > 0) +@array_api_compatible +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) + + 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)) + + def test_nan_policy(self, xp): + x = xp.arange(10.) + mask = (x == 3) + x = xp.where(mask, xp.asarray(xp.nan), x) + + # nan_policy='raise' raises an error + message = 'The input contains nan values' + with pytest.raises(ValueError, match=message): + _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.asarray(xp.nan), weights) + res = _xp_mean(xp.arange(10.), weights=weights, nan_policy='omit') + ref = xp.mean(x[~mask]) + xp_assert_close(res, ref) + + # Check for warning if omitting NaNs causes empty slice + message = 'After omitting NaNs...' + with pytest.warns(RuntimeWarning, match=message): + 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) + + 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)) + + @array_api_compatible def test_chk_asarray(xp): rng = np.random.default_rng(2348923425434) From c17b05e397a5e2cf5276920105b082c5ab24bc78 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 11 Jun 2024 09:12:05 -0600 Subject: [PATCH 393/500] DOC: `array_api.rst`: update 1.14 functions with array API support (#20936) --- doc/source/dev/api-dev/array_api.rst | 13 +++++++++++-- doc/source/release/1.14.0-notes.rst | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/source/dev/api-dev/array_api.rst b/doc/source/dev/api-dev/array_api.rst index 326e819c4e35..96527261eb00 100644 --- a/doc/source/dev/api-dev/array_api.rst +++ b/doc/source/dev/api-dev/array_api.rst @@ -105,10 +105,19 @@ Support is provided in `scipy.special` for the following functions: `scipy.special.erf`, `scipy.special.erfc`, `scipy.special.i0`, `scipy.special.i0e`, `scipy.special.i1`, `scipy.special.i1e`, `scipy.special.gammaln`, `scipy.special.gammainc`, `scipy.special.gammaincc`, -`scipy.special.logit`, and `scipy.special.expit`. +`scipy.special.logit`, `scipy.special.expit`, `scipy.special.entr`, +`scipy.special.rel_entr`, `scipy.special.rel_entr`, `scipy.special.xlogy`, +and `scipy.special.chdtrc`. Support is provided in `scipy.stats` for the following functions: -`scipy.stats.pearsonr` and `scipy.stats.moment`. +`scipy.stats.describe`, `scipy.stats.moment`, `scipy.stats.skew`, +`scipy.stats.kurtosis`, `scipy.stats.kstat`, `scipy.stats.kstatvar`, +`scipy.stats.circmean`, `scipy.stats.circvar`, `scipy.stats.circstd`, +`scipy.stats.entropy`, `scipy.stats.variation` , `scipy.stats.sem`, +`scipy.stats.ttest_1samp`, `scipy.stats.pearsonr`, `scipy.stats.chisquare`, +`scipy.stats.skewtest`, `scipy.stats.kurtosistest`, `scipy.stats.normaltest`, +`scipy.stats.jarque_bera`, `scipy.stats.bartlett`, `scipy.stats.power_divergence`, +and `scipy.stats.monte_carlo_test`. Implementation notes diff --git a/doc/source/release/1.14.0-notes.rst b/doc/source/release/1.14.0-notes.rst index 0d6b0c661fe9..27ec106ef125 100644 --- a/doc/source/release/1.14.0-notes.rst +++ b/doc/source/release/1.14.0-notes.rst @@ -160,6 +160,7 @@ As of 1.14.0, there is support for - `scipy.stats`: (select functions) + - `scipy.stats.describe` - `scipy.stats.moment` - `scipy.stats.skew` - `scipy.stats.kurtosis` From 4f8025835b6bfbc19c1c2b13161f77e793347a94 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 11 Jun 2024 20:14:44 -0400 Subject: [PATCH 394/500] TST: adjust tolerances of failing tests --- scipy/optimize/tests/test_differentiate.py | 2 +- scipy/special/tests/test_hyp2f1.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/optimize/tests/test_differentiate.py b/scipy/optimize/tests/test_differentiate.py index ad9407675b46..195fec2f180a 100644 --- a/scipy/optimize/tests/test_differentiate.py +++ b/scipy/optimize/tests/test_differentiate.py @@ -64,7 +64,7 @@ def f(x, *args, **kwargs): assert_equal(res.df.shape, shape) ref_error = [ref.error for ref in refs] - assert_allclose(res.error.ravel(), ref_error, atol=5e-15) + assert_allclose(res.error.ravel(), ref_error, atol=1e-12) assert_equal(res.error.shape, shape) ref_success = [ref.success for ref in refs] diff --git a/scipy/special/tests/test_hyp2f1.py b/scipy/special/tests/test_hyp2f1.py index f5a7978abed2..6c2f6ec103bf 100644 --- a/scipy/special/tests/test_hyp2f1.py +++ b/scipy/special/tests/test_hyp2f1.py @@ -791,7 +791,7 @@ def test_region1(self, hyp2f1_test_case): c=2.0397202577726152, z=(-0.3157894736842106+0.7368421052631575j), expected=(54749.216391029935-23078.144720887536j), - rtol=1e-15, + rtol=2e-15, ), ), pytest.param( @@ -2183,7 +2183,7 @@ def test_region4(self, hyp2f1_test_case): c=16.088264119063613, z=(0.26551724137931054+1.024137931034483j), expected=(-0.18515160890991517+0.7959014164484782j), - rtol=1.1e-15, + rtol=2e-15, ), ), pytest.param( From 1a18471619270fcf9098604d5838b23f10bd3996 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Wed, 12 Jun 2024 03:40:10 -0700 Subject: [PATCH 395/500] DOC/MAINT: single to double backticks to remove improper linking (#20941) Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Co-authored-by: Matt Haberland --- scipy/fftpack/_realtransforms.py | 8 ++++---- scipy/sparse/_construct.py | 10 +++++----- scipy/sparse/_data.py | 2 +- scipy/sparse/_dok.py | 2 +- scipy/sparse/linalg/_isolve/iterative.py | 2 +- scipy/sparse/linalg/_isolve/lsmr.py | 4 ++-- scipy/sparse/linalg/_isolve/lsqr.py | 4 ++-- scipy/sparse/linalg/_isolve/tfqmr.py | 2 +- scipy/sparse/linalg/_svdp.py | 6 +++--- scipy/spatial/_ckdtree.pyx | 4 ++-- scipy/spatial/_kdtree.py | 2 +- scipy/spatial/_spherical_voronoi.py | 8 ++++---- scipy/spatial/distance.py | 4 ++-- scipy/special/_add_newdocs.py | 12 ++++++------ scipy/special/_basic.py | 6 +++--- scipy/stats/_mstats_basic.py | 4 ++-- scipy/stats/_multivariate.py | 16 ++++++++-------- scipy/stats/_qmc.py | 6 +++--- .../test_generation/reference_distributions.py | 2 +- 19 files changed, 52 insertions(+), 52 deletions(-) diff --git a/scipy/fftpack/_realtransforms.py b/scipy/fftpack/_realtransforms.py index f56f68fce4ea..ad71d517b0ac 100644 --- a/scipy/fftpack/_realtransforms.py +++ b/scipy/fftpack/_realtransforms.py @@ -333,7 +333,7 @@ def dct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False): \cos\left(\frac{\pi(2k+1)n}{2N}\right) The (unnormalized) DCT-III is the inverse of the (unnormalized) DCT-II, up - to a factor `2N`. The orthonormalized DCT-III is exactly the inverse of + to a factor ``2N``. The orthonormalized DCT-III is exactly the inverse of the orthonormalized DCT-II. **Type IV** @@ -489,7 +489,7 @@ def dst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False): y_k = 2 \sum_{n=0}^{N-1} x_n \sin\left(\frac{\pi(k+1)(n+1)}{N+1}\right) Note that the DST-I is only supported for input size > 1. - The (unnormalized) DST-I is its own inverse, up to a factor `2(N+1)`. + The (unnormalized) DST-I is its own inverse, up to a factor ``2(N+1)``. The orthonormalized DST-I is exactly its own inverse. **Type II** @@ -522,7 +522,7 @@ def dst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False): \frac{\pi(2k+1)(n+1)}{2N}\right) The (unnormalized) DST-III is the inverse of the (unnormalized) DST-II, up - to a factor `2N`. The orthonormalized DST-III is exactly the inverse of the + to a factor ``2N``. The orthonormalized DST-III is exactly the inverse of the orthonormalized DST-II. .. versionadded:: 0.11.0 @@ -537,7 +537,7 @@ def dst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False): y_k = 2 \sum_{n=0}^{N-1} x_n \sin\left(\frac{\pi(2k+1)(2n+1)}{4N}\right) - The (unnormalized) DST-IV is its own inverse, up to a factor `2N`. The + The (unnormalized) DST-IV is its own inverse, up to a factor ``2N``. The orthonormalized DST-IV is exactly its own inverse. .. versionadded:: 1.2.0 diff --git a/scipy/sparse/_construct.py b/scipy/sparse/_construct.py index 3ceec66bda14..1cdde1b26a7b 100644 --- a/scipy/sparse/_construct.py +++ b/scipy/sparse/_construct.py @@ -709,8 +709,8 @@ def hstack(blocks, format=None, dtype=None): 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])`. + arrays, use ``block(hstack(blocks))`` or convert one block + e.g. ``blocks[0] = csr_array(blocks[0])``. See Also -------- @@ -756,7 +756,7 @@ def vstack(blocks, format=None, dtype=None): 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 + arrays, use ``block(vstack(blocks))`` or convert one block e.g. `blocks[0] = csr_array(blocks[0])`. See Also @@ -815,7 +815,7 @@ def bmat(blocks, format=None, dtype=None): Otherwise return a sparse matrix. If you want a sparse array built from blocks that are not sparse - arrays, use `block_array()`. + arrays, use ``block_array()``. See Also -------- @@ -1116,7 +1116,7 @@ def random_array(shape, *, density=0.01, format='coo', dtype=None, 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 `random_state` rng is used e.g. `rng.uniform(size=size)`. + For these, the `random_state` rng is used e.g. ``rng.uniform(size=size)``. Returns ------- diff --git a/scipy/sparse/_data.py b/scipy/sparse/_data.py index 9c4a2957e237..09352521feef 100644 --- a/scipy/sparse/_data.py +++ b/scipy/sparse/_data.py @@ -112,7 +112,7 @@ def power(self, n, dtype=None): ------ NotImplementedError : if n is a zero scalar If zero power is desired, special case it to use - `np.ones(A.shape, dtype=A.dtype)` + ``np.ones(A.shape, dtype=A.dtype)`` """ if not isscalarlike(n): raise NotImplementedError("input is not scalar") diff --git a/scipy/sparse/_dok.py b/scipy/sparse/_dok.py index 0d0778a5cde6..da4554209ce4 100644 --- a/scipy/sparse/_dok.py +++ b/scipy/sparse/_dok.py @@ -422,7 +422,7 @@ def conjtransp(self): .. deprecated:: 1.14.0 `conjtransp` is deprecated and will be removed in v1.16.0. - Use `.T.conj()` instead. + Use ``.T.conj()`` instead. """ msg = ("`conjtransp` is deprecated and will be removed in v1.16.0. " "Use `.T.conj()` instead.") diff --git a/scipy/sparse/linalg/_isolve/iterative.py b/scipy/sparse/linalg/_isolve/iterative.py index 0176654cfc80..7c79a0301ecb 100644 --- a/scipy/sparse/linalg/_isolve/iterative.py +++ b/scipy/sparse/linalg/_isolve/iterative.py @@ -573,7 +573,7 @@ def gmres(A, b, x0=None, *, rtol=1e-5, atol=0., restart=None, maxiter=None, M=No 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`. + 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 diff --git a/scipy/sparse/linalg/_isolve/lsmr.py b/scipy/sparse/linalg/_isolve/lsmr.py index e9dd114a78b5..3eeb05af62a6 100644 --- a/scipy/sparse/linalg/_isolve/lsmr.py +++ b/scipy/sparse/linalg/_isolve/lsmr.py @@ -155,7 +155,7 @@ def lsmr(A, b, damp=0.0, atol=1e-6, btol=1e-6, conlim=1e8, >>> x array([0., 0.]) - The stopping code `istop=0` returned indicates that a vector of zeros was + 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: @@ -170,7 +170,7 @@ def lsmr(A, b, damp=0.0, atol=1e-6, btol=1e-6, conlim=1e8, >>> normr 4.440892098500627e-16 - As indicated by `istop=1`, `lsmr` found a solution obeying the tolerance + 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 diff --git a/scipy/sparse/linalg/_isolve/lsqr.py b/scipy/sparse/linalg/_isolve/lsqr.py index ba684d147e42..77c6aa33d26a 100644 --- a/scipy/sparse/linalg/_isolve/lsqr.py +++ b/scipy/sparse/linalg/_isolve/lsqr.py @@ -282,7 +282,7 @@ def lsqr(A, b, damp=0.0, atol=1e-6, btol=1e-6, conlim=1e8, >>> x array([ 0., 0.]) - The stopping code `istop=0` returned indicates that a vector of zeros was + 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: @@ -297,7 +297,7 @@ def lsqr(A, b, damp=0.0, atol=1e-6, btol=1e-6, conlim=1e8, >>> r1norm 4.440892098500627e-16 - As indicated by `istop=1`, `lsqr` found a solution obeying the tolerance + 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 diff --git a/scipy/sparse/linalg/_isolve/tfqmr.py b/scipy/sparse/linalg/_isolve/tfqmr.py index 2966dc7bcc81..a13acdcb3eee 100644 --- a/scipy/sparse/linalg/_isolve/tfqmr.py +++ b/scipy/sparse/linalg/_isolve/tfqmr.py @@ -38,7 +38,7 @@ def tfqmr(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M=None, 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. + 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. diff --git a/scipy/sparse/linalg/_svdp.py b/scipy/sparse/linalg/_svdp.py index 9b85d6c7eefe..7d7d35180237 100644 --- a/scipy/sparse/linalg/_svdp.py +++ b/scipy/sparse/linalg/_svdp.py @@ -127,7 +127,7 @@ def _svdp(A, k, which='LM', irl_mode=True, kmax=None, 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`. + 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. @@ -136,14 +136,14 @@ def _svdp(A, k, which='LM', irl_mode=True, kmax=None, 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``. + 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`. + Maximum number of restarts in IRL mode. Default is ``1000``. Accessed only if ``irl_mode=True``. random_state : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional diff --git a/scipy/spatial/_ckdtree.pyx b/scipy/spatial/_ckdtree.pyx index 548cc5461368..e0e9244593e6 100644 --- a/scipy/spatial/_ckdtree.pyx +++ b/scipy/spatial/_ckdtree.pyx @@ -480,7 +480,7 @@ cdef class cKDTree: 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`. + with ``copy_data=True``. leafsize : positive int The number of points at which the algorithm switches over to brute-force. @@ -1284,7 +1284,7 @@ cdef class cKDTree: 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 + ``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 diff --git a/scipy/spatial/_kdtree.py b/scipy/spatial/_kdtree.py index f65412e9e4ad..b76c7e58168f 100644 --- a/scipy/spatial/_kdtree.py +++ b/scipy/spatial/_kdtree.py @@ -267,7 +267,7 @@ class KDTree(cKDTree): 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`. + with ``copy_data=True``. leafsize : positive int The number of points at which the algorithm switches over to brute-force. diff --git a/scipy/spatial/_spherical_voronoi.py b/scipy/spatial/_spherical_voronoi.py index 6b9ba8242923..e1c4fab463bd 100644 --- a/scipy/spatial/_spherical_voronoi.py +++ b/scipy/spatial/_spherical_voronoi.py @@ -70,9 +70,9 @@ class SphericalVoronoi: ------- 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`. + 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`. + areas is ``4 * pi * radius**2``. Raises ------ @@ -321,10 +321,10 @@ 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`. + is ``2 * pi * radius``. For 3D point sets, the regions are spherical polygons. The sum of the - areas is `4 * pi * radius**2`. + areas is ``4 * pi * radius**2``. .. versionadded:: 1.5.0 diff --git a/scipy/spatial/distance.py b/scipy/spatial/distance.py index d57a0472953b..6eb0fd30a099 100644 --- a/scipy/spatial/distance.py +++ b/scipy/spatial/distance.py @@ -785,12 +785,12 @@ def jaccard(u, v, w=None): Notes ----- - When both `u` and `v` lead to a `0/0` division i.e. there is no overlap + When both `u` and `v` lead to a ``0/0`` division i.e. there is no overlap between the items in the vectors the returned distance is 0. See the Wikipedia page on the Jaccard index [1]_, and this paper [2]_. .. versionchanged:: 1.2.0 - Previously, when `u` and `v` lead to a `0/0` division, the function + Previously, when `u` and `v` lead to a ``0/0`` division, the function would return NaN. This was changed to return 0 instead. References diff --git a/scipy/special/_add_newdocs.py b/scipy/special/_add_newdocs.py index 17b4457e9819..3e18dbee2edb 100644 --- a/scipy/special/_add_newdocs.py +++ b/scipy/special/_add_newdocs.py @@ -5457,7 +5457,7 @@ def add_newdoc(name, doc): Returns ------- a : scalar or ndarray - Values of the `a` parameter such that `p = gdtr(a, b, x)`. `1/a` + Values of the `a` parameter such that ``p = gdtr(a, b, x)`. ``1/a`` is the "scale" parameter of the gamma distribution. See Also @@ -5512,7 +5512,7 @@ def add_newdoc(name, doc): Parameters ---------- a : array_like - `a` parameter values of `gdtr(a, b, x)`. `1/a` is the "scale" + `a` parameter values of ``gdtr(a, b, x)`. ``1/a`` is the "scale" parameter of the gamma distribution. p : array_like Probability values. @@ -5582,10 +5582,10 @@ def add_newdoc(name, doc): Parameters ---------- a : array_like - `a` parameter values of `gdtr(a, b, x)`. `1/a` is the "scale" + `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 + `b` parameter values of ``gdtr(a, b, x)``. `b` is the "shape" parameter of the gamma distribution. p : array_like Probability values. @@ -5602,8 +5602,8 @@ def add_newdoc(name, doc): 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)`. + gdtria : Inverse with respect to `a` of ``gdtr(a, b, x)``. + gdtrib : Inverse with respect to `b` of ``gdtr(a, b, x)``. Notes ----- diff --git a/scipy/special/_basic.py b/scipy/special/_basic.py index c041a1084ff0..e89c0c4edd17 100644 --- a/scipy/special/_basic.py +++ b/scipy/special/_basic.py @@ -3248,9 +3248,9 @@ def stirling2(N, K, *, exact=False): 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 + ``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 diff --git a/scipy/stats/_mstats_basic.py b/scipy/stats/_mstats_basic.py index f2ab57298830..f8a97049aec1 100644 --- a/scipy/stats/_mstats_basic.py +++ b/scipy/stats/_mstats_basic.py @@ -2607,8 +2607,8 @@ def winsorize(a, limits=None, inclusive=(True, True), inplace=False, >>> 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. + 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], diff --git a/scipy/stats/_multivariate.py b/scipy/stats/_multivariate.py index ddb59ae8c68a..5d8c3cb0298c 100644 --- a/scipy/stats/_multivariate.py +++ b/scipy/stats/_multivariate.py @@ -653,7 +653,7 @@ def logcdf(self, x, mean=None, cov=1, allow_singular=False, maxpts=None, %(_mvn_doc_default_callparams)s maxpts : integer, optional The maximum number of points to use for integration - (default `1000000*dim`) + (default ``1000000*dim``) abseps : float, optional Absolute error tolerance (default 1e-5) releps : float, optional @@ -698,7 +698,7 @@ def cdf(self, x, mean=None, cov=1, allow_singular=False, maxpts=None, %(_mvn_doc_default_callparams)s maxpts : integer, optional The maximum number of points to use for integration - (default `1000000*dim`) + (default ``1000000*dim``) abseps : float, optional Absolute error tolerance (default 1e-5) releps : float, optional @@ -877,7 +877,7 @@ def __init__(self, mean=None, cov=1, allow_singular=False, seed=None, then that instance is used. maxpts : integer, optional The maximum number of points to use for integration of the - cumulative distribution function (default `1000000*dim`) + cumulative distribution function (default ``1000000*dim``) abseps : float, optional Absolute error tolerance for the cumulative distribution function (default 1e-5) @@ -968,15 +968,15 @@ def entropy(self): mean : array_like, optional Mean of the distribution (default: `None`) rowcov : array_like, optional - Among-row covariance matrix of the distribution (default: `1`) + Among-row covariance matrix of the distribution (default: ``1``) colcov : array_like, optional - Among-column covariance matrix of the distribution (default: `1`) + 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. +`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 @@ -1329,9 +1329,9 @@ def entropy(self, rowcov=1, colcov=1): Parameters ---------- rowcov : array_like, optional - Among-row covariance matrix of the distribution (default: `1`) + Among-row covariance matrix of the distribution (default: ``1``) colcov : array_like, optional - Among-column covariance matrix of the distribution (default: `1`) + Among-column covariance matrix of the distribution (default: ``1``) Returns ------- diff --git a/scipy/stats/_qmc.py b/scipy/stats/_qmc.py index 1aea6a694c8f..735cb6c4980e 100644 --- a/scipy/stats/_qmc.py +++ b/scipy/stats/_qmc.py @@ -704,10 +704,10 @@ def _van_der_corput_permutations( 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` + 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 + 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(random_state) diff --git a/scipy/stats/tests/test_generation/reference_distributions.py b/scipy/stats/tests/test_generation/reference_distributions.py index d5a78f30ae16..037b16a2d0c8 100644 --- a/scipy/stats/tests/test_generation/reference_distributions.py +++ b/scipy/stats/tests/test_generation/reference_distributions.py @@ -35,7 +35,7 @@ class ReferenceDistribution: - 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 + 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, From cf8e87d00edc1d0741ac6db862881de85d276918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 12 Jun 2024 08:08:53 -0500 Subject: [PATCH 396/500] CI: Use Cython nightly wheel on free-threaded CI (#20942) --- .github/workflows/linux.yml | 8 ++------ tools/wheels/cibw_before_build_linux.sh | 3 +-- tools/wheels/cibw_before_test.sh | 4 +--- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 27676a8253be..df8d9b42310c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -471,13 +471,9 @@ jobs: - name: Install pre-release pip run: | pip install -U --pre pip - # TODO: remove cython nightly install when cython does a release - - name: Install nightly Cython + - name: Install nightly NumPy and Cython run: | - pip install git+https://github.com/cython/cython - - name: Install nightly NumPy - run: | - pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple cython numpy - name: Install Python dependencies run: | pip install git+https://github.com/serge-sans-paille/pythran diff --git a/tools/wheels/cibw_before_build_linux.sh b/tools/wheels/cibw_before_build_linux.sh index 66e8c2265142..3a4c50da2936 100755 --- a/tools/wheels/cibw_before_build_linux.sh +++ b/tools/wheels/cibw_before_build_linux.sh @@ -23,8 +23,7 @@ cat $PROJECT_DIR/tools/wheels/LICENSE_linux.txt >> $PROJECT_DIR/LICENSE.txt FREE_THREADED_BUILD="$(python -c"import sysconfig; print(bool(sysconfig.get_config_var('Py_GIL_DISABLED')))")" if [[ $FREE_THREADED_BUILD == "True" ]]; then python -m pip install -U --pre pip - python -m pip install git+https://github.com/cython/cython - python -m pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + python -m pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy cython # python -m pip install git+https://github.com/serge-sans-paille/pythran python -m pip install ninja meson-python pybind11 pythran fi diff --git a/tools/wheels/cibw_before_test.sh b/tools/wheels/cibw_before_test.sh index d18f920bd2c4..3b2182b792e9 100644 --- a/tools/wheels/cibw_before_test.sh +++ b/tools/wheels/cibw_before_test.sh @@ -3,8 +3,6 @@ set -ex FREE_THREADED_BUILD="$(python -c"import sysconfig; print(bool(sysconfig.get_config_var('Py_GIL_DISABLED')))")" if [[ $FREE_THREADED_BUILD == "True" ]]; then # TODO: delete when numpy is buildable under free-threaded python - # with a released version of cython python -m pip install -U --pre pip - python -m pip install git+https://github.com/cython/cython - python -m pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + python -m pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy cython fi From e21e454fe6939122fac83a105c93a2f1607059b0 Mon Sep 17 00:00:00 2001 From: "H. Vetinari" Date: Thu, 13 Jun 2024 12:42:14 +1100 Subject: [PATCH 397/500] loosen tolerance in test_x0_working to pass with alternate BLAS backends --- scipy/sparse/linalg/_isolve/tests/test_iterative.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/sparse/linalg/_isolve/tests/test_iterative.py b/scipy/sparse/linalg/_isolve/tests/test_iterative.py index 4ebfa62c4e74..f3235260b39f 100644 --- a/scipy/sparse/linalg/_isolve/tests/test_iterative.py +++ b/scipy/sparse/linalg/_isolve/tests/test_iterative.py @@ -506,7 +506,7 @@ def test_x0_working(solver): x, info = solver(A, b, x0=x0, **kw) assert info == 0 - assert norm(A @ x - b) <= 2e-6*norm(b) + assert norm(A @ x - b) <= 3e-6*norm(b) def test_x0_equals_Mb(case): From 3bde286c20139998f662e6498f69cdffc29d08f9 Mon Sep 17 00:00:00 2001 From: "H. Vetinari" Date: Thu, 13 Jun 2024 12:50:22 +1100 Subject: [PATCH 398/500] loosen tolerance in test_krandinit slightly to pass with MKL --- scipy/cluster/tests/test_vq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/cluster/tests/test_vq.py b/scipy/cluster/tests/test_vq.py index 2f015f04d023..8565521667a7 100644 --- a/scipy/cluster/tests/test_vq.py +++ b/scipy/cluster/tests/test_vq.py @@ -355,7 +355,7 @@ def test_krandinit(self, xp): init = _krandinit(data, k, rng, xp) orig_cov = cov(data.T) init_cov = cov(init.T) - xp_assert_close(orig_cov, init_cov, atol=1e-2) + xp_assert_close(orig_cov, init_cov, atol=1.1e-2) def test_kmeans2_empty(self, xp): # Regression test for gh-1032. From bdf4fc282abf033a30d71aef92f080d1dbe082e0 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 12 Jun 2024 23:25:54 -0400 Subject: [PATCH 399/500] MAINT: stats.hmean/pmean: simplify prior to array API conversion --- scipy/stats/_stats_py.py | 56 ++++++++++++--------------------- scipy/stats/tests/test_stats.py | 7 +++-- 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 4409cc632cea..a5f01adc1d9d 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -288,25 +288,18 @@ def hmean(a, axis=0, dtype=None, *, weights=None): 1.9029126213592233 """ - if not isinstance(a, np.ndarray): - a = np.array(a, dtype=dtype) - elif dtype: - # Must change the default dtype allowing array type - if isinstance(a, np.ma.MaskedArray): - a = np.ma.asarray(a, dtype=dtype) - else: - a = np.asarray(a, dtype=dtype) + a = np.asarray(a, dtype=dtype) - if np.all(a >= 0): - # Harmonic mean only defined if greater than or equal to zero. - if weights is not None: - weights = np.asanyarray(weights, dtype=dtype) + if weights is not None: + weights = np.asarray(weights, dtype=dtype) - with np.errstate(divide='ignore'): - return 1.0 / np.average(1.0 / a, axis=axis, weights=weights) - else: - raise ValueError("Harmonic mean only defined if all elements greater " - "than or equal to zero") + if not np.all(a >= 0): + message = ("The harmonic mean is only defined if all elements are greater " + "than or equal to zero; 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) @_axis_nan_policy_factory( @@ -414,27 +407,18 @@ def pmean(a, p, *, axis=0, dtype=None, weights=None): if p == 0: return gmean(a, axis=axis, dtype=dtype, weights=weights) - if not isinstance(a, np.ndarray): - a = np.array(a, dtype=dtype) - elif dtype: - # Must change the default dtype allowing array type - if isinstance(a, np.ma.MaskedArray): - a = np.ma.asarray(a, dtype=dtype) - else: - a = np.asarray(a, dtype=dtype) + a = np.asarray(a, dtype=dtype) - if np.all(a >= 0): - # Power mean only defined if greater than or equal to zero - if weights is not None: - weights = np.asanyarray(weights, dtype=dtype) + if not np.all(a >= 0): + message = ("The power mean is only defined if all elements are greater " + "than or equal to zero; otherwise, the result is NaN.") + warnings.warn(message, RuntimeWarning, stacklevel=2) - with np.errstate(divide='ignore'): - return np.float_power( - np.average(np.float_power(a, p), axis=axis, weights=weights), - 1/p) - else: - raise ValueError("Power mean only defined if all elements greater " - "than or equal to zero") + if weights is not None: + weights = np.asanyarray(weights, dtype=dtype) + + with np.errstate(divide='ignore'): + return _xp_mean(a**float(p), axis=axis, weights=weights)**(1/p) ModeResult = namedtuple('ModeResult', ('mode', 'count')) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 02e68c76ee6a..1784db1582d5 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6804,7 +6804,9 @@ def test_1d_array_with_zero(self): def test_1d_array_with_negative_value(self): a = np.array([1, 0, -1]) - assert_raises(ValueError, stats.hmean, a) + message = "The harmonic mean is only defined..." + with pytest.warns(RuntimeWarning, match=message): + stats.hmean(a) # Note the next tests use axis=None as default, not axis=0 def test_2d_list(self): @@ -7020,7 +7022,8 @@ def test_1d_array_with_zero(self): def test_1d_array_with_negative_value(self): a, p = np.array([1, 0, -1]), 1.23 - with pytest.raises(ValueError, match='Power mean only defined if all'): + message = "The power mean is only defined..." + with pytest.warns(RuntimeWarning, match=message): stats.pmean(a, p) @pytest.mark.parametrize( From 36651824606c615662348a417b6d624c315a1870 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 13 Jun 2024 00:45:43 -0400 Subject: [PATCH 400/500] TST: stats.hmean: remove unnecessary test --- scipy/stats/_stats_py.py | 8 ++++---- scipy/stats/tests/test_axis_nan_policy.py | 14 -------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index a5f01adc1d9d..a088b28213cc 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -409,15 +409,15 @@ def pmean(a, p, *, axis=0, dtype=None, weights=None): a = np.asarray(a, dtype=dtype) + if weights is not None: + weights = np.asanyarray(weights, dtype=dtype) + if not np.all(a >= 0): message = ("The power mean is only defined if all elements are greater " "than or equal to zero; otherwise, the result is NaN.") warnings.warn(message, RuntimeWarning, stacklevel=2) - if weights is not None: - weights = np.asanyarray(weights, dtype=dtype) - - with np.errstate(divide='ignore'): + with np.errstate(divide='ignore', invalid='ignore'): return _xp_mean(a**float(p), axis=axis, weights=weights)**(1/p) diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index 786cde6362e1..818596140ede 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -1239,10 +1239,6 @@ def func(*args, **kwargs): a_masked3 = np.ma.masked_array(a, mask=(mask_a1 | mask_a2)) b_masked3 = np.ma.masked_array(b, mask=(mask_b1 | mask_b2)) - mask_all = (mask_a1 | mask_a2 | mask_b1 | mask_b2) - a_masked4 = np.ma.masked_array(a, mask=mask_all) - b_masked4 = np.ma.masked_array(b, mask=mask_all) - with np.testing.suppress_warnings() as sup: message = 'invalid value encountered' sup.filter(RuntimeWarning, message) @@ -1251,21 +1247,11 @@ def func(*args, **kwargs): 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) - # Would test with a_masked3/b_masked3, but there is a bug in np.average - # that causes a bug in _no_deco mean with masked weights. Would use - # np.ma.average, but that causes other problems. See numpy/numpy#7330. - if weighted_fun_name in {"hmean"}: - weighted_fun_ma = getattr(stats.mstats, weighted_fun_name) - res5 = weighted_fun_ma(a_masked4, weights=b_masked4, - axis=axis, _no_deco=True) 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) - if weighted_fun_name in {"hmean"}: - # _no_deco mean returns masked array, last element was masked - np.testing.assert_allclose(res5.compressed(), res[~np.isnan(res)]) def test_raise_invalid_args_g17713(): From 0d5fc257209750e4568314264328db8b9386dd8e Mon Sep 17 00:00:00 2001 From: Albert Steppi Date: Thu, 13 Jun 2024 03:33:51 -0400 Subject: [PATCH 401/500] BUG/BLD: special: Ensure symbols in `sf_error_state` shared library are exported/imported on MSVC (#20937) * Add compile flag fpp for ifx, not just ifort * Avoid designated initializers - This is a C++20 feature which just happens to work in every supported compiler except MSVC at the moment * Make sure symbols in shared library are exported/imported on MSVC Co-authored-by: h-vetinari Co-authored-by: Ralf Gommers --- scipy/_build_utils/src/scipy_dll.h | 18 +++++++++++ scipy/meson.build | 2 +- scipy/special/_gufuncs.cpp | 12 ++++++-- scipy/special/_special_ufuncs.cpp | 12 ++++++-- scipy/special/meson.build | 48 ++++++++++++++---------------- scipy/special/sf_error_state.c | 4 +-- scipy/special/sf_error_state.h | 6 ++-- 7 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 scipy/_build_utils/src/scipy_dll.h diff --git a/scipy/_build_utils/src/scipy_dll.h b/scipy/_build_utils/src/scipy_dll.h new file mode 100644 index 000000000000..5abeee874d38 --- /dev/null +++ b/scipy/_build_utils/src/scipy_dll.h @@ -0,0 +1,18 @@ +#pragma once + +// SCIPY_DLL +// inspired by https://github.com/abseil/abseil-cpp/blob/20240116.2/absl/base/config.h#L736-L753 +// +// When building sf_error_state as a DLL, this macro expands to `__declspec(dllexport)` +// so we can annotate symbols appropriately as being exported. When used in +// headers consuming a DLL, this macro expands to `__declspec(dllimport)` so +// that consumers know the symbol is defined inside the DLL. In all other cases, +// the macro expands to nothing. +// Note: SCIPY_DLL_{EX,IM}PORTS are set in scipy/special/meson.build +#if defined(SCIPY_DLL_EXPORTS) + #define SCIPY_DLL __declspec(dllexport) +#elif defined(SCIPY_DLL_IMPORTS) + #define SCIPY_DLL __declspec(dllimport) +#else + #define SCIPY_DLL +#endif diff --git a/scipy/meson.build b/scipy/meson.build index 79a16cd5b8ea..a0857848a275 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -422,7 +422,7 @@ fortran_ignore_warnings = ff.get_supported_arguments( # Intel Fortran (ifort) does not run the preprocessor by default, if Fortran # code uses preprocessor statements, add this compile flag to it. _fflag_fpp = [] -if ff.get_id() == 'intel-cl' +if ff.get_id() in ['intel-cl', 'intel-llvm-cl'] if is_windows _fflag_fpp = ff.get_supported_arguments('/fpp') else diff --git a/scipy/special/_gufuncs.cpp b/scipy/special/_gufuncs.cpp index d35edf3ab096..216adf5bb1eb 100644 --- a/scipy/special/_gufuncs.cpp +++ b/scipy/special/_gufuncs.cpp @@ -53,9 +53,15 @@ extern const char *sph_harm_all_doc; extern "C" int wrap_PyUFunc_getfperr() { return PyUFunc_getfperr(); } static PyModuleDef _gufuncs_def = { - .m_base = PyModuleDef_HEAD_INIT, - .m_name = "_gufuncs", - .m_size = -1, + PyModuleDef_HEAD_INIT, + "_gufuncs", + NULL, + -1, + NULL, + NULL, + NULL, + NULL, + NULL }; PyMODINIT_FUNC PyInit__gufuncs() { diff --git a/scipy/special/_special_ufuncs.cpp b/scipy/special/_special_ufuncs.cpp index 0ce889b9c43d..7e9aa01aca93 100644 --- a/scipy/special/_special_ufuncs.cpp +++ b/scipy/special/_special_ufuncs.cpp @@ -206,9 +206,15 @@ extern const char *yve_doc; extern "C" int wrap_PyUFunc_getfperr() { return PyUFunc_getfperr(); } static PyModuleDef _special_ufuncs_def = { - .m_base = PyModuleDef_HEAD_INIT, - .m_name = "_special_ufuncs", - .m_size = -1, + PyModuleDef_HEAD_INIT, + "_special_ufuncs", + NULL, + -1, + NULL, + NULL, + NULL, + NULL, + NULL }; PyMODINIT_FUNC PyInit__special_ufuncs() { diff --git a/scipy/special/meson.build b/scipy/special/meson.build index a52ec928f616..82b813ea851b 100644 --- a/scipy/special/meson.build +++ b/scipy/special/meson.build @@ -25,14 +25,27 @@ _ufuncs_pxi_pxd_sources = [ fs.copyfile('_ufuncs_extra_code_common.pxi'), ] +if meson.get_compiler('cpp').get_id() in ['msvc', 'clang-cl', 'intel-cl'] + scipy_export_dll_args = ['-DSCIPY_DLL_EXPORTS'] + scipy_import_dll_args = ['-DSCIPY_DLL_IMPORTS'] +else + scipy_export_dll_args = [] + scipy_import_dll_args = [] +endif + sf_error_state_lib = shared_library('sf_error_state', ['sf_error_state.c'], include_directories: ['../_lib', '../_build_utils/src'], - cpp_args: ['-DSP_SPECFUN_ERROR'], + c_args: scipy_export_dll_args, install: true, install_dir: py3.get_install_dir() / 'scipy/special', ) +sf_error_state_dep = declare_dependency( + compile_args: ['-DSP_SPECFUN_ERROR'] + scipy_import_dll_args, + link_with: sf_error_state_lib, +) + ufuncs_sources = [ '_cosine.c', 'special_wrappers.cpp', @@ -58,9 +71,7 @@ cdflib_lib = static_library('cdflib', py3.extension_module('_special_ufuncs', ['_special_ufuncs.cpp', '_special_ufuncs_docs.cpp', 'sf_error.cc'], include_directories: ['../_lib', '../_build_utils/src'], - cpp_args: ['-DSP_SPECFUN_ERROR'], - dependencies: [np_dep], - link_with: [sf_error_state_lib], + dependencies: [np_dep, sf_error_state_dep], link_args: version_link_args, install: true, subdir: 'scipy/special', @@ -70,9 +81,7 @@ py3.extension_module('_special_ufuncs', py3.extension_module('_gufuncs', ['_gufuncs.cpp', '_gufuncs_docs.cpp', 'sf_error.cc'], include_directories: ['../_lib', '../_build_utils/src'], - cpp_args: ['-DSP_SPECFUN_ERROR'], - dependencies: [np_dep], - link_with: [sf_error_state_lib], + dependencies: [np_dep, sf_error_state_dep], link_args: version_link_args, install: true, subdir: 'scipy/special', @@ -126,19 +135,16 @@ py3.extension_module('_ufuncs', ufuncs_sources, uf_cython_gen.process(cython_special[0]), # _ufuncs.pyx ], - c_args: [cython_c_args, Wno_maybe_uninitialized], - cpp_args: ['-DSP_SPECFUN_ERROR'], + c_args: [cython_c_args, Wno_maybe_uninitialized] + scipy_import_dll_args, include_directories: ['../_lib', '../_build_utils/src'], dependencies: [ lapack_dep, npymath_lib, np_dep, + sf_error_state_dep, ], link_args: version_link_args, - link_with: [ - sf_error_state_lib, - cdflib_lib - ], + link_with: cdflib_lib, install: true, subdir: 'scipy/special', install_rpath: '$ORIGIN', @@ -165,7 +171,6 @@ ufuncs_cxx_cpp_args = [ cython_cpp_args, '-DBOOST_MATH_STANDALONE=1', '-DCYTHON_EXTERN_C=extern "C"', - '-DSP_SPECFUN_ERROR', # For error handling in special functions. ] py3.extension_module('_ufuncs_cxx', @@ -176,8 +181,7 @@ py3.extension_module('_ufuncs_cxx', include_directories: ['../_lib/boost_math/include', '../_lib', '../_build_utils/src'], link_args: version_link_args, - link_with: [sf_error_state_lib], - dependencies: [np_dep, ellint_dep], + dependencies: [np_dep, ellint_dep, sf_error_state_dep], install: true, subdir: 'scipy/special', install_rpath: '$ORIGIN', @@ -186,11 +190,9 @@ py3.extension_module('_ufuncs_cxx', py3.extension_module('_ellip_harm_2', [uf_cython_gen.process('_ellip_harm_2.pyx'), 'sf_error.cc'], c_args: cython_c_args, - cpp_args: ['-DSP_SPECFUN_ERROR'], include_directories: ['../_lib', '../_build_utils/src'], - link_with: [sf_error_state_lib], link_args: version_link_args, - dependencies: [lapack_dep, np_dep], + dependencies: [lapack_dep, np_dep, sf_error_state_dep], install: true, subdir: 'scipy/special', install_rpath: '$ORIGIN', @@ -205,14 +207,10 @@ py3.extension_module('cython_special', 'dd_real_wrappers.cpp' ], c_args: [cython_c_args, Wno_maybe_uninitialized], - cpp_args: ['-DSP_SPECFUN_ERROR'], include_directories: ['../_lib', '../_build_utils/src'], link_args: version_link_args, - dependencies: [np_dep, npymath_lib], - link_with: [ - sf_error_state_lib, - cdflib_lib - ], + dependencies: [np_dep, npymath_lib, sf_error_state_dep], + link_with: cdflib_lib, install: true, subdir: 'scipy/special', install_rpath: '$ORIGIN', diff --git a/scipy/special/sf_error_state.c b/scipy/special/sf_error_state.c index 1a664170d23c..d2c465a21368 100644 --- a/scipy/special/sf_error_state.c +++ b/scipy/special/sf_error_state.c @@ -19,13 +19,13 @@ static volatile sf_action_t sf_error_actions[] = { }; -void scipy_sf_error_set_action(sf_error_t code, sf_action_t action) +SCIPY_DLL void scipy_sf_error_set_action(sf_error_t code, sf_action_t action) { sf_error_actions[(int)code] = action; } -sf_action_t scipy_sf_error_get_action(sf_error_t code) +SCIPY_DLL sf_action_t scipy_sf_error_get_action(sf_error_t code) { return sf_error_actions[(int)code]; } diff --git a/scipy/special/sf_error_state.h b/scipy/special/sf_error_state.h index a4cb563bb2d1..dbedddcd2048 100644 --- a/scipy/special/sf_error_state.h +++ b/scipy/special/sf_error_state.h @@ -1,6 +1,6 @@ #pragma once - +#include "scipy_dll.h" #include "special/error.h" @@ -14,9 +14,9 @@ extern "C" { SF_ERROR_RAISE /* Raise on errors */ } sf_action_t; - void scipy_sf_error_set_action(sf_error_t code, sf_action_t action); + SCIPY_DLL void scipy_sf_error_set_action(sf_error_t code, sf_action_t action); - sf_action_t scipy_sf_error_get_action(sf_error_t code); + SCIPY_DLL sf_action_t scipy_sf_error_get_action(sf_error_t code); #ifdef __cplusplus } From bc11927c03f9c8d52455def8566a31b180b6e3d3 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Thu, 13 Jun 2024 11:11:40 +0200 Subject: [PATCH 402/500] DOC: Single to double backticks for non-targets A few places what was meant was likely verbatim 2 places where emphasis was likely what was ment. --- scipy/interpolate/_fitpack_py.py | 4 ++-- scipy/linalg/_basic.py | 6 +++--- scipy/linalg/_generate_pyx.py | 2 +- scipy/linalg/_interpolative_backend.py | 20 ++++++++++---------- scipy/linalg/_special_matrices.py | 4 ++-- scipy/linalg/interpolative.py | 6 +++--- scipy/ndimage/_ni_docstrings.py | 4 ++-- scipy/odr/_odrpack.py | 6 +++--- scipy/optimize/_direct_py.py | 4 ++-- scipy/optimize/_optimize.py | 2 +- scipy/optimize/_shgo.py | 4 ++-- scipy/signal/_signaltools.py | 2 +- scipy/special/_add_newdocs.py | 16 ++++++++-------- scipy/special/_basic.py | 2 +- scipy/stats/_continuous_distns.py | 10 +++++----- scipy/stats/_discrete_distns.py | 2 +- scipy/stats/_mgc.py | 2 +- scipy/stats/_morestats.py | 8 ++++---- scipy/stats/_stats_py.py | 8 ++++---- 19 files changed, 56 insertions(+), 56 deletions(-) diff --git a/scipy/interpolate/_fitpack_py.py b/scipy/interpolate/_fitpack_py.py index c977b342b36a..88bd91bc6791 100644 --- a/scipy/interpolate/_fitpack_py.py +++ b/scipy/interpolate/_fitpack_py.py @@ -140,7 +140,7 @@ def splprep(x, w=None, u=None, ub=None, ue=None, k=3, task=0, s=None, t=None, >>> tck, u = splprep([x, y], s=0) >>> new_points = splev(u, tck) - Notice that (i) we force interpolation by using `s=0`, + Notice that (i) we force interpolation by using ``s=0``, (ii) the parameterization, ``u``, is generated automatically. Now plot the result: @@ -508,7 +508,7 @@ def sproot(tck, mest=10): >>> sproot(tck) array([], dtype=float64) - Converting to a PPoly object does find the roots at `x=2`: + Converting to a PPoly object does find the roots at ``x=2``: >>> ppoly = PPoly.from_spline(tck) >>> ppoly.roots(extrapolate=False) diff --git a/scipy/linalg/_basic.py b/scipy/linalg/_basic.py index 84e4cafdc4a6..71be3319b6b7 100644 --- a/scipy/linalg/_basic.py +++ b/scipy/linalg/_basic.py @@ -264,14 +264,14 @@ def solve(a, b, lower=False, overwrite_a=False, def solve_triangular(a, b, trans=0, lower=False, unit_diagonal=False, overwrite_b=False, check_finite=True): """ - Solve the equation `a x = b` for `x`, assuming a is a triangular matrix. + Solve the equation ``a x = b`` for `x`, assuming a is a triangular matrix. Parameters ---------- a : (M, M) array_like A triangular matrix b : (M,) or (M, N) array_like - Right-hand side matrix in `a x = b` + Right-hand side matrix in ``a x = b`` lower : bool, optional Use only data contained in the lower triangle of `a`. Default is to use upper triangle. @@ -298,7 +298,7 @@ def solve_triangular(a, b, trans=0, lower=False, unit_diagonal=False, Returns ------- x : (M,) or (M, N) ndarray - Solution to the system `a x = b`. Shape of return matches `b`. + Solution to the system ``a x = b``. Shape of return matches `b`. Raises ------ diff --git a/scipy/linalg/_generate_pyx.py b/scipy/linalg/_generate_pyx.py index 8a00f5d279e1..bdecd5f90b7d 100644 --- a/scipy/linalg/_generate_pyx.py +++ b/scipy/linalg/_generate_pyx.py @@ -63,7 +63,7 @@ def import_wrappers_common(): If using ``cdotu``, ``cdotc``, ``zdotu``, ``zdotc``, ``sladiv``, or ``dladiv``, the ``CYTHON_CCOMPLEX`` define must be set to 0 during compilation. For -example, in a `meson.build` file when using Meson:: +example, in a ``meson.build`` file when using Meson:: py.extension_module('ext_module' 'ext_module.pyx', diff --git a/scipy/linalg/_interpolative_backend.py b/scipy/linalg/_interpolative_backend.py index 7835314f79c1..a9fd70948ef4 100644 --- a/scipy/linalg/_interpolative_backend.py +++ b/scipy/linalg/_interpolative_backend.py @@ -105,7 +105,7 @@ def idd_frm(n, w, x): randomly permuted. :param n: - Greatest power-of-two integer satisfying `n <= x.size` as obtained from + Greatest power-of-two integer satisfying ``n <= x.size`` as obtained from :func:`idd_frmi`; `n` is also the length of the output vector. :type n: int :param w: @@ -131,10 +131,10 @@ def idd_sfrm(l, n, w, x): the transformed vector is known a priori. :param l: - Length of transformed vector, satisfying `l <= n`. + Length of transformed vector, satisfying ``l <= n``. :type l: int :param n: - Greatest power-of-two integer satisfying `n <= x.size` as obtained from + Greatest power-of-two integer satisfying ``n <= x.size`` as obtained from :func:`idd_sfrmi`. :type n: int :param w: @@ -160,7 +160,7 @@ def idd_frmi(m): :type m: int :return: - Greatest power-of-two integer `n` satisfying `n <= m`. + Greatest power-of-two integer `n` satisfying ``n <= m``. :rtype: int :return: Initialization array to be used by :func:`idd_frm`. @@ -181,7 +181,7 @@ def idd_sfrmi(l, m): :type m: int :return: - Greatest power-of-two integer `n` satisfying `n <= m`. + Greatest power-of-two integer `n` satisfying ``n <= m``. :rtype: int :return: Initialization array to be used by :func:`idd_sfrm`. @@ -899,7 +899,7 @@ def idz_frm(n, w, x): randomly permuted. :param n: - Greatest power-of-two integer satisfying `n <= x.size` as obtained from + Greatest power-of-two integer satisfying ``n <= x.size`` as obtained from :func:`idz_frmi`; `n` is also the length of the output vector. :type n: int :param w: @@ -925,10 +925,10 @@ def idz_sfrm(l, n, w, x): the transformed vector is known a priori. :param l: - Length of transformed vector, satisfying `l <= n`. + Length of transformed vector, satisfying ``l <= n``. :type l: int :param n: - Greatest power-of-two integer satisfying `n <= x.size` as obtained from + Greatest power-of-two integer satisfying ``n <= x.size`` as obtained from :func:`idz_sfrmi`. :type n: int :param w: @@ -954,7 +954,7 @@ def idz_frmi(m): :type m: int :return: - Greatest power-of-two integer `n` satisfying `n <= m`. + Greatest power-of-two integer `n` satisfying ``n <= m``. :rtype: int :return: Initialization array to be used by :func:`idz_frm`. @@ -975,7 +975,7 @@ def idz_sfrmi(l, m): :type m: int :return: - Greatest power-of-two integer `n` satisfying `n <= m`. + Greatest power-of-two integer `n` satisfying ``n <= m``. :rtype: int :return: Initialization array to be used by :func:`idz_sfrm`. diff --git a/scipy/linalg/_special_matrices.py b/scipy/linalg/_special_matrices.py index 791ef4a6ba5d..99b7b49d6b39 100644 --- a/scipy/linalg/_special_matrices.py +++ b/scipy/linalg/_special_matrices.py @@ -677,9 +677,9 @@ def pascal(n, kind='symmetric', exact=True): If `exact` is True, the result is either an array of type numpy.uint64 (if n < 35) or an object array of Python long integers. If `exact` is False, the coefficients in the matrix are computed using - `scipy.special.comb` with `exact=False`. The result will be a floating + `scipy.special.comb` with ``exact=False``. The result will be a floating point array, and the values in the array will not be the exact - coefficients, but this version is much faster than `exact=True`. + coefficients, but this version is much faster than ``exact=True``. Returns ------- diff --git a/scipy/linalg/interpolative.py b/scipy/linalg/interpolative.py index b91cdd63a6a5..4f12d4366da7 100644 --- a/scipy/linalg/interpolative.py +++ b/scipy/linalg/interpolative.py @@ -540,7 +540,7 @@ def interp_decomp(A, eps_or_k, rand=True): A : :class:`numpy.ndarray` or :class:`scipy.sparse.linalg.LinearOperator` with `rmatvec` Matrix to be factored eps_or_k : float or int - Relative error (if `eps_or_k < 1`) or rank (if `eps_or_k >= 1`) of + Relative error (if ``eps_or_k < 1``) or rank (if ``eps_or_k >= 1``) of approximation. rand : bool, optional Whether to use random sampling if `A` is of type :class:`numpy.ndarray` @@ -551,7 +551,7 @@ def interp_decomp(A, eps_or_k, rand=True): ------- k : int Rank required to achieve specified relative precision if - `eps_or_k < 1`. + ``eps_or_k < 1``. idx : :class:`numpy.ndarray` Column index array. proj : :class:`numpy.ndarray` @@ -883,7 +883,7 @@ def svd(A, eps_or_k, rand=True): :class:`scipy.sparse.linalg.LinearOperator` with the `matvec` and `rmatvec` methods (to apply the matrix and its adjoint). eps_or_k : float or int - Relative error (if `eps_or_k < 1`) or rank (if `eps_or_k >= 1`) of + Relative error (if ``eps_or_k < 1``) or rank (if ``eps_or_k >= 1``) of approximation. rand : bool, optional Whether to use random sampling if `A` is of type :class:`numpy.ndarray` diff --git a/scipy/ndimage/_ni_docstrings.py b/scipy/ndimage/_ni_docstrings.py index e6469f2c75fc..98de97c32acb 100644 --- a/scipy/ndimage/_ni_docstrings.py +++ b/scipy/ndimage/_ni_docstrings.py @@ -183,9 +183,9 @@ """prefilter : bool, optional Determines if the input array is prefiltered with `spline_filter` before interpolation. The default is True, which will create a - temporary `float64` array of filtered values if `order > 1`. If + temporary `float64` array of filtered values if ``order > 1``. If setting this to False, the output will be slightly blurred if - `order > 1`, unless the input is prefiltered, i.e. it is the result + ``order > 1``, unless the input is prefiltered, i.e. it is the result of calling `spline_filter` on the original input.""") docdict = { diff --git a/scipy/odr/_odrpack.py b/scipy/odr/_odrpack.py index 652e06f9bebe..ac1b97e3726d 100644 --- a/scipy/odr/_odrpack.py +++ b/scipy/odr/_odrpack.py @@ -493,13 +493,13 @@ class Model: `fjacb` if the response variable is multi-dimensional, then the return array's shape is `(q, p, n)` such that ``fjacb(x,beta)[l,k,i] = - d f_l(X,B)/d B_k`` evaluated at the ith data point. If `q == 1`, then + d f_l(X,B)/d B_k`` evaluated at the ith data point. If ``q == 1``, then the return array is only rank-2 and with shape `(p, n)`. `fjacd` as with fjacb, only the return array's shape is `(q, m, n)` such that ``fjacd(x,beta)[l,j,i] = d f_l(X,B)/d X_j`` at the ith data - point. If `q == 1`, then the return array's shape is `(m, n)`. If - `m == 1`, the shape is (q, n). If `m == q == 1`, the shape is `(n,)`. + point. If ``q == 1``, then the return array's shape is `(m, n)`. If + ``m == 1``, the shape is (q, n). If `m == q == 1`, the shape is `(n,)`. """ diff --git a/scipy/optimize/_direct_py.py b/scipy/optimize/_direct_py.py index 440cbb5ae866..1c24afbb0f0b 100644 --- a/scipy/optimize/_direct_py.py +++ b/scipy/optimize/_direct_py.py @@ -106,10 +106,10 @@ def direct( of the complete search space. Must lie between 0 and 1. Default is 1e-16. len_tol : float, optional - If `locally_biased=True`, terminate the optimization once half of + If ``locally_biased=True``, terminate the optimization once half of the normalized maximal side length of the hyperrectangle containing the lowest function value is smaller than `len_tol`. - If `locally_biased=False`, terminate the optimization once half of + If ``locally_biased=False``, terminate the optimization once half of the normalized diagonal of the hyperrectangle containing the lowest function value is smaller than `len_tol`. Must lie between 0 and 1. Default is 1e-6. diff --git a/scipy/optimize/_optimize.py b/scipy/optimize/_optimize.py index 37472486fd53..684a38b0faee 100644 --- a/scipy/optimize/_optimize.py +++ b/scipy/optimize/_optimize.py @@ -3709,7 +3709,7 @@ def brute(func, ranges, args=(), Ns=20, full_output=0, finish=fmin, the `finish` program's results usually will not coincide with any gridpoint, and may fall outside the grid's boundary. Thus, if a minimum only needs to be found over the provided grid points, make - sure to pass in `finish=None`. + sure to pass in ``finish=None``. *Note 2*: The grid of points is a `numpy.mgrid` object. For `brute` the `ranges` and `Ns` inputs have the following effect. diff --git a/scipy/optimize/_shgo.py b/scipy/optimize/_shgo.py index 4dce006dcbca..9c8f36a777f8 100644 --- a/scipy/optimize/_shgo.py +++ b/scipy/optimize/_shgo.py @@ -70,9 +70,9 @@ def shgo( n : int, optional Number of sampling points used in the construction of the simplicial complex. For the default ``simplicial`` sampling method 2**dim + 1 - sampling points are generated instead of the default `n=100`. For all + sampling points are generated instead of the default ``n=100``. For all other specified values `n` sampling points are generated. For - ``sobol``, ``halton`` and other arbitrary `sampling_methods` `n=100` or + ``sobol``, ``halton`` and other arbitrary `sampling_methods` ``n=100`` or another specified number of sampling points are generated. iters : int, optional Number of iterations used in the construction of the simplicial diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index ef1d50fdb74b..f2efc0a455e5 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -1371,7 +1371,7 @@ def convolve(in1, in2, mode='full', method='auto'): `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 + there are certain constraints that may force ``method='direct'`` (more detail in `choose_conv_method` docstring). Examples diff --git a/scipy/special/_add_newdocs.py b/scipy/special/_add_newdocs.py index 3e18dbee2edb..44079df5df3c 100644 --- a/scipy/special/_add_newdocs.py +++ b/scipy/special/_add_newdocs.py @@ -2154,12 +2154,12 @@ def add_newdoc(name, doc): ----- Wrapper for the Cephes [1]_ routine `ellpe`. - For `m > 0` the computation uses the approximation, + For ``m > 0`` the computation uses the approximation, .. math:: E(m) \approx P(1-m) - (1-m) \log(1-m) Q(1-m), where :math:`P` and :math:`Q` are tenth-order polynomials. For - `m < 0`, the relation + ``m < 0``, the relation .. math:: E(m) = E(m/(m - 1)) \sqrt(1-m) @@ -2371,14 +2371,14 @@ def add_newdoc(name, doc): ----- Wrapper for the Cephes [1]_ routine `ellpk`. - For `p <= 1`, computation uses the approximation, + For ``p <= 1``, computation uses the approximation, .. math:: K(p) \\approx P(p) - \\log(p) Q(p), where :math:`P` and :math:`Q` are tenth-order polynomials. The argument `p` is used internally rather than `m` so that the logarithmic - singularity at `m = 1` will be shifted to the origin; this preserves - maximum accuracy. For `p > 1`, the identity + singularity at ``m = 1`` will be shifted to the origin; this preserves + maximum accuracy. For ``p > 1``, the identity .. math:: K(p) = K(1/p)/\\sqrt(p) @@ -5172,7 +5172,7 @@ def add_newdoc(name, doc): >>> import numpy as np >>> import scipy.special as sc - It is 1 for `x > 0`. + It is 1 for ``x > 0``. >>> sc.gammasgn([1, 2, 3, 4]) array([1., 1., 1., 1.]) @@ -6062,7 +6062,7 @@ def add_newdoc(name, doc): >>> 1 + (a / b) * x array([-1., -3., -5., -7.]) - It reduces to the exponential function when `a = b`. + 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]) @@ -11329,7 +11329,7 @@ def add_newdoc(name, doc): 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, + values computed by the Cephes routines `y0` and `y1`. If ``n = 0`` or 1, the routine for `y0` or `y1` is called directly. References diff --git a/scipy/special/_basic.py b/scipy/special/_basic.py index e89c0c4edd17..c003bb423cc1 100644 --- a/scipy/special/_basic.py +++ b/scipy/special/_basic.py @@ -3247,7 +3247,7 @@ def stirling2(N, K, *, exact=False): 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 + 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 diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py index a4e1dcd27a19..2234cc3fd75b 100644 --- a/scipy/stats/_continuous_distns.py +++ b/scipy/stats/_continuous_distns.py @@ -4943,14 +4943,14 @@ class geninvgauss_gen(rv_continuous): 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]_). + 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`. + `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]_. @@ -9240,7 +9240,7 @@ class semicircular_gen(rv_continuous): for :math:`-1 \le x \le 1`. - The distribution is a special case of `rdist` with `c = 3`. + The distribution is a special case of `rdist` with ``c = 3``. %(after_notes)s @@ -10625,7 +10625,7 @@ def fit(self, data, *args, **kwds): 11.0 If we know the data comes from a uniform distribution where the support - starts at 0, we can use `floc=0`: + starts at 0, we can use ``floc=0``: >>> loc, scale = uniform.fit(x, floc=0) >>> loc @@ -10634,7 +10634,7 @@ def fit(self, data, *args, **kwds): 13.0 Alternatively, if we know the length of the support is 12, we can use - `fscale=12`: + ``fscale=12``: >>> loc, scale = uniform.fit(x, fscale=12) >>> loc diff --git a/scipy/stats/_discrete_distns.py b/scipy/stats/_discrete_distns.py index f7721f5c3a03..087e4b042967 100644 --- a/scipy/stats/_discrete_distns.py +++ b/scipy/stats/_discrete_distns.py @@ -1389,7 +1389,7 @@ class zipfian_gen(rv_discrete): %(example)s - Confirm that `zipfian` reduces to `zipf` for large `n`, `a > 1`. + Confirm that `zipfian` reduces to `zipf` for large `n`, ``a > 1``. >>> import numpy as np >>> from scipy.stats import zipf, zipfian diff --git a/scipy/stats/_mgc.py b/scipy/stats/_mgc.py index e8eb835cbafb..ea905bba9994 100644 --- a/scipy/stats/_mgc.py +++ b/scipy/stats/_mgc.py @@ -536,7 +536,7 @@ def _two_sample_transform(u, v): Returns ------- x : ndarray - Concatenate `u` and `v` along the `axis = 0`. `x` thus has shape + 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 diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 19f24929bb47..dc28fbd95d3b 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -63,8 +63,8 @@ def bayes_mvs(data, alpha=0.90): (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 + 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 @@ -4374,7 +4374,7 @@ 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) + radians, their *circular mean* is defined by ([1]_, Eq. 2.2.4) .. math:: @@ -4466,7 +4466,7 @@ 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) + radians, their *circular variance* is defined by ([2]_, Eq. 2.3.3) .. math:: diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 4409cc632cea..43422356c2ab 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -2194,7 +2194,7 @@ 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 + 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. @@ -2982,7 +2982,7 @@ def zscore(a, axis=0, ddof=0, nan_policy='propagate'): [-0.22095197, 0.24468594, 1.19042819, -1.21416216], [-0.82780366, 1.4457416 , -0.43867764, -0.1792603 ]]) - An example with `nan_policy='omit'`: + 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]]) @@ -10626,7 +10626,7 @@ def expectile(a, alpha=0.5, *, weights=None): 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. + 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`. @@ -11014,7 +11014,7 @@ def _xp_mean(x, /, *, axis=None, weights=None, keepdims=False, nan_policy='propa 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 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. From c109bb1c4d1560e01058307260032d639da9feb6 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 13 Jun 2024 15:16:39 -0400 Subject: [PATCH 403/500] ENH: stats.gmean: add array API support (#20946) Co-authored-by: Lucas Colley --- scipy/stats/_stats_py.py | 10 ++-- scipy/stats/tests/test_stats.py | 87 ++++++++++++++++++++------------- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 43422356c2ab..e9c8521639da 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -202,16 +202,16 @@ def gmean(a, axis=0, dtype=None, weights=None): 2.80668351922014 """ - - a = np.asarray(a, dtype=dtype) + xp = array_namespace(a, weights) + a = xp.asarray(a, dtype=dtype) if weights is not None: - weights = np.asarray(weights, dtype=dtype) + weights = xp.asarray(weights, dtype=dtype) with np.errstate(divide='ignore'): - log_a = np.log(a) + log_a = xp.log(a) - return np.exp(np.average(log_a, axis=axis, weights=weights)) + return xp.exp(_xp_mean(log_a, axis=axis, weights=weights)) @_axis_nan_policy_factory( diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 02e68c76ee6a..d6a701037f00 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6753,12 +6753,15 @@ def test_obrientransform(): assert_array_almost_equal(result[0], expected, decimal=4) -def check_equal_gmean(array_like, desired, axis=None, dtype=None, rtol=1e-7, +def check_equal_gmean(array_like, desired, *, xp, axis=None, dtype=None, rtol=1e-7, weights=None): # Note this doesn't test when axis is not specified + dtype = dtype or xp.float64 + array_like = xp.asarray(array_like, dtype=dtype) + desired = xp.asarray(desired, dtype=dtype) + weights = xp.asarray(weights, dtype=dtype) if weights is not None else weights x = stats.gmean(array_like, axis=axis, dtype=dtype, weights=weights) - assert_allclose(x, desired, rtol=rtol) - assert_equal(x.dtype, dtype) + xp_assert_close(x, desired, rtol=rtol) def check_equal_hmean(array_like, desired, axis=None, dtype=None, rtol=1e-7, @@ -6874,115 +6877,129 @@ def test_weights_masked_1d_array(self): check_equal_hmean(a, desired, weights=weights, rtol=1e-5) +@array_api_compatible class TestGeoMean: - def test_0(self): + def test_0(self, xp): a = [1, 0, 2] desired = 0 - check_equal_gmean(a, desired) + check_equal_gmean(a, desired, xp=xp) - def test_1d_list(self): + def test_1d_list(self, xp): # Test a 1d list a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] desired = 45.2872868812 - check_equal_gmean(a, desired) + check_equal_gmean(a, desired, xp=xp) a = [1, 2, 3, 4] desired = power(1 * 2 * 3 * 4, 1. / 4.) - check_equal_gmean(a, desired, rtol=1e-14) + check_equal_gmean(a, desired, rtol=1e-14, xp=xp) - def test_1d_array(self): + def test_1d_array(self, xp): # Test a 1d array a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) desired = 45.2872868812 - check_equal_gmean(a, desired) + check_equal_gmean(a, desired, xp=xp) a = array([1, 2, 3, 4], float32) desired = power(1 * 2 * 3 * 4, 1. / 4.) - check_equal_gmean(a, desired, dtype=float32) + check_equal_gmean(a, desired, dtype=xp.float32, xp=xp) # Note the next tests use axis=None as default, not axis=0 - def test_2d_list(self): + def test_2d_list(self, xp): # Test a 2d list a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] desired = 52.8885199 - check_equal_gmean(a, desired) + check_equal_gmean(a, desired, xp=xp) - def test_2d_array(self): + def test_2d_array(self, xp): # Test a 2d array a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] desired = 52.8885199 - check_equal_gmean(array(a), desired) + check_equal_gmean(array(a), desired, xp=xp) - def test_2d_axis0(self): + def test_2d_axis0(self, xp): # Test a 2d list 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) + 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) + check_equal_gmean(a, desired, axis=0, rtol=1e-14, xp=xp) - def test_2d_axis1(self): + def test_2d_axis1(self, xp): # Test a 2d list 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) + 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) + check_equal_gmean(a, desired, axis=1, rtol=1e-14, xp=xp) - def test_large_values(self): + def test_large_values(self, xp): a = array([1e100, 1e200, 1e300]) desired = 1e200 - check_equal_gmean(a, desired, rtol=1e-13) + check_equal_gmean(a, desired, rtol=1e-13, xp=xp) - def test_1d_list0(self): + def test_1d_list0(self, xp): # Test a 1d list 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) + check_equal_gmean(a, desired, xp=xp) - def test_1d_array0(self): + def test_1d_array0(self, xp): # Test a 1d array with zero element a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 0]) desired = 0.0 # due to exp(-inf)=0 with np.errstate(divide='ignore'): - check_equal_gmean(a, desired) + check_equal_gmean(a, desired, xp=xp) - def test_1d_list_neg(self): + def test_1d_list_neg(self, xp): # Test a 1d list 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) + check_equal_gmean(a, desired, xp=xp) - def test_weights_1d_list(self): + @pytest.mark.skip_xp_backends( + np_only=True, + reasons=['array-likes only supported for NumPy backend'], + ) + @pytest.mark.usefixtures("skip_xp_backends") + 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 - check_equal_gmean(a, desired, weights=weights, rtol=1e-5) - def test_weights_1d_array(self): + # 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_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]) weights = np.array([2, 5, 6, 4, 3]) desired = 2.77748 - check_equal_gmean(a, desired, weights=weights, rtol=1e-5) + check_equal_gmean(a, desired, weights=weights, rtol=1e-5, xp=xp) - def test_weights_masked_1d_array(self): + @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 - check_equal_gmean(a, desired, weights=weights, rtol=1e-5) + 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) class TestPowMean: From 0f911083ea9b2de2f49df09655a9bf380691a162 Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Tue, 23 Apr 2024 09:26:38 +0200 Subject: [PATCH 404/500] MAINT:linalg:Remove id_dist Fortran files [skip ci] --- scipy/linalg/src/id_dist/README.txt | 6 - scipy/linalg/src/id_dist/doc/doc.bib | 19 - scipy/linalg/src/id_dist/doc/doc.tex | 977 ------ scipy/linalg/src/id_dist/doc/supertabular.sty | 483 --- scipy/linalg/src/id_dist/src/dfft.f | 3014 ----------------- scipy/linalg/src/id_dist/src/id_rand.f | 379 --- scipy/linalg/src/id_dist/src/id_rtrans.f | 746 ---- scipy/linalg/src/id_dist/src/idd_frm.f | 525 --- scipy/linalg/src/id_dist/src/idd_house.f | 288 -- scipy/linalg/src/id_dist/src/idd_id.f | 560 --- scipy/linalg/src/id_dist/src/idd_id2svd.f | 384 --- scipy/linalg/src/id_dist/src/idd_qrpiv.f | 893 ----- scipy/linalg/src/id_dist/src/idd_sfft.f | 443 --- scipy/linalg/src/id_dist/src/idd_snorm.f | 400 --- scipy/linalg/src/id_dist/src/idd_svd.f | 409 --- scipy/linalg/src/id_dist/src/iddp_aid.f | 386 --- scipy/linalg/src/id_dist/src/iddp_asvd.f | 180 - scipy/linalg/src/id_dist/src/iddp_rid.f | 376 -- scipy/linalg/src/id_dist/src/iddp_rsvd.f | 216 -- scipy/linalg/src/id_dist/src/iddr_aid.f | 208 -- scipy/linalg/src/id_dist/src/iddr_asvd.f | 114 - scipy/linalg/src/id_dist/src/iddr_rid.f | 155 - scipy/linalg/src/id_dist/src/iddr_rsvd.f | 157 - scipy/linalg/src/id_dist/src/idz_frm.f | 419 --- scipy/linalg/src/id_dist/src/idz_house.f | 298 -- scipy/linalg/src/id_dist/src/idz_id.f | 566 ---- scipy/linalg/src/id_dist/src/idz_id2svd.f | 389 --- scipy/linalg/src/id_dist/src/idz_qrpiv.f | 898 ----- scipy/linalg/src/id_dist/src/idz_sfft.f | 210 -- scipy/linalg/src/id_dist/src/idz_snorm.f | 407 --- scipy/linalg/src/id_dist/src/idz_svd.f | 438 --- scipy/linalg/src/id_dist/src/idzp_aid.f | 390 --- scipy/linalg/src/id_dist/src/idzp_asvd.f | 207 -- scipy/linalg/src/id_dist/src/idzp_rid.f | 379 --- scipy/linalg/src/id_dist/src/idzp_rsvd.f | 244 -- scipy/linalg/src/id_dist/src/idzr_aid.f | 209 -- scipy/linalg/src/id_dist/src/idzr_asvd.f | 118 - scipy/linalg/src/id_dist/src/idzr_rid.f | 156 - scipy/linalg/src/id_dist/src/idzr_rsvd.f | 159 - scipy/linalg/src/id_dist/src/prini.f | 113 - 40 files changed, 16918 deletions(-) delete mode 100644 scipy/linalg/src/id_dist/README.txt delete mode 100644 scipy/linalg/src/id_dist/doc/doc.bib delete mode 100644 scipy/linalg/src/id_dist/doc/doc.tex delete mode 100644 scipy/linalg/src/id_dist/doc/supertabular.sty delete mode 100644 scipy/linalg/src/id_dist/src/dfft.f delete mode 100644 scipy/linalg/src/id_dist/src/id_rand.f delete mode 100644 scipy/linalg/src/id_dist/src/id_rtrans.f delete mode 100644 scipy/linalg/src/id_dist/src/idd_frm.f delete mode 100644 scipy/linalg/src/id_dist/src/idd_house.f delete mode 100644 scipy/linalg/src/id_dist/src/idd_id.f delete mode 100644 scipy/linalg/src/id_dist/src/idd_id2svd.f delete mode 100644 scipy/linalg/src/id_dist/src/idd_qrpiv.f delete mode 100644 scipy/linalg/src/id_dist/src/idd_sfft.f delete mode 100644 scipy/linalg/src/id_dist/src/idd_snorm.f delete mode 100644 scipy/linalg/src/id_dist/src/idd_svd.f delete mode 100644 scipy/linalg/src/id_dist/src/iddp_aid.f delete mode 100644 scipy/linalg/src/id_dist/src/iddp_asvd.f delete mode 100644 scipy/linalg/src/id_dist/src/iddp_rid.f delete mode 100644 scipy/linalg/src/id_dist/src/iddp_rsvd.f delete mode 100644 scipy/linalg/src/id_dist/src/iddr_aid.f delete mode 100644 scipy/linalg/src/id_dist/src/iddr_asvd.f delete mode 100644 scipy/linalg/src/id_dist/src/iddr_rid.f delete mode 100644 scipy/linalg/src/id_dist/src/iddr_rsvd.f delete mode 100644 scipy/linalg/src/id_dist/src/idz_frm.f delete mode 100644 scipy/linalg/src/id_dist/src/idz_house.f delete mode 100644 scipy/linalg/src/id_dist/src/idz_id.f delete mode 100644 scipy/linalg/src/id_dist/src/idz_id2svd.f delete mode 100644 scipy/linalg/src/id_dist/src/idz_qrpiv.f delete mode 100644 scipy/linalg/src/id_dist/src/idz_sfft.f delete mode 100644 scipy/linalg/src/id_dist/src/idz_snorm.f delete mode 100644 scipy/linalg/src/id_dist/src/idz_svd.f delete mode 100644 scipy/linalg/src/id_dist/src/idzp_aid.f delete mode 100644 scipy/linalg/src/id_dist/src/idzp_asvd.f delete mode 100644 scipy/linalg/src/id_dist/src/idzp_rid.f delete mode 100644 scipy/linalg/src/id_dist/src/idzp_rsvd.f delete mode 100644 scipy/linalg/src/id_dist/src/idzr_aid.f delete mode 100644 scipy/linalg/src/id_dist/src/idzr_asvd.f delete mode 100644 scipy/linalg/src/id_dist/src/idzr_rid.f delete mode 100644 scipy/linalg/src/id_dist/src/idzr_rsvd.f delete mode 100644 scipy/linalg/src/id_dist/src/prini.f diff --git a/scipy/linalg/src/id_dist/README.txt b/scipy/linalg/src/id_dist/README.txt deleted file mode 100644 index 000bb1e5f593..000000000000 --- a/scipy/linalg/src/id_dist/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -Please see the documentation in subdirectory doc of this id_dist directory. - -At the minimum, please read Subsection 2.1 and Section 3 in the documentation, -and beware that the _N.B._'s in the source code comments highlight important -information about the routines -- _N.B._ stands for _nota_bene_ (Latin for -"note well"). diff --git a/scipy/linalg/src/id_dist/doc/doc.bib b/scipy/linalg/src/id_dist/doc/doc.bib deleted file mode 100644 index 1ab5cb220942..000000000000 --- a/scipy/linalg/src/id_dist/doc/doc.bib +++ /dev/null @@ -1,19 +0,0 @@ -@book{golub-van_loan, - author = {Gene Golub and Charles {Van L}oan}, - title = {Matrix Computations}, - edition = {Third}, - publisher = {Johns Hopkins University Press}, - year = {1996}, - address = {Baltimore, Maryland} -} - -@article{halko-martinsson-tropp, - author = {Nathan Halko and {P.-G.} Martinsson and Joel A. Tropp}, - title = {Finding structure with randomness: probabilistic algorithms - for constructing approximate matrix decompositions}, - journal = {SIAM Review}, - volume = {53}, - number = {2}, - pages = {217--288}, - year = {2011} -} diff --git a/scipy/linalg/src/id_dist/doc/doc.tex b/scipy/linalg/src/id_dist/doc/doc.tex deleted file mode 100644 index 8bcece8c4b69..000000000000 --- a/scipy/linalg/src/id_dist/doc/doc.tex +++ /dev/null @@ -1,977 +0,0 @@ -\documentclass[letterpaper,12pt]{article} -\usepackage[margin=1in]{geometry} -\usepackage{verbatim} -\usepackage{amsmath} -\usepackage{supertabular} -\usepackage{array} - -\def\T{{\hbox{\scriptsize{\rm T}}}} -\def\epsilon{\varepsilon} -\def\bigoh{\mathcal{O}} -\def\phi{\varphi} -\def\st{{\hbox{\scriptsize{\rm st}}}} -\def\th{{\hbox{\scriptsize{\rm th}}}} -\def\x{\mathbf{x}} - - -\title{ID: A software package for low-rank approximation - of matrices via interpolative decompositions, Version 0.4} -\author{Per-Gunnar Martinsson, Vladimir Rokhlin,\\ - Yoel Shkolnisky, and Mark Tygert} - - -\begin{document} - -\maketitle - -\newpage - -{\parindent=0pt - -The present document and all of the software -in the accompanying distribution (which is contained in the directory -{\tt id\_dist} and its subdirectories, or in the file -{\tt id\_dist.tar.gz})\, is - -\bigskip - -Copyright \copyright\ 2014 by P.-G. Martinsson, V. Rokhlin, -Y. Shkolnisky, and M. Tygert. - -\bigskip - -All rights reserved. - -\bigskip - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -\begin{enumerate} -\item Redistributions of source code must retain the above copyright -notice, this list of conditions, and the following disclaimer. -\item 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. -\item None of the names of the copyright holders may be used to endorse -or promote products derived from this software without specific prior -written permission. -\end{enumerate} - -\bigskip - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 OWNERS 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. - -} - -\newpage - -\tableofcontents - -\newpage - - - -\hrule - -\medskip - -\centerline{\Large \bf IMPORTANT} - -\medskip - -\hrule - -\medskip - -\noindent At the minimum, please read Subsection~\ref{warning} -and Section~\ref{naming} below, and beware that the {\it N.B.}'s -in the source code comments highlight key information about the routines; -{\it N.B.} stands for {\it nota bene} (Latin for ``note well''). - -\medskip - -\hrule - -\bigskip - - - -\section{Introduction} - -This software distribution provides Fortran routines -for computing low-rank approximations to matrices, -in the forms of interpolative decompositions (IDs) -and singular value decompositions (SVDs). -The routines use algorithms based on the ID. -The ID is also commonly known as -the approximation obtained via skeletonization, -the approximation obtained via subsampling, -and the approximation obtained via subset selection. -The ID provides many advantages in many applications, -and we suspect that it will become increasingly popular -once tools for its computation become more widely available. -This software distribution includes some such tools, -as well as tools for computing low-rank approximations -in the form of SVDs. -Section~\ref{defs} below defines IDs and SVDs, -and provides references to detailed discussions of the algorithms -used in this software package. - -Please beware that normalized power iterations are better suited than -the software in this distribution -for computing principal component analyses -in the typical case when the square of the signal-to-noise ratio -is not orders of magnitude greater than both dimensions -of the data matrix; see~\cite{halko-martinsson-tropp}. - -The algorithms used in this distribution have been optimized -for accuracy, efficiency, and reliability; -as a somewhat counterintuitive consequence, many must be randomized. -All randomized codes in this software package succeed -with overwhelmingly high probability (see, for example, -\cite{halko-martinsson-tropp}). -The truly paranoid are welcome to use the routines {\tt idd\_diffsnorm} -and {\tt idz\_diffsnorm} to evaluate rapidly the quality -of the approximations produced by the randomized algorithms -(as done, for example, in the files -{\tt idd\_a\_test.f}, {\tt idd\_r\_test.f}, {\tt idz\_a\_test.f}, -and {\tt idz\_r\_test.f} in the {\tt test} subdirectory -of the main directory {\tt id\_dist}). -In most circumstances, evaluating the quality of an approximation -via routines {\tt idd\_diffsnorm} or {\tt idz\_diffsnorm} is much faster -than forming the approximation to be evaluated. Still, we are unaware -of any instance in which a properly-compiled routine failed to produce -an accurate approximation. -To facilitate successful compilation, we encourage the user -to read the instructions in the next section, -and to read Section~\ref{naming}, too. - - - -\section{Compilation instructions} - - -Followed in numerical order, the subsections of this section -provide step-by-step instructions for compiling the software -under a Unix-compatible operating system. - - -\subsection{Beware that default command-line flags may not be - sufficient for compiling the source codes!} -\label{warning} - -The Fortran source codes in this distribution pass {\tt real*8} -variables as integer variables, integers as {\tt real*8}'s, -{\tt real*8}'s as {\tt complex*16}'s, and so on. -This is common practice in numerical codes, and is not an error; -be sure to provide the relevant command-line flags to the compiler -(for example, run {\tt fort77} and {\tt f2c} with the flag {\tt -!P}). -When following the compilation instructions -in Subsection~\ref{makefile_edit} below, -be sure to set {\tt FFLAGS} appropriately. - - -\subsection{Install LAPACK} - -The SVD routines in this distribution depend on LAPACK. -Before compiling the present distribution, -create the LAPACK and BLAS archive (library) {\tt .a} files; -information about installing LAPACK is available -at {\tt http://www.netlib.org/lapack/} (and several other web sites). - - -\subsection{Decompress and untar the file {\tt id\_dist.tar.gz}} - -At the command line, decompress and untar the file -{\tt id\_dist.tar.gz} by issuing a command such as -{\tt tar -xvvzf id\_dist.tar.gz}. -This will create a directory named {\tt id\_dist}. - - -\subsection{Edit the Makefile} -\label{makefile_edit} - -The directory {\tt id\_dist} contains a file named {\tt Makefile}. -In {\tt Makefile}, set the following: -% -\begin{itemize} -\item {\tt FC} is the Fortran compiler. -\item {\tt FFLAGS} is the set of command-line flags - (specifying optimization settings, for example) - for the Fortran compiler specified by {\tt FC}; - please heed the warning in Subsection~\ref{warning} above! -\item {\tt BLAS\_LIB} is the file-system path to the BLAS archive - (library) {\tt .a} file. -\item {\tt LAPACK\_LIB} is the file-system path to the LAPACK archive - (library) {\tt .a} file. -\item {\tt ARCH} is the archiver utility (usually {\tt ar}). -\item {\tt ARCHFLAGS} is the set of command-line flags - for the archiver specified by {\tt ARCH} needed - to create an archive (usually {\tt cr}). -\item {\tt RANLIB} is to be set to {\tt ranlib} - when {\tt ranlib} is available, and is to be set to {\tt echo} - when {\tt ranlib} is not available. -\end{itemize} - - -\subsection{Make and test the libraries} - -At the command line in a shell that adheres -to the Bourne shell conventions for redirection, issue the command -``{\tt make clean; make}'' to both create the archive (library) -{\tt id\_lib.a} and test it. -(In most modern Unix distributions, {\tt sh} is the Bourne shell, -or else is fully compatible with the Bourne shell; -the Korn shell {\tt ksh} and the Bourne-again shell {\tt bash} -also use the Bourne shell conventions for redirection.) -{\tt make} places the file {\tt id\_lib.a} -in the directory {\tt id\_dist}; the archive (library) file -{\tt id\_lib.a} contains machine code for all user-callable routines -in this distribution. - - - -\section{Naming conventions} -\label{naming} - -The names of routines and files in this distribution -start with prefixes, followed by an underscore (``\_''). -The prefixes are two to four characters in length, -and have the following meanings: -% -\begin{itemize} -\item The first two letters are always ``{\tt id}'', - the name of this distribution. -\item The third letter (when present) is either ``{\tt d}'' - or ``{\tt z}''; - ``{\tt d}'' stands for double precision ({\tt real*8}), - and ``{\tt z}'' stands for double complex ({\tt complex*16}). -\item The fourth letter (when present) is either ``{\tt r}'' - or ``{\tt p}''; - ``{\tt r}'' stands for specified rank, - and ``{\tt p}'' stands for specified precision. - The specified rank routines require the user to provide - the rank of the approximation to be constructed, - while the specified precision routines adjust the rank adaptively - to attain the desired precision. -\end{itemize} - -For example, {\tt iddr\_aid} is a {\tt real*8} routine which computes -an approximation of specified rank. -{\tt idz\_snorm} is a {\tt complex*16} routine. -{\tt id\_randperm} is yet another routine in this distribution. - - - -\section{Example programs} - -For examples of how to use the user-callable routines -in this distribution, see the source codes in subdirectory {\tt test} -of the main directory {\tt id\_dist}. - - - -\section{Directory structure} - -The main {\tt id\_dist} directory contains a Makefile, -the auxiliary text files {\tt README.txt} and {\tt size.txt}, -and the following subdirectories, described in the subsections below: -% -\begin{enumerate} -\item {\tt bin} -\item {\tt development} -\item {\tt doc} -\item {\tt src} -\item {\tt test} -\item {\tt tmp} -\end{enumerate} -% -If a ``{\tt make all}'' command has completed successfully, -then the main {\tt id\_dist} directory will also contain -an archive (library) file {\tt id\_lib.a} containing machine code -for all of the user-callable routines. - - -\subsection{Subdirectory {\tt bin}} - -Once all of the libraries have been made via the Makefile -in the main {\tt id\_dist} directory, -the subdirectory {\tt bin} will contain object files (machine code), -each compiled from the corresponding file of source code -in the subdirectory {\tt src} of {\tt id\_dist}. - - -\subsection{Subdirectory {\tt development}} - -Each Fortran file in the subdirectory {\tt development} -(except for {\tt dfft.f} and {\tt prini.f}) -specifies its dependencies at the top, then provides a main program -for testing and debugging, and finally provides source code -for a library of user-callable subroutines. -The Fortran file {\tt dfft.f} is a copy of P. N. Swarztrauber's FFTPACK library -for computing fast Fourier transforms. -The Fortran file {\tt prini.f} is a copy of V. Rokhlin's library -of formatted printing routines. -Both {\tt dfft.f} (version 4) and {\tt prini.f} are in the public domain. -The shell script {\tt RUNME.sh} runs shell scripts {\tt make\_src.sh} -and {\tt make\_test.sh}, which fill the subdirectories {\tt src} -and {\tt test} of the main directory {\tt id\_dist} -with source codes for user-callable routines -and with the main program testing codes. - - -\subsection{Subdirectory {\tt doc}} - -Subdirectory {\tt doc} contains this documentation, -supplementing comments in the source codes. - - -\subsection{Subdirectory {\tt src}} - -The files in the subdirectory {\tt src} provide source code -for software libraries. Each file in the subdirectory {\tt src} -(except for {\tt dfft.f} and {\tt prini.f}) is -the bottom part of the corresponding file -in the subdirectory {\tt development} of {\tt id\_dist}. -The file {\tt dfft.f} is just a copy -of P. N. Swarztrauber's FFTPACK library -for computing fast Fourier transforms. -The file {\tt prini.f} is a copy of V. Rokhlin's library -of formatted printing routines. -Both {\tt dfft.f} (version 4) and {\tt prini.f} are in the public domain. - - -\subsection{Subdirectory {\tt test}} - -The files in subdirectory {\tt test} provide source code -for testing and debugging. Each file in subdirectory {\tt test} is -the top part of the corresponding file -in subdirectory {\tt development} of {\tt id\_dist}, -and provides a main program and a list of its dependencies. -These codes provide examples of how to call the user-callable routines. - - - -\section{Catalog of the routines} - -The main routines for decomposing {\tt real*8} matrices are: -% -\begin{enumerate} -% -\item IDs of arbitrary (generally dense) matrices: -{\tt iddp\_id}, {\tt iddr\_id}, {\tt iddp\_aid}, {\tt iddr\_aid} -% -\item IDs of matrices that may be rapidly applied to arbitrary vectors -(as may the matrices' transposes): -{\tt iddp\_rid}, {\tt iddr\_rid} -% -\item SVDs of arbitrary (generally dense) matrices: -{\tt iddp\_svd}, {\tt iddr\_svd}, {\tt iddp\_asvd},\\{\tt iddr\_asvd} -% -\item SVDs of matrices that may be rapidly applied to arbitrary vectors -(as may the matrices' transposes): -{\tt iddp\_rsvd}, {\tt iddr\_rsvd} -% -\end{enumerate} - -Similarly, the main routines for decomposing {\tt complex*16} matrices -are: -% -\begin{enumerate} -% -\item IDs of arbitrary (generally dense) matrices: -{\tt idzp\_id}, {\tt idzr\_id}, {\tt idzp\_aid}, {\tt idzr\_aid} -% -\item IDs of matrices that may be rapidly applied to arbitrary vectors -(as may the matrices' adjoints): -{\tt idzp\_rid}, {\tt idzr\_rid} -% -\item SVDs of arbitrary (generally dense) matrices: -{\tt idzp\_svd}, {\tt idzr\_svd}, {\tt idzp\_asvd},\\{\tt idzr\_asvd} -% -\item SVDs of matrices that may be rapidly applied to arbitrary vectors -(as may the matrices' adjoints): -{\tt idzp\_rsvd}, {\tt idzr\_rsvd} -% -\end{enumerate} - -This distribution also includes routines for constructing pivoted $QR$ -decompositions (in {\tt idd\_qrpiv.f} and {\tt idz\_qrpiv.f}), for -estimating the spectral norms of matrices that may be applied rapidly -to arbitrary vectors as may their adjoints (in {\tt idd\_snorm.f} -and {\tt idz\_snorm.f}), for converting IDs to SVDs (in -{\tt idd\_id2svd.f} and {\tt idz\_id2svd.f}), and for computing rapidly -arbitrary subsets of the entries of the discrete Fourier transforms -of vectors (in {\tt idd\_sfft.f} and {\tt idz\_sfft.f}). - - -\subsection{List of the routines} - -The following is an alphabetical list of the routines -in this distribution, together with brief descriptions -of their functionality and the names of the files containing -the routines' source code: - -\begin{center} -% -\tablehead{\bf Routine & \bf Description & \bf Source file \\} -\tabletail{\hline} -% -\begin{supertabular}{>{\raggedright}p{1.2in} p{.53\textwidth} l} -% -\hline -{\tt id\_frand} & generates pseudorandom numbers drawn uniformly from -the interval $[0,1]$; this routine is more efficient than routine -{\tt id\_srand}, but cannot generate fewer than 55 pseudorandom numbers -per call & {\tt id\_rand.f} \\\hline -% -{\tt id\_frandi} & initializes the seed values for routine -{\tt id\_frand} to specified values & {\tt id\_rand.f} \\\hline -% -{\tt id\_frando} & initializes the seed values for routine -{\tt id\_frand} to their original, default values & {\tt id\_rand.f} -\\\hline -% -{\tt id\_randperm} & generates a uniformly random permutation & -{\tt id\_rand.f} \\\hline -% -{\tt id\_srand} & generates pseudorandom numbers drawn uniformly from -the interval $[0,1]$; this routine is less efficient than routine -{\tt id\_frand}, but can generate fewer than 55 pseudorandom numbers -per call & {\tt id\_rand.f} \\\hline -% -{\tt id\_srandi} & initializes the seed values for routine -{\tt id\_srand} to specified values & {\tt id\_rand.f} \\\hline -% -{\tt id\_srando} & initializes the seed values for routine -{\tt id\_srand} to their original, default values & {\tt id\_rand.f} -\\\hline -% -{\tt idd\_copycols} & collects together selected columns of a matrix & -{\tt idd\_id.f} \\\hline -% -{\tt idd\_diffsnorm} & estimates the spectral norm of the difference -between two matrices specified by routines for applying the matrices -and their transposes to arbitrary vectors; this routine uses the power -method with a random starting vector & {\tt idd\_snorm.f} \\\hline -% -{\tt idd\_enorm} & calculates the Euclidean norm of a vector & -{\tt idd\_snorm.f} \\\hline -% -{\tt idd\_estrank} & estimates the numerical rank of an arbitrary -(generally dense) matrix to a specified precision; this routine is -randomized, and must be initialized with routine {\tt idd\_frmi} & -{\tt iddp\_aid.f} \\\hline -% -{\tt idd\_frm} & transforms a vector into a vector which is -sufficiently scrambled to be subsampled, via a composition of Rokhlin's -random transform, random subselection, and a fast Fourier transform & -{\tt idd\_frm.f} \\\hline -% -{\tt idd\_frmi} & initializes routine {\tt idd\_frm} & {\tt idd\_frm.f} -\\\hline -% -{\tt idd\_getcols} & collects together selected columns of a matrix -specified by a routine for applying the matrix to arbitrary vectors & -{\tt idd\_id.f} \\\hline -% -{\tt idd\_house} & calculates the vector and scalar needed to apply the -Householder transformation reflecting a given vector into its first -entry & {\tt idd\_house.f} \\\hline -% -{\tt idd\_houseapp} & applies a Householder matrix to a vector & -{\tt idd\_house.f} \\\hline -% -{\tt idd\_id2svd} & converts an approximation to a matrix in the form -of an ID into an approximation in the form of an SVD & -{\tt idd\_id2svd.f} \\\hline -% -{\tt idd\_ldiv} & finds the greatest integer less than or equal to a -specified integer, that is divisible by another (larger) specified -integer & {\tt idd\_sfft.f} \\\hline -% -{\tt idd\_pairsamps} & calculates the indices of the pairs of integers -that the individual integers in a specified set belong to & -{\tt idd\_frm.f} \\\hline -% -{\tt idd\_permmult} & multiplies together a bunch of permutations & -{\tt idd\_qrpiv.f} \\\hline -% -{\tt idd\_qinqr} & reconstructs the $Q$ matrix in a $QR$ decomposition -from the output of routines {\tt iddp\_qrpiv} or {\tt iddr\_qrpiv} & -{\tt idd\_qrpiv.f} \\\hline -% -{\tt idd\_qrmatmat} & applies to multiple vectors collected together as -a matrix the $Q$ matrix (or its transpose) in the $QR$ decomposition of -a matrix, as described by the output of routines {\tt iddp\_qrpiv} or -{\tt iddr\_qrpiv}; to apply $Q$ (or its transpose) to a single vector -without having to provide a work array, use routine {\tt idd\_qrmatvec} -instead & {\tt idd\_qrpiv.f} \\\hline -% -{\tt idd\_qrmatvec} & applies to a single vector the $Q$ matrix (or its -transpose) in the $QR$ decomposition of a matrix, as described by the -output of routines {\tt iddp\_qrpiv} or {\tt iddr\_qrpiv}; to apply $Q$ -(or its transpose) to several vectors efficiently, use routine -{\tt idd\_qrmatmat} instead & {\tt idd\_qrpiv.f} \\\hline -% -{\tt idd\_random\_} {\tt transf} & applies rapidly a -random orthogonal matrix to a user-supplied vector & {\tt id\_rtrans.f} -\\\hline -% -{\tt idd\_random\_ transf\_init} & \raggedright initializes routines -{\tt idd\_random\_transf} and {\tt idd\_random\_transf\_inverse} & -{\tt id\_rtrans.f} \\\hline -% -{\tt idd\_random\_} {\tt transf\_inverse} & applies -rapidly the inverse of the operator applied by routine -{\tt idd\_random\_transf} & {\tt id\_rtrans.f} \\\hline -% -{\tt idd\_reconid} & reconstructs a matrix from its ID & -{\tt idd\_id.f} \\\hline -% -{\tt idd\_reconint} & constructs $P$ in the ID $A = B \, P$, where the -columns of $B$ are a subset of the columns of $A$, and $P$ is the -projection coefficient matrix, given {\tt list}, {\tt krank}, and -{\tt proj} output by routines {\tt iddr\_id}, {\tt iddp\_id}, -{\tt iddr\_aid}, {\tt iddp\_aid}, {\tt iddr\_rid}, or {\tt iddp\_rid} & -{\tt idd\_id.f} \\\hline -% -{\tt idd\_sfft} & rapidly computes a subset of the entries of the -discrete Fourier transform of a vector, composed with permutation -matrices both on input and on output & {\tt idd\_sfft.f} \\\hline -% -{\tt idd\_sffti} & initializes routine {\tt idd\_sfft} & -{\tt idd\_sfft.f} \\\hline -% -{\tt idd\_sfrm} & transforms a vector into a scrambled vector of -specified length, via a composition of Rokhlin's random transform, -random subselection, and a fast Fourier transform & {\tt idd\_frm.f} -\\\hline -% -{\tt idd\_sfrmi} & initializes routine {\tt idd\_sfrm} & -{\tt idd\_frm.f} \\\hline -% -{\tt idd\_snorm} & estimates the spectral norm of a matrix specified by -routines for applying the matrix and its transpose to arbitrary -vectors; this routine uses the power method with a random starting -vector & {\tt idd\_snorm.f} \\\hline -% -{\tt iddp\_aid} & computes the ID of an arbitrary (generally dense) -matrix, to a specified precision; this routine is randomized, and must -be initialized with routine {\tt idd\_frmi} & {\tt iddp\_aid.f} -\\\hline -% -{\tt iddp\_asvd} & computes the SVD of an arbitrary (generally dense) -matrix, to a specified precision; this routine is randomized, and must -be initialized with routine {\tt idd\_frmi} & {\tt iddp\_asvd.f} -\\\hline -% -{\tt iddp\_id} & computes the ID of an arbitrary (generally dense) -matrix, to a specified precision; this routine is often less efficient -than routine {\tt iddp\_aid} & {\tt idd\_id.f} \\\hline -% -{\tt iddp\_qrpiv} & computes the pivoted $QR$ decomposition of an -arbitrary (generally dense) matrix via Householder transformations, -stopping at a specified precision of the decomposition & -{\tt idd\_qrpiv.f} \\\hline -% -{\tt iddp\_rid} & computes the ID, to a specified precision, of a -matrix specified by a routine for applying its transpose to arbitrary -vectors; this routine is randomized & {\tt iddp\_rid.f} \\\hline -% -{\tt iddp\_rsvd} & computes the SVD, to a specified precision, of a -matrix specified by routines for applying the matrix and its transpose -to arbitrary vectors; this routine is randomized & {\tt iddp\_rsvd.f} -\\\hline -% -{\tt iddp\_svd} & computes the SVD of an arbitrary (generally dense) -matrix, to a specified precision; this routine is often less efficient -than routine {\tt iddp\_asvd} & {\tt idd\_svd.f} \\\hline -% -{\tt iddr\_aid} & computes the ID of an arbitrary (generally dense) -matrix, to a specified rank; this routine is randomized, and must be -initialized by routine {\tt iddr\_aidi} & {\tt iddr\_aid.f} \\\hline -% -{\tt iddr\_aidi} & initializes routine {\tt iddr\_aid} & -{\tt iddr\_aid.f} \\\hline -% -{\tt iddr\_asvd} & computes the SVD of an arbitrary (generally dense) -matrix, to a specified rank; this routine is randomized, and must be -initialized with routine {\tt idd\_aidi} & {\tt iddr\_asvd.f} -\\\hline -% -{\tt iddr\_id} & computes the ID of an arbitrary (generally dense) -matrix, to a specified rank; this routine is often less efficient than -routine {\tt iddr\_aid} & {\tt idd\_id.f} \\\hline -% -{\tt iddr\_qrpiv} & computes the pivoted $QR$ decomposition of an -arbitrary (generally dense) matrix via Householder transformations, -stopping at a specified rank of the decomposition & {\tt idd\_qrpiv.f} -\\\hline -% -{\tt iddr\_rid} & computes the ID, to a specified rank, of a matrix -specified by a routine for applying its transpose to arbitrary vectors; -this routine is randomized & {\tt iddr\_rid.f} \\\hline -% -{\tt iddr\_rsvd} & computes the SVD, to a specified rank, of a matrix -specified by routines for applying the matrix and its transpose to -arbitrary vectors; this routine is randomized & {\tt iddr\_rsvd.f} -\\\hline -% -{\tt iddr\_svd} & computes the SVD of an arbitrary (generally dense) -matrix, to a specified rank; this routine is often less efficient than -routine {\tt iddr\_asvd} & {\tt idd\_svd.f} \\\hline -% -{\tt idz\_copycols} & collects together selected columns of a matrix & -{\tt idz\_id.f} \\\hline -% -{\tt idz\_diffsnorm} & estimates the spectral norm of the difference -between two matrices specified by routines for applying the matrices -and their adjoints to arbitrary vectors; this routine uses the power -method with a random starting vector & {\tt idz\_snorm.f} \\\hline -% -{\tt idz\_enorm} & calculates the Euclidean norm of a vector & -{\tt idz\_snorm.f} \\\hline -% -{\tt idz\_estrank} & estimates the numerical rank of an arbitrary -(generally dense) matrix to a specified precision; this routine is -randomized, and must be initialized with routine {\tt idz\_frmi} & -{\tt idzp\_aid.f} \\\hline -% -{\tt idz\_frm} & transforms a vector into a vector which is -sufficiently scrambled to be subsampled, via a composition of Rokhlin's -random transform, random subselection, and a fast Fourier transform & -{\tt idz\_frm.f} \\\hline -% -{\tt idz\_frmi} & initializes routine {\tt idz\_frm} & {\tt idz\_frm.f} -\\\hline -% -{\tt idz\_getcols} & collects together selected columns of a matrix -specified by a routine for applying the matrix to arbitrary vectors & -{\tt idz\_id.f} \\\hline -% -{\tt idz\_house} & calculates the vector and scalar needed to apply the -Householder transformation reflecting a given vector into its first -entry & {\tt idz\_house.f} \\\hline -% -{\tt idz\_houseapp} & applies a Householder matrix to a vector & -{\tt idz\_house.f} \\\hline -% -{\tt idz\_id2svd} & converts an approximation to a matrix in the form -of an ID into an approximation in the form of an SVD & -{\tt idz\_id2svd.f} \\\hline -% -{\tt idz\_ldiv} & finds the greatest integer less than or equal to a -specified integer, that is divisible by another (larger) specified -integer & {\tt idz\_sfft.f} \\\hline -% -{\tt idz\_permmult} & multiplies together a bunch of permutations & -{\tt idz\_qrpiv.f} \\\hline -% -{\tt idz\_qinqr} & reconstructs the $Q$ matrix in a $QR$ decomposition -from the output of routines {\tt idzp\_qrpiv} or {\tt idzr\_qrpiv} & -{\tt idz\_qrpiv.f} \\\hline -% -{\tt idz\_qrmatmat} & applies to multiple vectors collected together as -a matrix the $Q$ matrix (or its adjoint) in the $QR$ decomposition of -a matrix, as described by the output of routines {\tt idzp\_qrpiv} or -{\tt idzr\_qrpiv}; to apply $Q$ (or its adjoint) to a single vector -without having to provide a work array, use routine {\tt idz\_qrmatvec} -instead & {\tt idz\_qrpiv.f} \\\hline -% -{\tt idz\_qrmatvec} & applies to a single vector the $Q$ matrix (or its -adjoint) in the $QR$ decomposition of a matrix, as described by the -output of routines {\tt idzp\_qrpiv} or {\tt idzr\_qrpiv}; to apply $Q$ -(or its adjoint) to several vectors efficiently, use routine -{\tt idz\_qrmatmat} instead & {\tt idz\_qrpiv.f} \\\hline -% -{\tt idz\_random\_ transf} & applies rapidly a random unitary matrix to -a user-supplied vector & {\tt id\_rtrans.f} \\\hline -% -{\tt idz\_random\_ transf\_init} & \raggedright initializes routines -{\tt idz\_random\_transf} and {\tt idz\_random\_transf\_inverse} & -{\tt id\_rtrans.f} \\\hline -% -{\tt idz\_random\_ transf\_inverse} & applies rapidly the inverse of -the operator applied by routine {\tt idz\_random\_transf} & -{\tt id\_rtrans.f} \\\hline -% -{\tt idz\_reconid} & reconstructs a matrix from its ID & -{\tt idz\_id.f} \\\hline -% -{\tt idz\_reconint} & constructs $P$ in the ID $A = B \, P$, where the -columns of $B$ are a subset of the columns of $A$, and $P$ is the -projection coefficient matrix, given {\tt list}, {\tt krank}, and -{\tt proj} output by routines {\tt idzr\_id}, {\tt idzp\_id}, -{\tt idzr\_aid}, {\tt idzp\_aid}, {\tt idzr\_rid}, or {\tt idzp\_rid} & -{\tt idz\_id.f} \\\hline -% -{\tt idz\_sfft} & rapidly computes a subset of the entries of the -discrete Fourier transform of a vector, composed with permutation -matrices both on input and on output & {\tt idz\_sfft.f} \\\hline -% -{\tt idz\_sffti} & initializes routine {\tt idz\_sfft} & -{\tt idz\_sfft.f} \\\hline -% -{\tt idz\_sfrm} & transforms a vector into a scrambled vector of -specified length, via a composition of Rokhlin's random transform, -random subselection, and a fast Fourier transform & {\tt idz\_frm.f} -\\\hline -% -{\tt idz\_sfrmi} & initializes routine {\tt idz\_sfrm} & -{\tt idz\_frm.f} \\\hline -% -{\tt idz\_snorm} & estimates the spectral norm of a matrix specified by -routines for applying the matrix and its adjoint to arbitrary -vectors; this routine uses the power method with a random starting -vector & {\tt idz\_snorm.f} \\\hline -% -{\tt idzp\_aid} & computes the ID of an arbitrary (generally dense) -matrix, to a specified precision; this routine is randomized, and must -be initialized with routine {\tt idz\_frmi} & {\tt idzp\_aid.f} -\\\hline -% -{\tt idzp\_asvd} & computes the SVD of an arbitrary (generally dense) -matrix, to a specified precision; this routine is randomized, and must -be initialized with routine {\tt idz\_frmi} & {\tt idzp\_asvd.f} -\\\hline -% -{\tt idzp\_id} & computes the ID of an arbitrary (generally dense) -matrix, to a specified precision; this routine is often less efficient -than routine {\tt idzp\_aid} & {\tt idz\_id.f} \\\hline -% -{\tt idzp\_qrpiv} & computes the pivoted $QR$ decomposition of an -arbitrary (generally dense) matrix via Householder transformations, -stopping at a specified precision of the decomposition & -{\tt idz\_qrpiv.f} \\\hline -% -{\tt idzp\_rid} & computes the ID, to a specified precision, of a -matrix specified by a routine for applying its adjoint to arbitrary -vectors; this routine is randomized & {\tt idzp\_rid.f} \\\hline -% -{\tt idzp\_rsvd} & computes the SVD, to a specified precision, of a -matrix specified by routines for applying the matrix and its adjoint -to arbitrary vectors; this routine is randomized & {\tt idzp\_rsvd.f} -\\\hline -% -{\tt idzp\_svd} & computes the SVD of an arbitrary (generally dense) -matrix, to a specified precision; this routine is often less efficient -than routine {\tt idzp\_asvd} & {\tt idz\_svd.f} \\\hline -% -{\tt idzr\_aid} & computes the ID of an arbitrary (generally dense) -matrix, to a specified rank; this routine is randomized, and must be -initialized by routine {\tt idzr\_aidi} & {\tt idzr\_aid.f} \\\hline -% -{\tt idzr\_aidi} & initializes routine {\tt idzr\_aid} & -{\tt idzr\_aid.f} \\\hline -% -{\tt idzr\_asvd} & computes the SVD of an arbitrary (generally dense) -matrix, to a specified rank; this routine is randomized, and must be -initialized with routine {\tt idz\_aidi} & {\tt idzr\_asvd.f} -\\\hline -% -{\tt idzr\_id} & computes the ID of an arbitrary (generally dense) -matrix, to a specified rank; this routine is often less efficient than -routine {\tt idzr\_aid} & {\tt idz\_id.f} \\\hline -% -{\tt idzr\_qrpiv} & computes the pivoted $QR$ decomposition of an -arbitrary (generally dense) matrix via Householder transformations, -stopping at a specified rank of the decomposition & {\tt idz\_qrpiv.f} -\\\hline -% -{\tt idzr\_rid} & computes the ID, to a specified rank, of a matrix -specified by a routine for applying its adjoint to arbitrary vectors; -this routine is randomized & {\tt idzr\_rid.f} \\\hline -% -{\tt idzr\_rsvd} & computes the SVD, to a specified rank, of a matrix -specified by routines for applying the matrix and its adjoint to -arbitrary vectors; this routine is randomized & {\tt idzr\_rsvd.f} -\\\hline -% -{\tt idzr\_svd} & computes the SVD of an arbitrary (generally dense) -matrix, to a specified rank; this routine is often less efficient than -routine {\tt idzr\_asvd} & {\tt idz\_svd.f} \\ -% -\end{supertabular} -\end{center} - - - -\section{Documentation in the source codes} - -Each routine in the source codes includes documentation -in the comments immediately following the declaration -of the subroutine's calling sequence. -This documentation describes the purpose of the routine, -the input and output variables, and the required work arrays (if any). -This documentation also cites relevant references. -Please pay attention to the {\it N.B.}'s; -{\it N.B.} stands for {\it nota bene} (Latin for ``note well'') -and highlights important information about the routines. - - - -\section{Notation and decompositions} -\label{defs} - -This section sets notational conventions employed -in this documentation and the associated software, -and defines both the singular value decomposition (SVD) -and the interpolative decomposition (ID). -For information concerning other mathematical objects -used in the code (such as Householder transformations, -pivoted $QR$ decompositions, and discrete and fast Fourier transforms ---- DFTs and FFTs), see, for example,~\cite{golub-van_loan}. -For detailed descriptions and proofs of the mathematical facts -discussed in the present section, see, for example, -\cite{golub-van_loan} and the references -in~\cite{halko-martinsson-tropp}. - -Throughout this document and the accompanying software distribution, -$\| \x \|$ always denotes the Euclidean norm of the vector $\x$, -and $\| A \|$ always denotes the spectral norm of the matrix $A$. -Subsection~\ref{Euclidean} below defines the Euclidean norm; -Subsection~\ref{spectral} below defines the spectral norm. -We use $A^*$ to denote the adjoint of the matrix $A$. - - -\subsection{Euclidean norm} -\label{Euclidean} - -For any positive integer $n$, and vector $\x$ of length $n$, -the Euclidean ($l^2$) norm $\| \x \|$ is -% -\begin{equation} -\| \x \| = \sqrt{ \sum_{k=1}^n |x_k|^2 }, -\end{equation} -% -where $x_1$,~$x_2$, \dots, $x_{n-1}$,~$x_n$ are the entries of $\x$. - - -\subsection{Spectral norm} -\label{spectral} - -For any positive integers $m$ and $n$, and $m \times n$ matrix $A$, -the spectral ($l^2$ operator) norm $\| A \|$ is -% -\begin{equation} -\| A_{m \times n} \| -= \max \frac{\| A_{m \times n} \, \x_{n \times 1} \|} - {\| \x_{n \times 1} \|}, -\end{equation} -% -where the $\max$ is taken over all $n \times 1$ column vectors $\x$ -such that $\| \x \| \ne 0$. - - -\subsection{Singular value decomposition (SVD)} - -For any positive real number $\epsilon$, -positive integers $k$, $m$, and $n$ with $k \le m$ and $k \le n$, -and any $m \times n$ matrix $A$, -a rank-$k$ approximation to $A$ in the form of an SVD -(to precision $\epsilon$) consists of an $m \times k$ matrix $U$ -whose columns are orthonormal, an $n \times k$ matrix $V$ -whose columns are orthonormal, and a diagonal $k \times k$ matrix -$\Sigma$ with diagonal entries -$\Sigma_{1,1} \ge \Sigma_{2,2} \ge \dots \ge \Sigma_{n-1,n-1} - \ge \Sigma_{n,n} \ge 0$, -such that -% -\begin{equation} -\| A_{m \times n} - U_{m \times k} \, \Sigma_{k \times k} - \, (V^*)_{k \times n} \| \le \epsilon. -\end{equation} -% -The product $U \, \Sigma \, V^*$ is known as an SVD. -The columns of $U$ are known as left singular vectors; -the columns of $V$ are known as right singular vectors. -The diagonal entries of $\Sigma$ are known as singular values. - -When $k = m$ or $k = n$, and $A = U \, \Sigma \, V^*$, -then $U \, \Sigma \, V^*$ is known as the SVD -of $A$; the columns of $U$ are the left singular vectors of $A$, -the columns of $V$ are the right singular vectors of $A$, -and the diagonal entries of $\Sigma$ are the singular values of $A$. -For any positive integer $k$ with $k < m$ and $k < n$, -there exists a rank-$k$ approximation to $A$ in the form of an SVD, -to precision $\sigma_{k+1}$, where $\sigma_{k+1}$ is the $(k+1)^\st$ -greatest singular value of $A$. - - -\subsection{Interpolative decomposition (ID)} - -For any positive real number $\epsilon$, -positive integers $k$, $m$, and $n$ with $k \le m$ and $k \le n$, -and any $m \times n$ matrix $A$, -a rank-$k$ approximation to $A$ in the form of an ID -(to precision $\epsilon$) consists of a $k \times n$ matrix $P$, -and an $m \times k$ matrix $B$ whose columns constitute a subset -of the columns of $A$, such that -% -\begin{enumerate} -\item $\| A_{m \times n} - B_{m \times k} \, P_{k \times n} \| - \le \epsilon$, -\item some subset of the columns of $P$ makes up the $k \times k$ - identity matrix, and -\item every entry of $P$ has an absolute value less than or equal - to a reasonably small positive real number, say 2. -\end{enumerate} -% -The product $B \, P$ is known as an ID. -The matrix $P$ is known as the projection or interpolation matrix -of the ID. Property~1 above approximates each column of $A$ -via a linear combination of the columns of $B$ -(which are themselves columns of $A$), with the coefficients -in the linear combination given by the entries of $P$. - -The interpolative decomposition is ``interpolative'' -due to Property~2 above. The ID is numerically stable -due to Property~3 above. -It follows from Property~2 that the least ($k^\th$ greatest) singular value -of $P$ is at least 1. Combining Properties~2 and~3 yields that -% -\begin{equation} -\| P_{k \times n} \| \le \sqrt{4k(n-k)+1}. -\end{equation} - -When $k = m$ or $k = n$, and $A = B \, P$, -then $B \, P$ is known as the ID of $A$. -For any positive integer $k$ with $k < m$ and $k < n$, -there exists a rank-$k$ approximation to $A$ in the form of an ID, -to precision $\sqrt{k(n-k)+1} \; \sigma_{k+1}$, -where $\sigma_{k+1}$ is the $(k+1)^\st$ greatest singular value of $A$ -(in fact, there exists an ID in which every entry -of the projection matrix $P$ has an absolute value less than or equal -to 1). - - - -\section{Bug reports, feedback, and support} - -Please let us know about errors in the software or in the documentation -via e-mail to {\tt tygert@aya.yale.edu}. -We would also appreciate hearing about particular applications of the codes, -especially in the form of journal articles -e-mailed to {\tt tygert@aya.yale.edu}. -Mathematical and technical support may also be available via e-mail. Enjoy! - - - -\bibliographystyle{siam} -\bibliography{doc} - - -\end{document} diff --git a/scipy/linalg/src/id_dist/doc/supertabular.sty b/scipy/linalg/src/id_dist/doc/supertabular.sty deleted file mode 100644 index ac2638c232d5..000000000000 --- a/scipy/linalg/src/id_dist/doc/supertabular.sty +++ /dev/null @@ -1,483 +0,0 @@ -%% -%% This is file `supertabular.sty', -%% generated with the docstrip utility. -%% -%% The original source files were: -%% -%% supertabular.dtx (with options: `package') -%% Copyright (C) 1989-2004 Johannes Braams. All rights reserved. -%% -%% This file was generated from file(s) of the supertabular package. -%% ----------------------------------------------------------------- -%% -%% It may be distributed and/or modified under the -%% conditions of the LaTeX Project Public License, either version 1.3 -%% of this license or (at your option) any later version. -%% The latest version of this license is in -%% http://www.latex-project.org/lppl.txt -%% and version 1.3 or later is part of all distributions of LaTeX -%% version 2003/12/01 or later. -%% -%% This work has the LPPL maintenance status "maintained". -%% -%% The Current Maintainer of this work is Johannes Braams. -%% -%% This file may only be distributed together with a copy of the -%% supertabular package. You may however distribute the supertabular package -%% without such generated files. -%% -%% The list of all files belonging to the supertabular package is -%% given in the file `manifest.txt. -%% -%% The list of derived (unpacked) files belonging to the distribution -%% and covered by LPPL is defined by the unpacking scripts (with -%% extension .ins) which are part of the distribution. -%% Sourcefile `supertabular.dtx'. -%% -%% Copyright (C) 1988 by Theo Jurriens -%% Copyright (C) 1990-2004 by Johannes Braams texniek at braams.cistron.nl -%% Kersengaarde 33 -%% 2723 BP Zoetermeer NL -%% all rights reserved. -%% -%% -\NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{supertabular} - [2004/02/20 v4.1e the supertabular environment] -\newcount\c@tracingst -\DeclareOption{errorshow}{\c@tracingst\z@} -\DeclareOption{pageshow}{\c@tracingst\tw@} -\DeclareOption{debugshow}{\c@tracingst5\relax} -\ProcessOptions -\newif\if@topcaption \@topcaptiontrue -\def\topcaption{\@topcaptiontrue\tablecaption} -\def\bottomcaption{\@topcaptionfalse\tablecaption} -\long\def\tablecaption{% - \refstepcounter{table}\@dblarg{\@xtablecaption}} -\long\def\@xtablecaption[#1]#2{% - \long\gdef\@process@tablecaption{\ST@caption{table}[#1]{#2}}} -\global\let\@process@tablecaption\relax -\newif\ifST@star -\newif\ifST@mp -\newdimen\ST@wd -\newskip\ST@rightskip -\newskip\ST@leftskip -\newskip\ST@parfillskip -\long\def\ST@caption#1[#2]#3{\par% - \addcontentsline{\csname ext@#1\endcsname}{#1}% - {\protect\numberline{% - \csname the#1\endcsname}{\ignorespaces #2}} - \begingroup - \@parboxrestore - \normalsize - \if@topcaption \vskip -10\p@ \fi - \@makecaption{\csname fnum@#1\endcsname}{\ignorespaces #3}\par - \if@topcaption \vskip 10\p@ \fi - \endgroup} -\newcommand\tablehead[1]{% - \gdef\@tablehead{% - \noalign{% - \global\let\@savcr=\\ - \global\let\\=\org@tabularcr}% - #1% - \noalign{\global\let\\=\@savcr}}} -\tablehead{} -\newcommand\tablefirsthead[1]{\gdef\@table@first@head{#1}} -\newcommand\tabletail[1]{% - \gdef\@tabletail{% - \noalign{% - \global\let\@savcr=\\ - \global\let\\=\org@tabularcr}% - #1% - \noalign{\global\let\\=\@savcr}}} -\tabletail{} -\newcommand\tablelasttail[1]{\gdef\@table@last@tail{#1}} -\newcommand\sttraceon{\c@tracingst5\relax} -\newcommand\sttraceoff{\c@tracingst\z@} -\newcommand\ST@trace[2]{% - \ifnum\c@tracingst>#1\relax - \GenericWarning - {(supertabular)\@spaces\@spaces} - {Package supertabular: #2}% - \fi - } -\newdimen\ST@pageleft -\newcommand*\shrinkheight[1]{% - \noalign{\global\advance\ST@pageleft-#1\relax}} -\newcommand*\setSTheight[1]{% - \noalign{\global\ST@pageleft=#1\relax}} -\newdimen\ST@headht -\newdimen\ST@tailht -\newdimen\ST@pagesofar -\newdimen\ST@pboxht -\newdimen\ST@lineht -\newdimen\ST@stretchht -\newdimen\ST@prevht -\newdimen\ST@toadd -\newdimen\ST@dimen -\newbox\ST@pbox -\def\ST@tabularcr{% - {\ifnum0=`}\fi - \@ifstar{\ST@xtabularcr}{\ST@xtabularcr}} -\def\ST@xtabularcr{% - \@ifnextchar[%] - {\ST@argtabularcr}% - {\ifnum0=`{\fi}\cr\ST@cr}} -\def\ST@argtabularcr[#1]{% - \ifnum0=`{\fi}% - \ifdim #1>\z@ - \unskip\ST@xargarraycr{#1} - \else - \ST@yargarraycr{#1}% - \fi} -\def\ST@xargarraycr#1{% - \@tempdima #1\advance\@tempdima \dp \@arstrutbox - \vrule \@height\z@ \@depth\@tempdima \@width\z@ \cr - \noalign{\global\ST@toadd=#1}\ST@cr} -\def\ST@yargarraycr#1{% - \cr\noalign{\vskip #1\global\ST@toadd=#1}\ST@cr} -\def\ST@startpbox#1{% - \setbox\ST@pbox\vtop\bgroup\hsize#1\@arrayparboxrestore} -\def\ST@astartpbox#1{% - \bgroup\hsize#1% - \setbox\ST@pbox\vtop\bgroup\hsize#1\@arrayparboxrestore} -\def\ST@endpbox{% - \@finalstrut\@arstrutbox\par\egroup - \ST@dimen=\ht\ST@pbox - \advance\ST@dimen by \dp\ST@pbox - \ifnum\ST@pboxht<\ST@dimen - \global\ST@pboxht=\ST@dimen - \fi - \ST@dimen=\z@ - \box\ST@pbox\hfil} -\def\ST@aendpbox{% - \@finalstrut\@arstrutbox\par\egroup - \ST@dimen=\ht\ST@pbox - \advance\ST@dimen by \dp\ST@pbox - \ifnum\ST@pboxht<\ST@dimen - \global\ST@pboxht=\ST@dimen - \fi - \ST@dimen=\z@ - \unvbox\ST@pbox\egroup\hfil} -\def\estimate@lineht{% - \ST@lineht=\arraystretch \baslineskp - \global\advance\ST@lineht by 1\p@ - \ST@stretchht\ST@lineht\advance\ST@stretchht-\baslineskp - \ifdim\ST@stretchht<\z@\ST@stretchht\z@\fi - \ST@trace\tw@{Average line height: \the\ST@lineht}% - \ST@trace\tw@{Stretched line height: \the\ST@stretchht}% - } -\def\@calfirstpageht{% - \ST@trace\tw@{Calculating height of tabular on first page}% - \global\ST@pagesofar\pagetotal - \global\ST@pageleft\@colroom - \ST@trace\tw@{Height of text = \the\pagetotal; \MessageBreak - Height of page = \the\ST@pageleft}% - \if@twocolumn - \ST@trace\tw@{two column mode}% - \if@firstcolumn - \ST@trace\tw@{First column}% - \ifnum\ST@pagesofar > \ST@pageleft - \global\ST@pageleft=2\ST@pageleft - \ifnum\ST@pagesofar > \ST@pageleft - \newpage\@calnextpageht - \ST@trace\tw@{starting new page}% - \else - \ST@trace\tw@{Second column}% - \global\advance\ST@pageleft -\ST@pagesofar - \global\advance\ST@pageleft -\@colroom - \fi - \else - \global\advance\ST@pageleft by -\ST@pagesofar - \global\ST@pagesofar\z@ - \fi - \else - \ST@trace\tw@{Second column} - \ifnum\ST@pagesofar > \ST@pageleft - \ST@trace\tw@{starting new page}% - \newpage\@calnextpageht - \else - \global\advance\ST@pageleft by -\ST@pagesofar - \global\ST@pagesofar\z@ - \fi - \fi - \else - \ST@trace\tw@{one column mode}% - \ifnum\ST@pagesofar > \ST@pageleft - \ST@trace\tw@{starting new page}% - \newpage\@calnextpageht - \else - \global\advance\ST@pageleft by -\ST@pagesofar - \global\ST@pagesofar\z@ - \fi - \fi - \ST@trace\tw@{Available height: \the\ST@pageleft}% - \ifx\@@tablehead\@empty - \ST@headht=\z@ - \else - \setbox\@tempboxa=\vbox{\@arrayparboxrestore - \ST@restore - \expandafter\tabular\expandafter{\ST@tableformat}% - \@@tablehead\endtabular}% - \ST@headht=\ht\@tempboxa\advance\ST@headht\dp\@tempboxa - \fi - \ST@trace\tw@{Height of head: \the\ST@headht}% - \ifx\@tabletail\@empty - \ST@tailht=\z@ - \else - \setbox\@tempboxa=\vbox{\@arrayparboxrestore - \ST@restore - \expandafter\tabular\expandafter{\ST@tableformat} - \@tabletail\endtabular} - \ST@tailht=\ht\@tempboxa\advance\ST@tailht\dp\@tempboxa - \fi - \advance\ST@tailht by \ST@lineht - \ST@trace\tw@{Height of tail: \the\ST@tailht}% - \ST@trace\tw@{Maximum height of tabular: \the\ST@pageleft}% - \@tempdima\ST@headht - \advance\@tempdima\ST@lineht - \advance\@tempdima\ST@tailht - \ST@trace\tw@{Minimum height of tabular: \the\@tempdima}% - \ifnum\@tempdima>\ST@pageleft - \ST@trace\tw@{starting new page}% - \newpage\@calnextpageht - \fi -} -\def\@calnextpageht{% - \ST@trace\tw@{Calculating height of tabular on next page}% - \global\ST@pageleft\@colroom - \global\ST@pagesofar=\z@ - \ST@trace\tw@{Maximum height of tabular: \the\ST@pageleft}% - } -\def\x@supertabular{% - \let\org@tabular\tabular - \let\tabular\inner@tabular - \expandafter\let - \csname org@tabular*\expandafter\endcsname - \csname tabular*\endcsname - \expandafter\let\csname tabular*\expandafter\endcsname - \csname inner@tabular*\endcsname - \if@topcaption \@process@tablecaption \fi - \global\let\@oldcr=\\ - \def\baslineskp{\baselineskip}% - \ifx\undefined\@classix - \let\org@tabularcr\@tabularcr - \let\@tabularcr\ST@tabularcr - \let\org@startpbox=\@startpbox - \let\org@endpbox=\@endpbox - \let\@@startpbox=\ST@startpbox - \let\@@endpbox=\ST@endpbox - \else - \let\org@tabularcr\@arraycr - \let\@arraycr\ST@tabularcr - \let\org@startpbox=\@startpbox - \let\org@endpbox=\@endpbox - \let\@startpbox=\ST@astartpbox - \let\@endpbox=\ST@aendpbox - \fi - \ifx\@table@first@head\undefined - \let\@@tablehead=\@tablehead - \else - \let\@@tablehead=\@table@first@head - \fi - \let\ST@skippage\ST@skipfirstpart - \estimate@lineht - \@calfirstpageht - \noindent - } -\def\supertabular{% - \@ifnextchar[{\@supertabular}%] - {\@supertabular[]}} -\def\@supertabular[#1]#2{% - \def\ST@tableformat{#2}% - \ST@trace\tw@{Starting a new supertabular}% - \global\ST@starfalse - \global\ST@mpfalse - \x@supertabular - \expandafter\org@tabular\expandafter{\ST@tableformat}% - \@@tablehead} -\@namedef{supertabular*}#1{% - \@ifnextchar[{\@nameuse{@supertabular*}{#1}}% - {\@nameuse{@supertabular*}{#1}[]}%] - } -\@namedef{@supertabular*}#1[#2]#3{% - \ST@trace\tw@{Starting a new supertabular*}% - \def\ST@tableformat{#3}% - \ST@wd=#1\relax - \global\ST@startrue - \global\ST@mpfalse - \x@supertabular - \expandafter\csname org@tabular*\expandafter\endcsname - \expandafter{\expandafter\ST@wd\expandafter}% - \expandafter{\ST@tableformat}% - \@@tablehead}% -\def\mpsupertabular{% - \@ifnextchar[{\@mpsupertabular}%] - {\@mpsupertabular[]}} -\def\@mpsupertabular[#1]#2{% - \def\ST@tableformat{#2}% - \ST@trace\tw@{Starting a new mpsupertabular}% - \global\ST@starfalse - \global\ST@mptrue - \ST@rightskip \rightskip - \ST@leftskip \leftskip - \ST@parfillskip \parfillskip - \x@supertabular - \minipage{\columnwidth}% - \parfillskip\ST@parfillskip - \rightskip \ST@rightskip - \leftskip \ST@leftskip - \noindent\expandafter\org@tabular\expandafter{\ST@tableformat}% - \@@tablehead} -\@namedef{mpsupertabular*}#1{% - \@ifnextchar[{\@nameuse{@mpsupertabular*}{#1}}% - {\@nameuse{@mpsupertabular*}{#1}[]}%] - } -\@namedef{@mpsupertabular*}#1[#2]#3{% - \ST@trace\tw@{Starting a new mpsupertabular*}% - \def\ST@tableformat{#3}% - \ST@wd=#1\relax - \global\ST@startrue - \global\ST@mptrue - \ST@rightskip \rightskip - \ST@leftskip \leftskip - \ST@parfillskip \parfillskip - \x@supertabular - \minipage{\columnwidth}% - \parfillskip\ST@parfillskip - \rightskip \ST@rightskip - \leftskip \ST@leftskip - \noindent\expandafter\csname org@tabular*\expandafter\endcsname - \expandafter{\expandafter\ST@wd\expandafter}% - \expandafter{\ST@tableformat}% - \@@tablehead}% -\def\endsupertabular{% - \ifx\@table@last@tail\undefined - \@tabletail - \else - \@table@last@tail - \fi - \csname endtabular\ifST@star*\fi\endcsname - \ST@restore - \if@topcaption - \else - \@process@tablecaption - \@topcaptiontrue - \fi - \global\let\\\@oldcr - \global\let\@process@tablecaption\relax - \ST@trace\tw@{Ended a supertabular\ifST@star*\fi}% - } -\expandafter\let\csname endsupertabular*\endcsname\endsupertabular -\def\endmpsupertabular{% - \ifx\@table@last@tail\undefined - \@tabletail - \else - \@table@last@tail - \fi - \csname endtabular\ifST@star*\fi\endcsname - \endminipage - \ST@restore - \if@topcaption - \else - \@process@tablecaption - \@topcaptiontrue - \fi - \global\let\\\@oldcr - \global\let\@process@tablecaption\relax - \ST@trace\tw@{Ended a mpsupertabular\ifST@star*\fi}% - } -\expandafter\let\csname endmpsupertabular*\endcsname\endmpsupertabular -\def\ST@restore{% - \ifx\undefined\@classix - \let\@tabularcr\org@tabularcr - \else - \let\@arraycr\org@tabularcr - \fi - \let\@startpbox\org@startpbox - \let\@endpbox\org@endpbox - } -\def\inner@tabular{% - \ST@restore - \let\\\@oldcr - \noindent - \org@tabular} -\@namedef{inner@tabular*}{% - \ST@restore - \let\\\@oldcr - \noindent - \csname org@tabular*\endcsname} -\def\ST@cr{% - \noalign{% - \ifnum\ST@pboxht<\ST@lineht - \global\advance\ST@pageleft -\ST@lineht - \global\ST@prevht\ST@lineht - \else - \ST@trace\thr@@{Added par box with height \the\ST@pboxht}% - \global\advance\ST@pageleft -\ST@pboxht - \global\advance\ST@pageleft -0.1\ST@pboxht - \global\advance\ST@pageleft -\ST@stretchht - \global\ST@prevht\ST@pboxht - \global\ST@pboxht\z@ - \fi - \global\advance\ST@pageleft -\ST@toadd - \global\ST@toadd=\z@ - \ST@trace\thr@@{Space left for tabular: \the\ST@pageleft}% - } - \noalign{\global\let\ST@next\@empty}% - \ifnum\ST@pageleft<\z@ - \ST@skippage - \else - \noalign{\global\@tempdima\ST@tailht - \global\advance\@tempdima\ST@prevht - \ifST@mp - \ifvoid\@mpfootins\else - \global\advance\@tempdima\ht\@mpfootins - \global\advance\@tempdima 3pt - \fi - \fi} - \ifnum\ST@pageleft<\@tempdima - \ST@newpage - \fi - \fi - \ST@next} -\def\ST@skipfirstpart{% - \noalign{% - \ST@trace\tw@{Tabular too high, moving to next page}% - \global\advance\ST@pageleft\pagetotal - \global\ST@pagesofar\z@ - \newpage - \global\let\ST@skippage\ST@newpage - }} -\def\ST@newpage{% - \noalign{\ST@trace\tw@{Starting new page, writing tail}}% - \@tabletail - \ifST@star - \csname endtabular*\endcsname - \else - \endtabular - \fi - \ifST@mp - \endminipage - \fi - \global\let\ST@skippage\ST@newpage - \newpage\@calnextpageht - \let\ST@next\@tablehead - \ST@trace\tw@{writing head}% - \ifST@mp - \noindent\minipage{\columnwidth}% - \parfillskip\ST@parfillskip - \rightskip \ST@rightskip - \leftskip \ST@leftskip - \fi - \noindent - \ifST@star - \expandafter\csname org@tabular*\expandafter\endcsname - \expandafter{\expandafter\ST@wd\expandafter}% - \expandafter{\ST@tableformat}% - \else - \expandafter\org@tabular\expandafter{\ST@tableformat}% - \fi} -\endinput -%% -%% End of file `supertabular.sty'. diff --git a/scipy/linalg/src/id_dist/src/dfft.f b/scipy/linalg/src/id_dist/src/dfft.f deleted file mode 100644 index b1b1b3206380..000000000000 --- a/scipy/linalg/src/id_dist/src/dfft.f +++ /dev/null @@ -1,3014 +0,0 @@ -C -C FFTPACK -C -C * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -C -C VERSION 4 APRIL 1985 -C -C A PACKAGE OF FORTRAN SUBPROGRAMS FOR THE FAST FOURIER -C TRANSFORM OF PERIODIC AND OTHER SYMMETRIC SEQUENCES -C -C BY -C -C PAUL N SWARZTRAUBER -C -C NATIONAL CENTER FOR ATMOSPHERIC RESEARCH BOULDER,COLORADO 80307 -C -C WHICH IS SPONSORED BY THE NATIONAL SCIENCE FOUNDATION -C -C * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -C -C -C THIS PACKAGE CONSISTS OF PROGRAMS WHICH PERFORM FAST FOURIER -C TRANSFORMS FOR BOTH COMPLEX AND REAL PERIODIC SEQUENCES AND -C CERTAIN OTHER SYMMETRIC SEQUENCES THAT ARE LISTED BELOW. -C -C 1. DFFTI INITIALIZE DFFTF AND DFFTB -C 2. DFFTF FORWARD TRANSFORM OF A REAL PERIODIC SEQUENCE -C 3. DFFTB BACKWARD TRANSFORM OF A REAL COEFFICIENT ARRAY -C -C 4. DZFFTI INITIALIZE DZFFTF AND DZFFTB -C 5. DZFFTF A SIMPLIFIED REAL PERIODIC FORWARD TRANSFORM -C 6. DZFFTB A SIMPLIFIED REAL PERIODIC BACKWARD TRANSFORM -C -C 7. DSINTI INITIALIZE DSINT -C 8. DSINT SINE TRANSFORM OF A REAL ODD SEQUENCE -C -C 9. DCOSTI INITIALIZE DCOST -C 10. DCOST COSINE TRANSFORM OF A REAL EVEN SEQUENCE -C -C 11. DSINQI INITIALIZE DSINQF AND DSINQB -C 12. DSINQF FORWARD SINE TRANSFORM WITH ODD WAVE NUMBERS -C 13. DSINQB UNNORMALIZED INVERSE OF DSINQF -C -C 14. DCOSQI INITIALIZE DCOSQF AND DCOSQB -C 15. DCOSQF FORWARD COSINE TRANSFORM WITH ODD WAVE NUMBERS -C 16. DCOSQB UNNORMALIZED INVERSE OF DCOSQF -C -C 17. ZFFTI INITIALIZE ZFFTF AND ZFFTB -C 18. ZFFTF FORWARD TRANSFORM OF A COMPLEX PERIODIC SEQUENCE -C 19. ZFFTB UNNORMALIZED INVERSE OF ZFFTF -C -C -C ****************************************************************** -C -C SUBROUTINE DFFTI(N,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DFFTI INITIALIZES THE ARRAY WSAVE WHICH IS USED IN -C BOTH DFFTF AND DFFTB. THE PRIME FACTORIZATION OF N TOGETHER WITH -C A TABULATION OF THE TRIGONOMETRIC FUNCTIONS ARE COMPUTED AND -C STORED IN WSAVE. -C -C INPUT PARAMETER -C -C N THE LENGTH OF THE SEQUENCE TO BE TRANSFORMED. -C -C OUTPUT PARAMETER -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 2*N+15. -C THE SAME WORK ARRAY CAN BE USED FOR BOTH DFFTF AND DFFTB -C AS LONG AS N REMAINS UNCHANGED. DIFFERENT WSAVE ARRAYS -C ARE REQUIRED FOR DIFFERENT VALUES OF N. THE CONTENTS OF -C WSAVE MUST NOT BE CHANGED BETWEEN CALLS OF DFFTF OR DFFTB. -C -C ****************************************************************** -C -C SUBROUTINE DFFTF(N,R,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DFFTF COMPUTES THE FOURIER COEFFICIENTS OF A REAL -C PERODIC SEQUENCE (FOURIER ANALYSIS). THE TRANSFORM IS DEFINED -C BELOW AT OUTPUT PARAMETER R. -C -C INPUT PARAMETERS -C -C N THE LENGTH OF THE ARRAY R TO BE TRANSFORMED. THE METHOD -C IS MOST EFFICIENT WHEN N IS A PRODUCT OF SMALL PRIMES. -C N MAY CHANGE SO LONG AS DIFFERENT WORK ARRAYS ARE PROVIDED -C -C R A REAL ARRAY OF LENGTH N WHICH CONTAINS THE SEQUENCE -C TO BE TRANSFORMED -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 2*N+15. -C IN THE PROGRAM THAT CALLS DFFTF. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE DFFTI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C THE SAME WSAVE ARRAY CAN BE USED BY DFFTF AND DFFTB. -C -C -C OUTPUT PARAMETERS -C -C R R(1) = THE SUM FROM I=1 TO I=N OF R(I) -C -C IF N IS EVEN SET L =N/2 , IF N IS ODD SET L = (N+1)/2 -C -C THEN FOR K = 2,...,L -C -C R(2*K-2) = THE SUM FROM I = 1 TO I = N OF -C -C R(I)*COS((K-1)*(I-1)*2*PI/N) -C -C R(2*K-1) = THE SUM FROM I = 1 TO I = N OF -C -C -R(I)*SIN((K-1)*(I-1)*2*PI/N) -C -C IF N IS EVEN -C -C R(N) = THE SUM FROM I = 1 TO I = N OF -C -C (-1)**(I-1)*R(I) -C -C ***** NOTE -C THIS TRANSFORM IS UNNORMALIZED SINCE A CALL OF DFFTF -C FOLLOWED BY A CALL OF DFFTB WILL MULTIPLY THE INPUT -C SEQUENCE BY N. -C -C WSAVE CONTAINS RESULTS WHICH MUST NOT BE DESTROYED BETWEEN -C CALLS OF DFFTF OR DFFTB. -C -C -C ****************************************************************** -C -C SUBROUTINE DFFTB(N,R,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DFFTB COMPUTES THE REAL PERODIC SEQUENCE FROM ITS -C FOURIER COEFFICIENTS (FOURIER SYNTHESIS). THE TRANSFORM IS DEFINED -C BELOW AT OUTPUT PARAMETER R. -C -C INPUT PARAMETERS -C -C N THE LENGTH OF THE ARRAY R TO BE TRANSFORMED. THE METHOD -C IS MOST EFFICIENT WHEN N IS A PRODUCT OF SMALL PRIMES. -C N MAY CHANGE SO LONG AS DIFFERENT WORK ARRAYS ARE PROVIDED -C -C R A REAL ARRAY OF LENGTH N WHICH CONTAINS THE SEQUENCE -C TO BE TRANSFORMED -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 2*N+15. -C IN THE PROGRAM THAT CALLS DFFTB. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE DFFTI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C THE SAME WSAVE ARRAY CAN BE USED BY DFFTF AND DFFTB. -C -C -C OUTPUT PARAMETERS -C -C R FOR N EVEN AND FOR I = 1,...,N -C -C R(I) = R(1)+(-1)**(I-1)*R(N) -C -C PLUS THE SUM FROM K=2 TO K=N/2 OF -C -C 2.*R(2*K-2)*COS((K-1)*(I-1)*2*PI/N) -C -C -2.*R(2*K-1)*SIN((K-1)*(I-1)*2*PI/N) -C -C FOR N ODD AND FOR I = 1,...,N -C -C R(I) = R(1) PLUS THE SUM FROM K=2 TO K=(N+1)/2 OF -C -C 2.*R(2*K-2)*COS((K-1)*(I-1)*2*PI/N) -C -C -2.*R(2*K-1)*SIN((K-1)*(I-1)*2*PI/N) -C -C ***** NOTE -C THIS TRANSFORM IS UNNORMALIZED SINCE A CALL OF DFFTF -C FOLLOWED BY A CALL OF DFFTB WILL MULTIPLY THE INPUT -C SEQUENCE BY N. -C -C WSAVE CONTAINS RESULTS WHICH MUST NOT BE DESTROYED BETWEEN -C CALLS OF DFFTB OR DFFTF. -C -C -C ****************************************************************** -C -C SUBROUTINE DZFFTI(N,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DZFFTI INITIALIZES THE ARRAY WSAVE WHICH IS USED IN -C BOTH DZFFTF AND DZFFTB. THE PRIME FACTORIZATION OF N TOGETHER WITH -C A TABULATION OF THE TRIGONOMETRIC FUNCTIONS ARE COMPUTED AND -C STORED IN WSAVE. -C -C INPUT PARAMETER -C -C N THE LENGTH OF THE SEQUENCE TO BE TRANSFORMED. -C -C OUTPUT PARAMETER -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 3*N+15. -C THE SAME WORK ARRAY CAN BE USED FOR BOTH DZFFTF AND DZFFTB -C AS LONG AS N REMAINS UNCHANGED. DIFFERENT WSAVE ARRAYS -C ARE REQUIRED FOR DIFFERENT VALUES OF N. -C -C -C ****************************************************************** -C -C SUBROUTINE DZFFTF(N,R,AZERO,A,B,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DZFFTF COMPUTES THE FOURIER COEFFICIENTS OF A REAL -C PERODIC SEQUENCE (FOURIER ANALYSIS). THE TRANSFORM IS DEFINED -C BELOW AT OUTPUT PARAMETERS AZERO,A AND B. DZFFTF IS A SIMPLIFIED -C BUT SLOWER VERSION OF DFFTF. -C -C INPUT PARAMETERS -C -C N THE LENGTH OF THE ARRAY R TO BE TRANSFORMED. THE METHOD -C IS MUST EFFICIENT WHEN N IS THE PRODUCT OF SMALL PRIMES. -C -C R A REAL ARRAY OF LENGTH N WHICH CONTAINS THE SEQUENCE -C TO BE TRANSFORMED. R IS NOT DESTROYED. -C -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 3*N+15. -C IN THE PROGRAM THAT CALLS DZFFTF. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE DZFFTI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C THE SAME WSAVE ARRAY CAN BE USED BY DZFFTF AND DZFFTB. -C -C OUTPUT PARAMETERS -C -C AZERO THE SUM FROM I=1 TO I=N OF R(I)/N -C -C A,B FOR N EVEN B(N/2)=0. AND A(N/2) IS THE SUM FROM I=1 TO -C I=N OF (-1)**(I-1)*R(I)/N -C -C FOR N EVEN DEFINE KMAX=N/2-1 -C FOR N ODD DEFINE KMAX=(N-1)/2 -C -C THEN FOR K=1,...,KMAX -C -C A(K) EQUALS THE SUM FROM I=1 TO I=N OF -C -C 2./N*R(I)*COS(K*(I-1)*2*PI/N) -C -C B(K) EQUALS THE SUM FROM I=1 TO I=N OF -C -C 2./N*R(I)*SIN(K*(I-1)*2*PI/N) -C -C -C ****************************************************************** -C -C SUBROUTINE DZFFTB(N,R,AZERO,A,B,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DZFFTB COMPUTES A REAL PERODIC SEQUENCE FROM ITS -C FOURIER COEFFICIENTS (FOURIER SYNTHESIS). THE TRANSFORM IS -C DEFINED BELOW AT OUTPUT PARAMETER R. DZFFTB IS A SIMPLIFIED -C BUT SLOWER VERSION OF DFFTB. -C -C INPUT PARAMETERS -C -C N THE LENGTH OF THE OUTPUT ARRAY R. THE METHOD IS MOST -C EFFICIENT WHEN N IS THE PRODUCT OF SMALL PRIMES. -C -C AZERO THE CONSTANT FOURIER COEFFICIENT -C -C A,B ARRAYS WHICH CONTAIN THE REMAINING FOURIER COEFFICIENTS -C THESE ARRAYS ARE NOT DESTROYED. -C -C THE LENGTH OF THESE ARRAYS DEPENDS ON WHETHER N IS EVEN OR -C ODD. -C -C IF N IS EVEN N/2 LOCATIONS ARE REQUIRED -C IF N IS ODD (N-1)/2 LOCATIONS ARE REQUIRED -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 3*N+15. -C IN THE PROGRAM THAT CALLS DZFFTB. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE DZFFTI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C THE SAME WSAVE ARRAY CAN BE USED BY DZFFTF AND DZFFTB. -C -C -C OUTPUT PARAMETERS -C -C R IF N IS EVEN DEFINE KMAX=N/2 -C IF N IS ODD DEFINE KMAX=(N-1)/2 -C -C THEN FOR I=1,...,N -C -C R(I)=AZERO PLUS THE SUM FROM K=1 TO K=KMAX OF -C -C A(K)*COS(K*(I-1)*2*PI/N)+B(K)*SIN(K*(I-1)*2*PI/N) -C -C ********************* COMPLEX NOTATION ************************** -C -C FOR J=1,...,N -C -C R(J) EQUALS THE SUM FROM K=-KMAX TO K=KMAX OF -C -C C(K)*EXP(I*K*(J-1)*2*PI/N) -C -C WHERE -C -C C(K) = .5*CMPLX(A(K),-B(K)) FOR K=1,...,KMAX -C -C C(-K) = CONJG(C(K)) -C -C C(0) = AZERO -C -C AND I=SQRT(-1) -C -C *************** AMPLITUDE - PHASE NOTATION *********************** -C -C FOR I=1,...,N -C -C R(I) EQUALS AZERO PLUS THE SUM FROM K=1 TO K=KMAX OF -C -C ALPHA(K)*COS(K*(I-1)*2*PI/N+BETA(K)) -C -C WHERE -C -C ALPHA(K) = SQRT(A(K)*A(K)+B(K)*B(K)) -C -C COS(BETA(K))=A(K)/ALPHA(K) -C -C SIN(BETA(K))=-B(K)/ALPHA(K) -C -C ****************************************************************** -C -C SUBROUTINE DSINTI(N,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DSINTI INITIALIZES THE ARRAY WSAVE WHICH IS USED IN -C SUBROUTINE DSINT. THE PRIME FACTORIZATION OF N TOGETHER WITH -C A TABULATION OF THE TRIGONOMETRIC FUNCTIONS ARE COMPUTED AND -C STORED IN WSAVE. -C -C INPUT PARAMETER -C -C N THE LENGTH OF THE SEQUENCE TO BE TRANSFORMED. THE METHOD -C IS MOST EFFICIENT WHEN N+1 IS A PRODUCT OF SMALL PRIMES. -C -C OUTPUT PARAMETER -C -C WSAVE A WORK ARRAY WITH AT LEAST INT(2.5*N+15) LOCATIONS. -C DIFFERENT WSAVE ARRAYS ARE REQUIRED FOR DIFFERENT VALUES -C OF N. THE CONTENTS OF WSAVE MUST NOT BE CHANGED BETWEEN -C CALLS OF DSINT. -C -C ****************************************************************** -C -C SUBROUTINE DSINT(N,X,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DSINT COMPUTES THE DISCRETE FOURIER SINE TRANSFORM -C OF AN ODD SEQUENCE X(I). THE TRANSFORM IS DEFINED BELOW AT -C OUTPUT PARAMETER X. -C -C DSINT IS THE UNNORMALIZED INVERSE OF ITSELF SINCE A CALL OF DSINT -C FOLLOWED BY ANOTHER CALL OF DSINT WILL MULTIPLY THE INPUT SEQUENCE -C X BY 2*(N+1). -C -C THE ARRAY WSAVE WHICH IS USED BY SUBROUTINE DSINT MUST BE -C INITIALIZED BY CALLING SUBROUTINE DSINTI(N,WSAVE). -C -C INPUT PARAMETERS -C -C N THE LENGTH OF THE SEQUENCE TO BE TRANSFORMED. THE METHOD -C IS MOST EFFICIENT WHEN N+1 IS THE PRODUCT OF SMALL PRIMES. -C -C X AN ARRAY WHICH CONTAINS THE SEQUENCE TO BE TRANSFORMED -C -C -C WSAVE A WORK ARRAY WITH DIMENSION AT LEAST INT(2.5*N+15) -C IN THE PROGRAM THAT CALLS DSINT. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE DSINTI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C -C OUTPUT PARAMETERS -C -C X FOR I=1,...,N -C -C X(I)= THE SUM FROM K=1 TO K=N -C -C 2*X(K)*SIN(K*I*PI/(N+1)) -C -C A CALL OF DSINT FOLLOWED BY ANOTHER CALL OF -C DSINT WILL MULTIPLY THE SEQUENCE X BY 2*(N+1). -C HENCE DSINT IS THE UNNORMALIZED INVERSE -C OF ITSELF. -C -C WSAVE CONTAINS INITIALIZATION CALCULATIONS WHICH MUST NOT BE -C DESTROYED BETWEEN CALLS OF DSINT. -C -C ****************************************************************** -C -C SUBROUTINE DCOSTI(N,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DCOSTI INITIALIZES THE ARRAY WSAVE WHICH IS USED IN -C SUBROUTINE DCOST. THE PRIME FACTORIZATION OF N TOGETHER WITH -C A TABULATION OF THE TRIGONOMETRIC FUNCTIONS ARE COMPUTED AND -C STORED IN WSAVE. -C -C INPUT PARAMETER -C -C N THE LENGTH OF THE SEQUENCE TO BE TRANSFORMED. THE METHOD -C IS MOST EFFICIENT WHEN N-1 IS A PRODUCT OF SMALL PRIMES. -C -C OUTPUT PARAMETER -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 3*N+15. -C DIFFERENT WSAVE ARRAYS ARE REQUIRED FOR DIFFERENT VALUES -C OF N. THE CONTENTS OF WSAVE MUST NOT BE CHANGED BETWEEN -C CALLS OF DCOST. -C -C ****************************************************************** -C -C SUBROUTINE DCOST(N,X,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DCOST COMPUTES THE DISCRETE FOURIER COSINE TRANSFORM -C OF AN EVEN SEQUENCE X(I). THE TRANSFORM IS DEFINED BELOW AT OUTPUT -C PARAMETER X. -C -C DCOST IS THE UNNORMALIZED INVERSE OF ITSELF SINCE A CALL OF DCOST -C FOLLOWED BY ANOTHER CALL OF DCOST WILL MULTIPLY THE INPUT SEQUENCE -C X BY 2*(N-1). THE TRANSFORM IS DEFINED BELOW AT OUTPUT PARAMETER X -C -C THE ARRAY WSAVE WHICH IS USED BY SUBROUTINE DCOST MUST BE -C INITIALIZED BY CALLING SUBROUTINE DCOSTI(N,WSAVE). -C -C INPUT PARAMETERS -C -C N THE LENGTH OF THE SEQUENCE X. N MUST BE GREATER THAN 1. -C THE METHOD IS MOST EFFICIENT WHEN N-1 IS A PRODUCT OF -C SMALL PRIMES. -C -C X AN ARRAY WHICH CONTAINS THE SEQUENCE TO BE TRANSFORMED -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 3*N+15 -C IN THE PROGRAM THAT CALLS DCOST. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE DCOSTI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C -C OUTPUT PARAMETERS -C -C X FOR I=1,...,N -C -C X(I) = X(1)+(-1)**(I-1)*X(N) -C -C + THE SUM FROM K=2 TO K=N-1 -C -C 2*X(K)*COS((K-1)*(I-1)*PI/(N-1)) -C -C A CALL OF DCOST FOLLOWED BY ANOTHER CALL OF -C DCOST WILL MULTIPLY THE SEQUENCE X BY 2*(N-1) -C HENCE DCOST IS THE UNNORMALIZED INVERSE -C OF ITSELF. -C -C WSAVE CONTAINS INITIALIZATION CALCULATIONS WHICH MUST NOT BE -C DESTROYED BETWEEN CALLS OF DCOST. -C -C ****************************************************************** -C -C SUBROUTINE DSINQI(N,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DSINQI INITIALIZES THE ARRAY WSAVE WHICH IS USED IN -C BOTH DSINQF AND DSINQB. THE PRIME FACTORIZATION OF N TOGETHER WITH -C A TABULATION OF THE TRIGONOMETRIC FUNCTIONS ARE COMPUTED AND -C STORED IN WSAVE. -C -C INPUT PARAMETER -C -C N THE LENGTH OF THE SEQUENCE TO BE TRANSFORMED. THE METHOD -C IS MOST EFFICIENT WHEN N IS A PRODUCT OF SMALL PRIMES. -C -C OUTPUT PARAMETER -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 3*N+15. -C THE SAME WORK ARRAY CAN BE USED FOR BOTH DSINQF AND DSINQB -C AS LONG AS N REMAINS UNCHANGED. DIFFERENT WSAVE ARRAYS -C ARE REQUIRED FOR DIFFERENT VALUES OF N. THE CONTENTS OF -C WSAVE MUST NOT BE CHANGED BETWEEN CALLS OF DSINQF OR DSINQB. -C -C ****************************************************************** -C -C SUBROUTINE DSINQF(N,X,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DSINQF COMPUTES THE FAST FOURIER TRANSFORM OF QUARTER -C WAVE DATA. THAT IS , DSINQF COMPUTES THE COEFFICIENTS IN A SINE -C SERIES REPRESENTATION WITH ONLY ODD WAVE NUMBERS. THE TRANSFORM -C IS DEFINED BELOW AT OUTPUT PARAMETER X. -C -C DSINQB IS THE UNNORMALIZED INVERSE OF DSINQF SINCE A CALL OF DSINQF -C FOLLOWED BY A CALL OF DSINQB WILL MULTIPLY THE INPUT SEQUENCE X -C BY 4*N. -C -C THE ARRAY WSAVE WHICH IS USED BY SUBROUTINE DSINQF MUST BE -C INITIALIZED BY CALLING SUBROUTINE DSINQI(N,WSAVE). -C -C -C INPUT PARAMETERS -C -C N THE LENGTH OF THE ARRAY X TO BE TRANSFORMED. THE METHOD -C IS MOST EFFICIENT WHEN N IS A PRODUCT OF SMALL PRIMES. -C -C X AN ARRAY WHICH CONTAINS THE SEQUENCE TO BE TRANSFORMED -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 3*N+15. -C IN THE PROGRAM THAT CALLS DSINQF. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE DSINQI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C -C OUTPUT PARAMETERS -C -C X FOR I=1,...,N -C -C X(I) = (-1)**(I-1)*X(N) -C -C + THE SUM FROM K=1 TO K=N-1 OF -C -C 2*X(K)*SIN((2*I-1)*K*PI/(2*N)) -C -C A CALL OF DSINQF FOLLOWED BY A CALL OF -C DSINQB WILL MULTIPLY THE SEQUENCE X BY 4*N. -C THEREFORE DSINQB IS THE UNNORMALIZED INVERSE -C OF DSINQF. -C -C WSAVE CONTAINS INITIALIZATION CALCULATIONS WHICH MUST NOT -C BE DESTROYED BETWEEN CALLS OF DSINQF OR DSINQB. -C -C ****************************************************************** -C -C SUBROUTINE DSINQB(N,X,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DSINQB COMPUTES THE FAST FOURIER TRANSFORM OF QUARTER -C WAVE DATA. THAT IS , DSINQB COMPUTES A SEQUENCE FROM ITS -C REPRESENTATION IN TERMS OF A SINE SERIES WITH ODD WAVE NUMBERS. -C THE TRANSFORM IS DEFINED BELOW AT OUTPUT PARAMETER X. -C -C DSINQF IS THE UNNORMALIZED INVERSE OF DSINQB SINCE A CALL OF DSINQB -C FOLLOWED BY A CALL OF DSINQF WILL MULTIPLY THE INPUT SEQUENCE X -C BY 4*N. -C -C THE ARRAY WSAVE WHICH IS USED BY SUBROUTINE DSINQB MUST BE -C INITIALIZED BY CALLING SUBROUTINE DSINQI(N,WSAVE). -C -C -C INPUT PARAMETERS -C -C N THE LENGTH OF THE ARRAY X TO BE TRANSFORMED. THE METHOD -C IS MOST EFFICIENT WHEN N IS A PRODUCT OF SMALL PRIMES. -C -C X AN ARRAY WHICH CONTAINS THE SEQUENCE TO BE TRANSFORMED -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 3*N+15. -C IN THE PROGRAM THAT CALLS DSINQB. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE DSINQI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C -C OUTPUT PARAMETERS -C -C X FOR I=1,...,N -C -C X(I)= THE SUM FROM K=1 TO K=N OF -C -C 4*X(K)*SIN((2K-1)*I*PI/(2*N)) -C -C A CALL OF DSINQB FOLLOWED BY A CALL OF -C DSINQF WILL MULTIPLY THE SEQUENCE X BY 4*N. -C THEREFORE DSINQF IS THE UNNORMALIZED INVERSE -C OF DSINQB. -C -C WSAVE CONTAINS INITIALIZATION CALCULATIONS WHICH MUST NOT -C BE DESTROYED BETWEEN CALLS OF DSINQB OR DSINQF. -C -C ****************************************************************** -C -C SUBROUTINE DCOSQI(N,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DCOSQI INITIALIZES THE ARRAY WSAVE WHICH IS USED IN -C BOTH DCOSQF AND DCOSQB. THE PRIME FACTORIZATION OF N TOGETHER WITH -C A TABULATION OF THE TRIGONOMETRIC FUNCTIONS ARE COMPUTED AND -C STORED IN WSAVE. -C -C INPUT PARAMETER -C -C N THE LENGTH OF THE ARRAY TO BE TRANSFORMED. THE METHOD -C IS MOST EFFICIENT WHEN N IS A PRODUCT OF SMALL PRIMES. -C -C OUTPUT PARAMETER -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 3*N+15. -C THE SAME WORK ARRAY CAN BE USED FOR BOTH DCOSQF AND DCOSQB -C AS LONG AS N REMAINS UNCHANGED. DIFFERENT WSAVE ARRAYS -C ARE REQUIRED FOR DIFFERENT VALUES OF N. THE CONTENTS OF -C WSAVE MUST NOT BE CHANGED BETWEEN CALLS OF DCOSQF OR DCOSQB. -C -C ****************************************************************** -C -C SUBROUTINE DCOSQF(N,X,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DCOSQF COMPUTES THE FAST FOURIER TRANSFORM OF QUARTER -C WAVE DATA. THAT IS , DCOSQF COMPUTES THE COEFFICIENTS IN A COSINE -C SERIES REPRESENTATION WITH ONLY ODD WAVE NUMBERS. THE TRANSFORM -C IS DEFINED BELOW AT OUTPUT PARAMETER X -C -C DCOSQF IS THE UNNORMALIZED INVERSE OF DCOSQB SINCE A CALL OF DCOSQF -C FOLLOWED BY A CALL OF DCOSQB WILL MULTIPLY THE INPUT SEQUENCE X -C BY 4*N. -C -C THE ARRAY WSAVE WHICH IS USED BY SUBROUTINE DCOSQF MUST BE -C INITIALIZED BY CALLING SUBROUTINE DCOSQI(N,WSAVE). -C -C -C INPUT PARAMETERS -C -C N THE LENGTH OF THE ARRAY X TO BE TRANSFORMED. THE METHOD -C IS MOST EFFICIENT WHEN N IS A PRODUCT OF SMALL PRIMES. -C -C X AN ARRAY WHICH CONTAINS THE SEQUENCE TO BE TRANSFORMED -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 3*N+15 -C IN THE PROGRAM THAT CALLS DCOSQF. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE DCOSQI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C -C OUTPUT PARAMETERS -C -C X FOR I=1,...,N -C -C X(I) = X(1) PLUS THE SUM FROM K=2 TO K=N OF -C -C 2*X(K)*COS((2*I-1)*(K-1)*PI/(2*N)) -C -C A CALL OF DCOSQF FOLLOWED BY A CALL OF -C DCOSQB WILL MULTIPLY THE SEQUENCE X BY 4*N. -C THEREFORE DCOSQB IS THE UNNORMALIZED INVERSE -C OF DCOSQF. -C -C WSAVE CONTAINS INITIALIZATION CALCULATIONS WHICH MUST NOT -C BE DESTROYED BETWEEN CALLS OF DCOSQF OR DCOSQB. -C -C ****************************************************************** -C -C SUBROUTINE DCOSQB(N,X,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE DCOSQB COMPUTES THE FAST FOURIER TRANSFORM OF QUARTER -C WAVE DATA. THAT IS , DCOSQB COMPUTES A SEQUENCE FROM ITS -C REPRESENTATION IN TERMS OF A COSINE SERIES WITH ODD WAVE NUMBERS. -C THE TRANSFORM IS DEFINED BELOW AT OUTPUT PARAMETER X. -C -C DCOSQB IS THE UNNORMALIZED INVERSE OF DCOSQF SINCE A CALL OF DCOSQB -C FOLLOWED BY A CALL OF DCOSQF WILL MULTIPLY THE INPUT SEQUENCE X -C BY 4*N. -C -C THE ARRAY WSAVE WHICH IS USED BY SUBROUTINE DCOSQB MUST BE -C INITIALIZED BY CALLING SUBROUTINE DCOSQI(N,WSAVE). -C -C -C INPUT PARAMETERS -C -C N THE LENGTH OF THE ARRAY X TO BE TRANSFORMED. THE METHOD -C IS MOST EFFICIENT WHEN N IS A PRODUCT OF SMALL PRIMES. -C -C X AN ARRAY WHICH CONTAINS THE SEQUENCE TO BE TRANSFORMED -C -C WSAVE A WORK ARRAY THAT MUST BE DIMENSIONED AT LEAST 3*N+15 -C IN THE PROGRAM THAT CALLS DCOSQB. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE DCOSQI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C -C OUTPUT PARAMETERS -C -C X FOR I=1,...,N -C -C X(I)= THE SUM FROM K=1 TO K=N OF -C -C 4*X(K)*COS((2*K-1)*(I-1)*PI/(2*N)) -C -C A CALL OF DCOSQB FOLLOWED BY A CALL OF -C DCOSQF WILL MULTIPLY THE SEQUENCE X BY 4*N. -C THEREFORE DCOSQF IS THE UNNORMALIZED INVERSE -C OF DCOSQB. -C -C WSAVE CONTAINS INITIALIZATION CALCULATIONS WHICH MUST NOT -C BE DESTROYED BETWEEN CALLS OF DCOSQB OR DCOSQF. -C -C ****************************************************************** -C -C SUBROUTINE ZFFTI(N,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE ZFFTI INITIALIZES THE ARRAY WSAVE WHICH IS USED IN -C BOTH ZFFTF AND ZFFTB. THE PRIME FACTORIZATION OF N TOGETHER WITH -C A TABULATION OF THE TRIGONOMETRIC FUNCTIONS ARE COMPUTED AND -C STORED IN WSAVE. -C -C INPUT PARAMETER -C -C N THE LENGTH OF THE SEQUENCE TO BE TRANSFORMED -C -C OUTPUT PARAMETER -C -C WSAVE A WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 4*N+15 -C THE SAME WORK ARRAY CAN BE USED FOR BOTH ZFFTF AND ZFFTB -C AS LONG AS N REMAINS UNCHANGED. DIFFERENT WSAVE ARRAYS -C ARE REQUIRED FOR DIFFERENT VALUES OF N. THE CONTENTS OF -C WSAVE MUST NOT BE CHANGED BETWEEN CALLS OF ZFFTF OR ZFFTB. -C -C ****************************************************************** -C -C SUBROUTINE ZFFTF(N,C,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE ZFFTF COMPUTES THE FORWARD COMPLEX DISCRETE FOURIER -C TRANSFORM (THE FOURIER ANALYSIS). EQUIVALENTLY , ZFFTF COMPUTES -C THE FOURIER COEFFICIENTS OF A COMPLEX PERIODIC SEQUENCE. -C THE TRANSFORM IS DEFINED BELOW AT OUTPUT PARAMETER C. -C -C THE TRANSFORM IS NOT NORMALIZED. TO OBTAIN A NORMALIZED TRANSFORM -C THE OUTPUT MUST BE DIVIDED BY N. OTHERWISE A CALL OF ZFFTF -C FOLLOWED BY A CALL OF ZFFTB WILL MULTIPLY THE SEQUENCE BY N. -C -C THE ARRAY WSAVE WHICH IS USED BY SUBROUTINE ZFFTF MUST BE -C INITIALIZED BY CALLING SUBROUTINE ZFFTI(N,WSAVE). -C -C INPUT PARAMETERS -C -C -C N THE LENGTH OF THE COMPLEX SEQUENCE C. THE METHOD IS -C MORE EFFICIENT WHEN N IS THE PRODUCT OF SMALL PRIMES. N -C -C C A COMPLEX ARRAY OF LENGTH N WHICH CONTAINS THE SEQUENCE -C -C WSAVE A REAL WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 4N+15 -C IN THE PROGRAM THAT CALLS ZFFTF. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE ZFFTI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C THE SAME WSAVE ARRAY CAN BE USED BY ZFFTF AND ZFFTB. -C -C OUTPUT PARAMETERS -C -C C FOR J=1,...,N -C -C C(J)=THE SUM FROM K=1,...,N OF -C -C C(K)*EXP(-I*(J-1)*(K-1)*2*PI/N) -C -C WHERE I=SQRT(-1) -C -C WSAVE CONTAINS INITIALIZATION CALCULATIONS WHICH MUST NOT BE -C DESTROYED BETWEEN CALLS OF SUBROUTINE ZFFTF OR ZFFTB -C -C ****************************************************************** -C -C SUBROUTINE ZFFTB(N,C,WSAVE) -C -C ****************************************************************** -C -C SUBROUTINE ZFFTB COMPUTES THE BACKWARD COMPLEX DISCRETE FOURIER -C TRANSFORM (THE FOURIER SYNTHESIS). EQUIVALENTLY , ZFFTB COMPUTES -C A COMPLEX PERIODIC SEQUENCE FROM ITS FOURIER COEFFICIENTS. -C THE TRANSFORM IS DEFINED BELOW AT OUTPUT PARAMETER C. -C -C A CALL OF ZFFTF FOLLOWED BY A CALL OF ZFFTB WILL MULTIPLY THE -C SEQUENCE BY N. -C -C THE ARRAY WSAVE WHICH IS USED BY SUBROUTINE ZFFTB MUST BE -C INITIALIZED BY CALLING SUBROUTINE ZFFTI(N,WSAVE). -C -C INPUT PARAMETERS -C -C -C N THE LENGTH OF THE COMPLEX SEQUENCE C. THE METHOD IS -C MORE EFFICIENT WHEN N IS THE PRODUCT OF SMALL PRIMES. -C -C C A COMPLEX ARRAY OF LENGTH N WHICH CONTAINS THE SEQUENCE -C -C WSAVE A REAL WORK ARRAY WHICH MUST BE DIMENSIONED AT LEAST 4N+15 -C IN THE PROGRAM THAT CALLS ZFFTB. THE WSAVE ARRAY MUST BE -C INITIALIZED BY CALLING SUBROUTINE ZFFTI(N,WSAVE) AND A -C DIFFERENT WSAVE ARRAY MUST BE USED FOR EACH DIFFERENT -C VALUE OF N. THIS INITIALIZATION DOES NOT HAVE TO BE -C REPEATED SO LONG AS N REMAINS UNCHANGED THUS SUBSEQUENT -C TRANSFORMS CAN BE OBTAINED FASTER THAN THE FIRST. -C THE SAME WSAVE ARRAY CAN BE USED BY ZFFTF AND ZFFTB. -C -C OUTPUT PARAMETERS -C -C C FOR J=1,...,N -C -C C(J)=THE SUM FROM K=1,...,N OF -C -C C(K)*EXP(I*(J-1)*(K-1)*2*PI/N) -C -C WHERE I=SQRT(-1) -C -C WSAVE CONTAINS INITIALIZATION CALCULATIONS WHICH MUST NOT BE -C DESTROYED BETWEEN CALLS OF SUBROUTINE ZFFTF OR ZFFTB -C -C -C -C ["SEND INDEX FOR VFFTPK" DESCRIBES A VECTORIZED VERSION OF FFTPACK] -C -C -C - - SUBROUTINE ZFFTB1 (N,C,CH,WA,IFAC) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CH(*) ,C(*) ,WA(*) ,IFAC(*) - NF = IFAC(2) - NA = 0 - L1 = 1 - IW = 1 - DO 116 K1=1,NF - IP = IFAC(K1+2) - L2 = IP*L1 - IDO = N/L2 - IDOT = IDO+IDO - IDL1 = IDOT*L1 - IF (IP .NE. 4) GO TO 103 - IX2 = IW+IDOT - IX3 = IX2+IDOT - IF (NA .NE. 0) GO TO 101 - CALL DPASSB4 (IDOT,L1,C,CH,WA(IW),WA(IX2),WA(IX3)) - GO TO 102 - 101 CALL DPASSB4 (IDOT,L1,CH,C,WA(IW),WA(IX2),WA(IX3)) - 102 NA = 1-NA - GO TO 115 - 103 IF (IP .NE. 2) GO TO 106 - IF (NA .NE. 0) GO TO 104 - CALL DPASSB2 (IDOT,L1,C,CH,WA(IW)) - GO TO 105 - 104 CALL DPASSB2 (IDOT,L1,CH,C,WA(IW)) - 105 NA = 1-NA - GO TO 115 - 106 IF (IP .NE. 3) GO TO 109 - IX2 = IW+IDOT - IF (NA .NE. 0) GO TO 107 - CALL DPASSB3 (IDOT,L1,C,CH,WA(IW),WA(IX2)) - GO TO 108 - 107 CALL DPASSB3 (IDOT,L1,CH,C,WA(IW),WA(IX2)) - 108 NA = 1-NA - GO TO 115 - 109 IF (IP .NE. 5) GO TO 112 - IX2 = IW+IDOT - IX3 = IX2+IDOT - IX4 = IX3+IDOT - IF (NA .NE. 0) GO TO 110 - CALL DPASSB5 (IDOT,L1,C,CH,WA(IW),WA(IX2),WA(IX3),WA(IX4)) - GO TO 111 - 110 CALL DPASSB5 (IDOT,L1,CH,C,WA(IW),WA(IX2),WA(IX3),WA(IX4)) - 111 NA = 1-NA - GO TO 115 - 112 IF (NA .NE. 0) GO TO 113 - CALL DPASSB (NAC,IDOT,IP,L1,IDL1,C,C,C,CH,CH,WA(IW)) - GO TO 114 - 113 CALL DPASSB (NAC,IDOT,IP,L1,IDL1,CH,CH,CH,C,C,WA(IW)) - 114 IF (NAC .NE. 0) NA = 1-NA - 115 L1 = L2 - IW = IW+(IP-1)*IDOT - 116 CONTINUE - IF (NA .EQ. 0) RETURN - N2 = N+N - DO 117 I=1,N2 - C(I) = CH(I) - 117 CONTINUE - RETURN - END - - SUBROUTINE ZFFTB (N,C,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION C(*) ,WSAVE(*) - IF (N .EQ. 1) RETURN - IW1 = N+N+1 - IW2 = IW1+N+N - CALL ZFFTB1 (N,C,WSAVE,WSAVE(IW1),WSAVE(IW2)) - RETURN - END - - SUBROUTINE ZFFTF1 (N,C,CH,WA,IFAC) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CH(*) ,C(*) ,WA(*) ,IFAC(*) - NF = IFAC(2) - NA = 0 - L1 = 1 - IW = 1 - DO 116 K1=1,NF - IP = IFAC(K1+2) - L2 = IP*L1 - IDO = N/L2 - IDOT = IDO+IDO - IDL1 = IDOT*L1 - IF (IP .NE. 4) GO TO 103 - IX2 = IW+IDOT - IX3 = IX2+IDOT - IF (NA .NE. 0) GO TO 101 - CALL DPASSF4 (IDOT,L1,C,CH,WA(IW),WA(IX2),WA(IX3)) - GO TO 102 - 101 CALL DPASSF4 (IDOT,L1,CH,C,WA(IW),WA(IX2),WA(IX3)) - 102 NA = 1-NA - GO TO 115 - 103 IF (IP .NE. 2) GO TO 106 - IF (NA .NE. 0) GO TO 104 - CALL DPASSF2 (IDOT,L1,C,CH,WA(IW)) - GO TO 105 - 104 CALL DPASSF2 (IDOT,L1,CH,C,WA(IW)) - 105 NA = 1-NA - GO TO 115 - 106 IF (IP .NE. 3) GO TO 109 - IX2 = IW+IDOT - IF (NA .NE. 0) GO TO 107 - CALL DPASSF3 (IDOT,L1,C,CH,WA(IW),WA(IX2)) - GO TO 108 - 107 CALL DPASSF3 (IDOT,L1,CH,C,WA(IW),WA(IX2)) - 108 NA = 1-NA - GO TO 115 - 109 IF (IP .NE. 5) GO TO 112 - IX2 = IW+IDOT - IX3 = IX2+IDOT - IX4 = IX3+IDOT - IF (NA .NE. 0) GO TO 110 - CALL DPASSF5 (IDOT,L1,C,CH,WA(IW),WA(IX2),WA(IX3),WA(IX4)) - GO TO 111 - 110 CALL DPASSF5 (IDOT,L1,CH,C,WA(IW),WA(IX2),WA(IX3),WA(IX4)) - 111 NA = 1-NA - GO TO 115 - 112 IF (NA .NE. 0) GO TO 113 - CALL DPASSF (NAC,IDOT,IP,L1,IDL1,C,C,C,CH,CH,WA(IW)) - GO TO 114 - 113 CALL DPASSF (NAC,IDOT,IP,L1,IDL1,CH,CH,CH,C,C,WA(IW)) - 114 IF (NAC .NE. 0) NA = 1-NA - 115 L1 = L2 - IW = IW+(IP-1)*IDOT - 116 CONTINUE - IF (NA .EQ. 0) RETURN - N2 = N+N - DO 117 I=1,N2 - C(I) = CH(I) - 117 CONTINUE - RETURN - END - - - SUBROUTINE ZFFTF (N,C,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION C(*) ,WSAVE(*) - IF (N .EQ. 1) RETURN - IW1 = N+N+1 - IW2 = IW1+N+N - CALL ZFFTF1 (N,C,WSAVE,WSAVE(IW1),WSAVE(IW2)) - RETURN - END - - - SUBROUTINE ZFFTI1 (N,WA,IFAC) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION WA(*) ,IFAC(*) ,NTRYH(4) - DATA NTRYH(1),NTRYH(2),NTRYH(3),NTRYH(4)/3,4,2,5/ - NL = N - NF = 0 - J = 0 - 101 J = J+1 - IF (J-4) 102,102,103 - 102 NTRY = NTRYH(J) - GO TO 104 - 103 NTRY = NTRY+2 - 104 NQ = NL/NTRY - NR = NL-NTRY*NQ - IF (NR) 101,105,101 - 105 NF = NF+1 - IFAC(NF+2) = NTRY - NL = NQ - IF (NTRY .NE. 2) GO TO 107 - IF (NF .EQ. 1) GO TO 107 - DO 106 I=2,NF - IB = NF-I+2 - IFAC(IB+2) = IFAC(IB+1) - 106 CONTINUE - IFAC(3) = 2 - 107 IF (NL .NE. 1) GO TO 104 - IFAC(1) = N - IFAC(2) = NF - TPI = 6.2831853071795864769252867665590057D0 - ARGH = TPI/DBLE(N) - I = 2 - L1 = 1 - DO 110 K1=1,NF - IP = IFAC(K1+2) - LD = 0 - L2 = L1*IP - IDO = N/L2 - IDOT = IDO+IDO+2 - IPM = IP-1 - DO 109 J=1,IPM - I1 = I - WA(I-1) = 1.0D0 - WA(I) = 0.0D0 - LD = LD+L1 - FI = 0.0D0 - ARGLD = DBLE(LD)*ARGH - DO 108 II=4,IDOT,2 - I = I+2 - FI = FI+1.0D0 - ARG = FI*ARGLD - WA(I-1) = DCOS(ARG) - WA(I) = DSIN(ARG) - 108 CONTINUE - IF (IP .LE. 5) GO TO 109 - WA(I1-1) = WA(I-1) - WA(I1) = WA(I) - 109 CONTINUE - L1 = L2 - 110 CONTINUE - RETURN - END - - SUBROUTINE ZFFTI (N,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION WSAVE(*) - IF (N .EQ. 1) RETURN - IW1 = N+N+1 - IW2 = IW1+N+N - CALL ZFFTI1 (N,WSAVE(IW1),WSAVE(IW2)) - RETURN - END - - SUBROUTINE DCOSQB1 (N,X,W,XH) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION X(*) ,W(*) ,XH(*) - NS2 = (N+1)/2 - NP2 = N+2 - DO 101 I=3,N,2 - XIM1 = X(I-1)+X(I) - X(I) = X(I)-X(I-1) - X(I-1) = XIM1 - 101 CONTINUE - X(1) = X(1)+X(1) - MODN = MOD(N,2) - IF (MODN .EQ. 0) X(N) = X(N)+X(N) - CALL DFFTB (N,X,XH) - DO 102 K=2,NS2 - KC = NP2-K - XH(K) = W(K-1)*X(KC)+W(KC-1)*X(K) - XH(KC) = W(K-1)*X(K)-W(KC-1)*X(KC) - 102 CONTINUE - IF (MODN .EQ. 0) X(NS2+1) = W(NS2)*(X(NS2+1)+X(NS2+1)) - DO 103 K=2,NS2 - KC = NP2-K - X(K) = XH(K)+XH(KC) - X(KC) = XH(K)-XH(KC) - 103 CONTINUE - X(1) = X(1)+X(1) - RETURN - END - - SUBROUTINE DCOSQF1 (N,X,W,XH) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION X(*) ,W(*) ,XH(*) - NS2 = (N+1)/2 - NP2 = N+2 - DO 101 K=2,NS2 - KC = NP2-K - XH(K) = X(K)+X(KC) - XH(KC) = X(K)-X(KC) - 101 CONTINUE - MODN = MOD(N,2) - IF (MODN .EQ. 0) XH(NS2+1) = X(NS2+1)+X(NS2+1) - DO 102 K=2,NS2 - KC = NP2-K - X(K) = W(K-1)*XH(KC)+W(KC-1)*XH(K) - X(KC) = W(K-1)*XH(K)-W(KC-1)*XH(KC) - 102 CONTINUE - IF (MODN .EQ. 0) X(NS2+1) = W(NS2)*XH(NS2+1) - CALL DFFTF (N,X,XH) - DO 103 I=3,N,2 - XIM1 = X(I-1)-X(I) - X(I) = X(I-1)+X(I) - X(I-1) = XIM1 - 103 CONTINUE - RETURN - END - SUBROUTINE DCOSQI (N,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION WSAVE(*) - DATA PIH /1.5707963267948966192313216916397514D0/ - DT = PIH/DBLE(N) - FK = 0.0D0 - DO 101 K=1,N - FK = FK+1.0D0 - WSAVE(K) = DCOS(FK*DT) - 101 CONTINUE - CALL DFFTI (N,WSAVE(N+1)) - RETURN - END - SUBROUTINE DCOST (N,X,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION X(*) ,WSAVE(*) - NM1 = N-1 - NP1 = N+1 - NS2 = N/2 - IF (N-2) 106,101,102 - 101 X1H = X(1)+X(2) - X(2) = X(1)-X(2) - X(1) = X1H - RETURN - 102 IF (N .GT. 3) GO TO 103 - X1P3 = X(1)+X(3) - TX2 = X(2)+X(2) - X(2) = X(1)-X(3) - X(1) = X1P3+TX2 - X(3) = X1P3-TX2 - RETURN - 103 C1 = X(1)-X(N) - X(1) = X(1)+X(N) - DO 104 K=2,NS2 - KC = NP1-K - T1 = X(K)+X(KC) - T2 = X(K)-X(KC) - C1 = C1+WSAVE(KC)*T2 - T2 = WSAVE(K)*T2 - X(K) = T1-T2 - X(KC) = T1+T2 - 104 CONTINUE - MODN = MOD(N,2) - IF (MODN .NE. 0) X(NS2+1) = X(NS2+1)+X(NS2+1) - CALL DFFTF (NM1,X,WSAVE(N+1)) - XIM2 = X(2) - X(2) = C1 - DO 105 I=4,N,2 - XI = X(I) - X(I) = X(I-2)-X(I-1) - X(I-1) = XIM2 - XIM2 = XI - 105 CONTINUE - IF (MODN .NE. 0) X(N) = XIM2 - 106 RETURN - END - - SUBROUTINE DZFFT1 (N,WA,IFAC) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION WA(*) ,IFAC(*) ,NTRYH(4) - DATA NTRYH(1),NTRYH(2),NTRYH(3),NTRYH(4)/4,2,3,5/ - 1 ,TPI/6.2831853071795864769252867665590057D0/ - NL = N - NF = 0 - J = 0 - 101 J = J+1 - IF (J-4) 102,102,103 - 102 NTRY = NTRYH(J) - GO TO 104 - 103 NTRY = NTRY+2 - 104 NQ = NL/NTRY - NR = NL-NTRY*NQ - IF (NR) 101,105,101 - 105 NF = NF+1 - IFAC(NF+2) = NTRY - NL = NQ - IF (NTRY .NE. 2) GO TO 107 - IF (NF .EQ. 1) GO TO 107 - DO 106 I=2,NF - IB = NF-I+2 - IFAC(IB+2) = IFAC(IB+1) - 106 CONTINUE - IFAC(3) = 2 - 107 IF (NL .NE. 1) GO TO 104 - IFAC(1) = N - IFAC(2) = NF - ARGH = TPI/DBLE(N) - IS = 0 - NFM1 = NF-1 - L1 = 1 - IF (NFM1 .EQ. 0) RETURN - DO 111 K1=1,NFM1 - IP = IFAC(K1+2) - L2 = L1*IP - IDO = N/L2 - IPM = IP-1 - ARG1 = DBLE(L1)*ARGH - CH1 = 1.0D0 - SH1 = 0.0D0 - DCH1 = DCOS(ARG1) - DSH1 = DSIN(ARG1) - DO 110 J=1,IPM - CH1H = DCH1*CH1-DSH1*SH1 - SH1 = DCH1*SH1+DSH1*CH1 - CH1 = CH1H - I = IS+2 - WA(I-1) = CH1 - WA(I) = SH1 - IF (IDO .LT. 5) GO TO 109 - DO 108 II=5,IDO,2 - I = I+2 - WA(I-1) = CH1*WA(I-3)-SH1*WA(I-2) - WA(I) = CH1*WA(I-2)+SH1*WA(I-3) - 108 CONTINUE - 109 IS = IS+IDO - 110 CONTINUE - L1 = L2 - 111 CONTINUE - RETURN - END - - SUBROUTINE DCOSQB (N,X,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION X(*) ,WSAVE(*) - DATA TSQRT2 /2.8284271247461900976033774484193961D0/ - IF (N-2) 101,102,103 - 101 X(1) = 4.0D0*X(1) - RETURN - 102 X1 = 4.0D0*(X(1)+X(2)) - X(2) = TSQRT2*(X(1)-X(2)) - X(1) = X1 - RETURN - 103 CALL DCOSQB1 (N,X,WSAVE,WSAVE(N+1)) - RETURN - END - SUBROUTINE DCOSQF (N,X,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION X(*) ,WSAVE(*) - DATA SQRT2 /1.4142135623730950488016887242096980D0/ - IF (N-2) 102,101,103 - 101 TSQX = SQRT2*X(2) - X(2) = X(1)-TSQX - X(1) = X(1)+TSQX - 102 RETURN - 103 CALL DCOSQF1 (N,X,WSAVE,WSAVE(N+1)) - RETURN - END - SUBROUTINE DCOSTI (N,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION WSAVE(*) - DATA PI /3.1415926535897932384626433832795028D0/ - IF (N .LE. 3) RETURN - NM1 = N-1 - NP1 = N+1 - NS2 = N/2 - DT = PI/DBLE(NM1) - FK = 0.0D0 - DO 101 K=2,NS2 - KC = NP1-K - FK = FK+1.0D0 - WSAVE(K) = 2.0D0*DSIN(FK*DT) - WSAVE(KC) = 2.0D0*DCOS(FK*DT) - 101 CONTINUE - CALL DFFTI (NM1,WSAVE(N+1)) - RETURN - END - - SUBROUTINE DZFFTB (N,R,AZERO,A,B,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION R(*) ,A(*) ,B(*) ,WSAVE(*) - IF (N-2) 101,102,103 - 101 R(1) = AZERO - RETURN - 102 R(1) = AZERO+A(1) - R(2) = AZERO-A(1) - RETURN - 103 NS2 = (N-1)/2 - DO 104 I=1,NS2 - R(2*I) = .5D0*A(I) - R(2*I+1) = -.5D0*B(I) - 104 CONTINUE - R(1) = AZERO - IF (MOD(N,2) .EQ. 0) R(N) = A(NS2+1) - CALL DFFTB (N,R,WSAVE(N+1)) - RETURN - END - SUBROUTINE DZFFTF (N,R,AZERO,A,B,WSAVE) -C -C VERSION 3 JUNE 1979 -C - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION R(*) ,A(*) ,B(*) ,WSAVE(*) - IF (N-2) 101,102,103 - 101 AZERO = R(1) - RETURN - 102 AZERO = .5D0*(R(1)+R(2)) - A(1) = .5D0*(R(1)-R(2)) - RETURN - 103 DO 104 I=1,N - WSAVE(I) = R(I) - 104 CONTINUE - CALL DFFTF (N,WSAVE,WSAVE(N+1)) - CF = 2.0D0/DBLE(N) - CFM = -CF - AZERO = .5D0*CF*WSAVE(1) - NS2 = (N+1)/2 - NS2M = NS2-1 - DO 105 I=1,NS2M - A(I) = CF*WSAVE(2*I) - B(I) = CFM*WSAVE(2*I+1) - 105 CONTINUE - IF (MOD(N,2) .EQ. 1) RETURN - A(NS2) = .5D0*CF*WSAVE(N) - B(NS2) = 0.0D0 - RETURN - END - SUBROUTINE DZFFTI (N,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION WSAVE(*) - IF (N .EQ. 1) RETURN - CALL DZFFT1 (N,WSAVE(2*N+1),WSAVE(3*N+1)) - RETURN - END - SUBROUTINE DPASSB (NAC,IDO,IP,L1,IDL1,CC,C1,C2,CH,CH2,WA) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CH(IDO,L1,IP) ,CC(IDO,IP,L1) , - 1 C1(IDO,L1,IP) ,WA(*) ,C2(IDL1,IP), - 2 CH2(IDL1,IP) - IDOT = IDO/2 - NT = IP*IDL1 - IPP2 = IP+2 - IPPH = (IP+1)/2 - IDP = IP*IDO -C - IF (IDO .LT. L1) GO TO 106 - DO 103 J=2,IPPH - JC = IPP2-J - DO 102 K=1,L1 - DO 101 I=1,IDO - CH(I,K,J) = CC(I,J,K)+CC(I,JC,K) - CH(I,K,JC) = CC(I,J,K)-CC(I,JC,K) - 101 CONTINUE - 102 CONTINUE - 103 CONTINUE - DO 105 K=1,L1 - DO 104 I=1,IDO - CH(I,K,1) = CC(I,1,K) - 104 CONTINUE - 105 CONTINUE - GO TO 112 - 106 DO 109 J=2,IPPH - JC = IPP2-J - DO 108 I=1,IDO - DO 107 K=1,L1 - CH(I,K,J) = CC(I,J,K)+CC(I,JC,K) - CH(I,K,JC) = CC(I,J,K)-CC(I,JC,K) - 107 CONTINUE - 108 CONTINUE - 109 CONTINUE - DO 111 I=1,IDO - DO 110 K=1,L1 - CH(I,K,1) = CC(I,1,K) - 110 CONTINUE - 111 CONTINUE - 112 IDL = 2-IDO - INC = 0 - DO 116 L=2,IPPH - LC = IPP2-L - IDL = IDL+IDO - DO 113 IK=1,IDL1 - C2(IK,L) = CH2(IK,1)+WA(IDL-1)*CH2(IK,2) - C2(IK,LC) = WA(IDL)*CH2(IK,IP) - 113 CONTINUE - IDLJ = IDL - INC = INC+IDO - DO 115 J=3,IPPH - JC = IPP2-J - IDLJ = IDLJ+INC - IF (IDLJ .GT. IDP) IDLJ = IDLJ-IDP - WAR = WA(IDLJ-1) - WAI = WA(IDLJ) - DO 114 IK=1,IDL1 - C2(IK,L) = C2(IK,L)+WAR*CH2(IK,J) - C2(IK,LC) = C2(IK,LC)+WAI*CH2(IK,JC) - 114 CONTINUE - 115 CONTINUE - 116 CONTINUE - DO 118 J=2,IPPH - DO 117 IK=1,IDL1 - CH2(IK,1) = CH2(IK,1)+CH2(IK,J) - 117 CONTINUE - 118 CONTINUE - DO 120 J=2,IPPH - JC = IPP2-J - DO 119 IK=2,IDL1,2 - CH2(IK-1,J) = C2(IK-1,J)-C2(IK,JC) - CH2(IK-1,JC) = C2(IK-1,J)+C2(IK,JC) - CH2(IK,J) = C2(IK,J)+C2(IK-1,JC) - CH2(IK,JC) = C2(IK,J)-C2(IK-1,JC) - 119 CONTINUE - 120 CONTINUE - NAC = 1 - IF (IDO .EQ. 2) RETURN - NAC = 0 - DO 121 IK=1,IDL1 - C2(IK,1) = CH2(IK,1) - 121 CONTINUE - DO 123 J=2,IP - DO 122 K=1,L1 - C1(1,K,J) = CH(1,K,J) - C1(2,K,J) = CH(2,K,J) - 122 CONTINUE - 123 CONTINUE - IF (IDOT .GT. L1) GO TO 127 - IDIJ = 0 - DO 126 J=2,IP - IDIJ = IDIJ+2 - DO 125 I=4,IDO,2 - IDIJ = IDIJ+2 - DO 124 K=1,L1 - C1(I-1,K,J) = WA(IDIJ-1)*CH(I-1,K,J)-WA(IDIJ)*CH(I,K,J) - C1(I,K,J) = WA(IDIJ-1)*CH(I,K,J)+WA(IDIJ)*CH(I-1,K,J) - 124 CONTINUE - 125 CONTINUE - 126 CONTINUE - RETURN - 127 IDJ = 2-IDO - DO 130 J=2,IP - IDJ = IDJ+IDO - DO 129 K=1,L1 - IDIJ = IDJ - DO 128 I=4,IDO,2 - IDIJ = IDIJ+2 - C1(I-1,K,J) = WA(IDIJ-1)*CH(I-1,K,J)-WA(IDIJ)*CH(I,K,J) - C1(I,K,J) = WA(IDIJ-1)*CH(I,K,J)+WA(IDIJ)*CH(I-1,K,J) - 128 CONTINUE - 129 CONTINUE - 130 CONTINUE - RETURN - END - SUBROUTINE DPASSB2 (IDO,L1,CC,CH,WA1) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,2,L1) ,CH(IDO,L1,2) , - 1 WA1(*) - IF (IDO .GT. 2) GO TO 102 - DO 101 K=1,L1 - CH(1,K,1) = CC(1,1,K)+CC(1,2,K) - CH(1,K,2) = CC(1,1,K)-CC(1,2,K) - CH(2,K,1) = CC(2,1,K)+CC(2,2,K) - CH(2,K,2) = CC(2,1,K)-CC(2,2,K) - 101 CONTINUE - RETURN - 102 DO 104 K=1,L1 - DO 103 I=2,IDO,2 - CH(I-1,K,1) = CC(I-1,1,K)+CC(I-1,2,K) - TR2 = CC(I-1,1,K)-CC(I-1,2,K) - CH(I,K,1) = CC(I,1,K)+CC(I,2,K) - TI2 = CC(I,1,K)-CC(I,2,K) - CH(I,K,2) = WA1(I-1)*TI2+WA1(I)*TR2 - CH(I-1,K,2) = WA1(I-1)*TR2-WA1(I)*TI2 - 103 CONTINUE - 104 CONTINUE - RETURN - END - SUBROUTINE DPASSB3 (IDO,L1,CC,CH,WA1,WA2) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,3,L1) ,CH(IDO,L1,3) , - 1 WA1(*) ,WA2(*) - DATA TAUR,TAUI /-.5D0,.86602540378443864676372317075293618D0/ - IF (IDO .NE. 2) GO TO 102 - DO 101 K=1,L1 - TR2 = CC(1,2,K)+CC(1,3,K) - CR2 = CC(1,1,K)+TAUR*TR2 - CH(1,K,1) = CC(1,1,K)+TR2 - TI2 = CC(2,2,K)+CC(2,3,K) - CI2 = CC(2,1,K)+TAUR*TI2 - CH(2,K,1) = CC(2,1,K)+TI2 - CR3 = TAUI*(CC(1,2,K)-CC(1,3,K)) - CI3 = TAUI*(CC(2,2,K)-CC(2,3,K)) - CH(1,K,2) = CR2-CI3 - CH(1,K,3) = CR2+CI3 - CH(2,K,2) = CI2+CR3 - CH(2,K,3) = CI2-CR3 - 101 CONTINUE - RETURN - 102 DO 104 K=1,L1 - DO 103 I=2,IDO,2 - TR2 = CC(I-1,2,K)+CC(I-1,3,K) - CR2 = CC(I-1,1,K)+TAUR*TR2 - CH(I-1,K,1) = CC(I-1,1,K)+TR2 - TI2 = CC(I,2,K)+CC(I,3,K) - CI2 = CC(I,1,K)+TAUR*TI2 - CH(I,K,1) = CC(I,1,K)+TI2 - CR3 = TAUI*(CC(I-1,2,K)-CC(I-1,3,K)) - CI3 = TAUI*(CC(I,2,K)-CC(I,3,K)) - DR2 = CR2-CI3 - DR3 = CR2+CI3 - DI2 = CI2+CR3 - DI3 = CI2-CR3 - CH(I,K,2) = WA1(I-1)*DI2+WA1(I)*DR2 - CH(I-1,K,2) = WA1(I-1)*DR2-WA1(I)*DI2 - CH(I,K,3) = WA2(I-1)*DI3+WA2(I)*DR3 - CH(I-1,K,3) = WA2(I-1)*DR3-WA2(I)*DI3 - 103 CONTINUE - 104 CONTINUE - RETURN - END - SUBROUTINE DPASSB4 (IDO,L1,CC,CH,WA1,WA2,WA3) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,4,L1) ,CH(IDO,L1,4) , - 1 WA1(*) ,WA2(*) ,WA3(*) - IF (IDO .NE. 2) GO TO 102 - DO 101 K=1,L1 - TI1 = CC(2,1,K)-CC(2,3,K) - TI2 = CC(2,1,K)+CC(2,3,K) - TR4 = CC(2,4,K)-CC(2,2,K) - TI3 = CC(2,2,K)+CC(2,4,K) - TR1 = CC(1,1,K)-CC(1,3,K) - TR2 = CC(1,1,K)+CC(1,3,K) - TI4 = CC(1,2,K)-CC(1,4,K) - TR3 = CC(1,2,K)+CC(1,4,K) - CH(1,K,1) = TR2+TR3 - CH(1,K,3) = TR2-TR3 - CH(2,K,1) = TI2+TI3 - CH(2,K,3) = TI2-TI3 - CH(1,K,2) = TR1+TR4 - CH(1,K,4) = TR1-TR4 - CH(2,K,2) = TI1+TI4 - CH(2,K,4) = TI1-TI4 - 101 CONTINUE - RETURN - 102 DO 104 K=1,L1 - DO 103 I=2,IDO,2 - TI1 = CC(I,1,K)-CC(I,3,K) - TI2 = CC(I,1,K)+CC(I,3,K) - TI3 = CC(I,2,K)+CC(I,4,K) - TR4 = CC(I,4,K)-CC(I,2,K) - TR1 = CC(I-1,1,K)-CC(I-1,3,K) - TR2 = CC(I-1,1,K)+CC(I-1,3,K) - TI4 = CC(I-1,2,K)-CC(I-1,4,K) - TR3 = CC(I-1,2,K)+CC(I-1,4,K) - CH(I-1,K,1) = TR2+TR3 - CR3 = TR2-TR3 - CH(I,K,1) = TI2+TI3 - CI3 = TI2-TI3 - CR2 = TR1+TR4 - CR4 = TR1-TR4 - CI2 = TI1+TI4 - CI4 = TI1-TI4 - CH(I-1,K,2) = WA1(I-1)*CR2-WA1(I)*CI2 - CH(I,K,2) = WA1(I-1)*CI2+WA1(I)*CR2 - CH(I-1,K,3) = WA2(I-1)*CR3-WA2(I)*CI3 - CH(I,K,3) = WA2(I-1)*CI3+WA2(I)*CR3 - CH(I-1,K,4) = WA3(I-1)*CR4-WA3(I)*CI4 - CH(I,K,4) = WA3(I-1)*CI4+WA3(I)*CR4 - 103 CONTINUE - 104 CONTINUE - RETURN - END - SUBROUTINE DPASSB5 (IDO,L1,CC,CH,WA1,WA2,WA3,WA4) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,5,L1) ,CH(IDO,L1,5) , - 1 WA1(*) ,WA2(*) ,WA3(*) ,WA4(*) - DATA TR11,TI11,TR12,TI12 / - 1 .30901699437494742410229341718281905D0, - 2 .95105651629515357211643933337938214D0, - 3 -.80901699437494742410229341718281906D0, - 4 .58778525229247312916870595463907276D0/ - IF (IDO .NE. 2) GO TO 102 - DO 101 K=1,L1 - TI5 = CC(2,2,K)-CC(2,5,K) - TI2 = CC(2,2,K)+CC(2,5,K) - TI4 = CC(2,3,K)-CC(2,4,K) - TI3 = CC(2,3,K)+CC(2,4,K) - TR5 = CC(1,2,K)-CC(1,5,K) - TR2 = CC(1,2,K)+CC(1,5,K) - TR4 = CC(1,3,K)-CC(1,4,K) - TR3 = CC(1,3,K)+CC(1,4,K) - CH(1,K,1) = CC(1,1,K)+TR2+TR3 - CH(2,K,1) = CC(2,1,K)+TI2+TI3 - CR2 = CC(1,1,K)+TR11*TR2+TR12*TR3 - CI2 = CC(2,1,K)+TR11*TI2+TR12*TI3 - CR3 = CC(1,1,K)+TR12*TR2+TR11*TR3 - CI3 = CC(2,1,K)+TR12*TI2+TR11*TI3 - CR5 = TI11*TR5+TI12*TR4 - CI5 = TI11*TI5+TI12*TI4 - CR4 = TI12*TR5-TI11*TR4 - CI4 = TI12*TI5-TI11*TI4 - CH(1,K,2) = CR2-CI5 - CH(1,K,5) = CR2+CI5 - CH(2,K,2) = CI2+CR5 - CH(2,K,3) = CI3+CR4 - CH(1,K,3) = CR3-CI4 - CH(1,K,4) = CR3+CI4 - CH(2,K,4) = CI3-CR4 - CH(2,K,5) = CI2-CR5 - 101 CONTINUE - RETURN - 102 DO 104 K=1,L1 - DO 103 I=2,IDO,2 - TI5 = CC(I,2,K)-CC(I,5,K) - TI2 = CC(I,2,K)+CC(I,5,K) - TI4 = CC(I,3,K)-CC(I,4,K) - TI3 = CC(I,3,K)+CC(I,4,K) - TR5 = CC(I-1,2,K)-CC(I-1,5,K) - TR2 = CC(I-1,2,K)+CC(I-1,5,K) - TR4 = CC(I-1,3,K)-CC(I-1,4,K) - TR3 = CC(I-1,3,K)+CC(I-1,4,K) - CH(I-1,K,1) = CC(I-1,1,K)+TR2+TR3 - CH(I,K,1) = CC(I,1,K)+TI2+TI3 - CR2 = CC(I-1,1,K)+TR11*TR2+TR12*TR3 - CI2 = CC(I,1,K)+TR11*TI2+TR12*TI3 - CR3 = CC(I-1,1,K)+TR12*TR2+TR11*TR3 - CI3 = CC(I,1,K)+TR12*TI2+TR11*TI3 - CR5 = TI11*TR5+TI12*TR4 - CI5 = TI11*TI5+TI12*TI4 - CR4 = TI12*TR5-TI11*TR4 - CI4 = TI12*TI5-TI11*TI4 - DR3 = CR3-CI4 - DR4 = CR3+CI4 - DI3 = CI3+CR4 - DI4 = CI3-CR4 - DR5 = CR2+CI5 - DR2 = CR2-CI5 - DI5 = CI2-CR5 - DI2 = CI2+CR5 - CH(I-1,K,2) = WA1(I-1)*DR2-WA1(I)*DI2 - CH(I,K,2) = WA1(I-1)*DI2+WA1(I)*DR2 - CH(I-1,K,3) = WA2(I-1)*DR3-WA2(I)*DI3 - CH(I,K,3) = WA2(I-1)*DI3+WA2(I)*DR3 - CH(I-1,K,4) = WA3(I-1)*DR4-WA3(I)*DI4 - CH(I,K,4) = WA3(I-1)*DI4+WA3(I)*DR4 - CH(I-1,K,5) = WA4(I-1)*DR5-WA4(I)*DI5 - CH(I,K,5) = WA4(I-1)*DI5+WA4(I)*DR5 - 103 CONTINUE - 104 CONTINUE - RETURN - END - SUBROUTINE DPASSF (NAC,IDO,IP,L1,IDL1,CC,C1,C2,CH,CH2,WA) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CH(IDO,L1,IP) ,CC(IDO,IP,L1) , - 1 C1(IDO,L1,IP) ,WA(*) ,C2(IDL1,IP), - 2 CH2(IDL1,IP) - IDOT = IDO/2 - NT = IP*IDL1 - IPP2 = IP+2 - IPPH = (IP+1)/2 - IDP = IP*IDO -C - IF (IDO .LT. L1) GO TO 106 - DO 103 J=2,IPPH - JC = IPP2-J - DO 102 K=1,L1 - DO 101 I=1,IDO - CH(I,K,J) = CC(I,J,K)+CC(I,JC,K) - CH(I,K,JC) = CC(I,J,K)-CC(I,JC,K) - 101 CONTINUE - 102 CONTINUE - 103 CONTINUE - DO 105 K=1,L1 - DO 104 I=1,IDO - CH(I,K,1) = CC(I,1,K) - 104 CONTINUE - 105 CONTINUE - GO TO 112 - 106 DO 109 J=2,IPPH - JC = IPP2-J - DO 108 I=1,IDO - DO 107 K=1,L1 - CH(I,K,J) = CC(I,J,K)+CC(I,JC,K) - CH(I,K,JC) = CC(I,J,K)-CC(I,JC,K) - 107 CONTINUE - 108 CONTINUE - 109 CONTINUE - DO 111 I=1,IDO - DO 110 K=1,L1 - CH(I,K,1) = CC(I,1,K) - 110 CONTINUE - 111 CONTINUE - 112 IDL = 2-IDO - INC = 0 - DO 116 L=2,IPPH - LC = IPP2-L - IDL = IDL+IDO - DO 113 IK=1,IDL1 - C2(IK,L) = CH2(IK,1)+WA(IDL-1)*CH2(IK,2) - C2(IK,LC) = -WA(IDL)*CH2(IK,IP) - 113 CONTINUE - IDLJ = IDL - INC = INC+IDO - DO 115 J=3,IPPH - JC = IPP2-J - IDLJ = IDLJ+INC - IF (IDLJ .GT. IDP) IDLJ = IDLJ-IDP - WAR = WA(IDLJ-1) - WAI = WA(IDLJ) - DO 114 IK=1,IDL1 - C2(IK,L) = C2(IK,L)+WAR*CH2(IK,J) - C2(IK,LC) = C2(IK,LC)-WAI*CH2(IK,JC) - 114 CONTINUE - 115 CONTINUE - 116 CONTINUE - DO 118 J=2,IPPH - DO 117 IK=1,IDL1 - CH2(IK,1) = CH2(IK,1)+CH2(IK,J) - 117 CONTINUE - 118 CONTINUE - DO 120 J=2,IPPH - JC = IPP2-J - DO 119 IK=2,IDL1,2 - CH2(IK-1,J) = C2(IK-1,J)-C2(IK,JC) - CH2(IK-1,JC) = C2(IK-1,J)+C2(IK,JC) - CH2(IK,J) = C2(IK,J)+C2(IK-1,JC) - CH2(IK,JC) = C2(IK,J)-C2(IK-1,JC) - 119 CONTINUE - 120 CONTINUE - NAC = 1 - IF (IDO .EQ. 2) RETURN - NAC = 0 - DO 121 IK=1,IDL1 - C2(IK,1) = CH2(IK,1) - 121 CONTINUE - DO 123 J=2,IP - DO 122 K=1,L1 - C1(1,K,J) = CH(1,K,J) - C1(2,K,J) = CH(2,K,J) - 122 CONTINUE - 123 CONTINUE - IF (IDOT .GT. L1) GO TO 127 - IDIJ = 0 - DO 126 J=2,IP - IDIJ = IDIJ+2 - DO 125 I=4,IDO,2 - IDIJ = IDIJ+2 - DO 124 K=1,L1 - C1(I-1,K,J) = WA(IDIJ-1)*CH(I-1,K,J)+WA(IDIJ)*CH(I,K,J) - C1(I,K,J) = WA(IDIJ-1)*CH(I,K,J)-WA(IDIJ)*CH(I-1,K,J) - 124 CONTINUE - 125 CONTINUE - 126 CONTINUE - RETURN - 127 IDJ = 2-IDO - DO 130 J=2,IP - IDJ = IDJ+IDO - DO 129 K=1,L1 - IDIJ = IDJ - DO 128 I=4,IDO,2 - IDIJ = IDIJ+2 - C1(I-1,K,J) = WA(IDIJ-1)*CH(I-1,K,J)+WA(IDIJ)*CH(I,K,J) - C1(I,K,J) = WA(IDIJ-1)*CH(I,K,J)-WA(IDIJ)*CH(I-1,K,J) - 128 CONTINUE - 129 CONTINUE - 130 CONTINUE - RETURN - END - SUBROUTINE DPASSF2 (IDO,L1,CC,CH,WA1) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,2,L1) ,CH(IDO,L1,2) , - 1 WA1(*) - IF (IDO .GT. 2) GO TO 102 - DO 101 K=1,L1 - CH(1,K,1) = CC(1,1,K)+CC(1,2,K) - CH(1,K,2) = CC(1,1,K)-CC(1,2,K) - CH(2,K,1) = CC(2,1,K)+CC(2,2,K) - CH(2,K,2) = CC(2,1,K)-CC(2,2,K) - 101 CONTINUE - RETURN - 102 DO 104 K=1,L1 - DO 103 I=2,IDO,2 - CH(I-1,K,1) = CC(I-1,1,K)+CC(I-1,2,K) - TR2 = CC(I-1,1,K)-CC(I-1,2,K) - CH(I,K,1) = CC(I,1,K)+CC(I,2,K) - TI2 = CC(I,1,K)-CC(I,2,K) - CH(I,K,2) = WA1(I-1)*TI2-WA1(I)*TR2 - CH(I-1,K,2) = WA1(I-1)*TR2+WA1(I)*TI2 - 103 CONTINUE - 104 CONTINUE - RETURN - END - SUBROUTINE DPASSF3 (IDO,L1,CC,CH,WA1,WA2) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,3,L1) ,CH(IDO,L1,3) , - 1 WA1(*) ,WA2(*) - DATA TAUR,TAUI /-.5D0,-.86602540378443864676372317075293618D0/ - IF (IDO .NE. 2) GO TO 102 - DO 101 K=1,L1 - TR2 = CC(1,2,K)+CC(1,3,K) - CR2 = CC(1,1,K)+TAUR*TR2 - CH(1,K,1) = CC(1,1,K)+TR2 - TI2 = CC(2,2,K)+CC(2,3,K) - CI2 = CC(2,1,K)+TAUR*TI2 - CH(2,K,1) = CC(2,1,K)+TI2 - CR3 = TAUI*(CC(1,2,K)-CC(1,3,K)) - CI3 = TAUI*(CC(2,2,K)-CC(2,3,K)) - CH(1,K,2) = CR2-CI3 - CH(1,K,3) = CR2+CI3 - CH(2,K,2) = CI2+CR3 - CH(2,K,3) = CI2-CR3 - 101 CONTINUE - RETURN - 102 DO 104 K=1,L1 - DO 103 I=2,IDO,2 - TR2 = CC(I-1,2,K)+CC(I-1,3,K) - CR2 = CC(I-1,1,K)+TAUR*TR2 - CH(I-1,K,1) = CC(I-1,1,K)+TR2 - TI2 = CC(I,2,K)+CC(I,3,K) - CI2 = CC(I,1,K)+TAUR*TI2 - CH(I,K,1) = CC(I,1,K)+TI2 - CR3 = TAUI*(CC(I-1,2,K)-CC(I-1,3,K)) - CI3 = TAUI*(CC(I,2,K)-CC(I,3,K)) - DR2 = CR2-CI3 - DR3 = CR2+CI3 - DI2 = CI2+CR3 - DI3 = CI2-CR3 - CH(I,K,2) = WA1(I-1)*DI2-WA1(I)*DR2 - CH(I-1,K,2) = WA1(I-1)*DR2+WA1(I)*DI2 - CH(I,K,3) = WA2(I-1)*DI3-WA2(I)*DR3 - CH(I-1,K,3) = WA2(I-1)*DR3+WA2(I)*DI3 - 103 CONTINUE - 104 CONTINUE - RETURN - END - SUBROUTINE DPASSF4 (IDO,L1,CC,CH,WA1,WA2,WA3) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,4,L1) ,CH(IDO,L1,4) , - 1 WA1(*) ,WA2(*) ,WA3(*) - IF (IDO .NE. 2) GO TO 102 - DO 101 K=1,L1 - TI1 = CC(2,1,K)-CC(2,3,K) - TI2 = CC(2,1,K)+CC(2,3,K) - TR4 = CC(2,2,K)-CC(2,4,K) - TI3 = CC(2,2,K)+CC(2,4,K) - TR1 = CC(1,1,K)-CC(1,3,K) - TR2 = CC(1,1,K)+CC(1,3,K) - TI4 = CC(1,4,K)-CC(1,2,K) - TR3 = CC(1,2,K)+CC(1,4,K) - CH(1,K,1) = TR2+TR3 - CH(1,K,3) = TR2-TR3 - CH(2,K,1) = TI2+TI3 - CH(2,K,3) = TI2-TI3 - CH(1,K,2) = TR1+TR4 - CH(1,K,4) = TR1-TR4 - CH(2,K,2) = TI1+TI4 - CH(2,K,4) = TI1-TI4 - 101 CONTINUE - RETURN - 102 DO 104 K=1,L1 - DO 103 I=2,IDO,2 - TI1 = CC(I,1,K)-CC(I,3,K) - TI2 = CC(I,1,K)+CC(I,3,K) - TI3 = CC(I,2,K)+CC(I,4,K) - TR4 = CC(I,2,K)-CC(I,4,K) - TR1 = CC(I-1,1,K)-CC(I-1,3,K) - TR2 = CC(I-1,1,K)+CC(I-1,3,K) - TI4 = CC(I-1,4,K)-CC(I-1,2,K) - TR3 = CC(I-1,2,K)+CC(I-1,4,K) - CH(I-1,K,1) = TR2+TR3 - CR3 = TR2-TR3 - CH(I,K,1) = TI2+TI3 - CI3 = TI2-TI3 - CR2 = TR1+TR4 - CR4 = TR1-TR4 - CI2 = TI1+TI4 - CI4 = TI1-TI4 - CH(I-1,K,2) = WA1(I-1)*CR2+WA1(I)*CI2 - CH(I,K,2) = WA1(I-1)*CI2-WA1(I)*CR2 - CH(I-1,K,3) = WA2(I-1)*CR3+WA2(I)*CI3 - CH(I,K,3) = WA2(I-1)*CI3-WA2(I)*CR3 - CH(I-1,K,4) = WA3(I-1)*CR4+WA3(I)*CI4 - CH(I,K,4) = WA3(I-1)*CI4-WA3(I)*CR4 - 103 CONTINUE - 104 CONTINUE - RETURN - END - SUBROUTINE DPASSF5 (IDO,L1,CC,CH,WA1,WA2,WA3,WA4) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,5,L1) ,CH(IDO,L1,5) , - 1 WA1(*) ,WA2(*) ,WA3(*) ,WA4(*) - DATA TR11,TI11,TR12,TI12 / - 1 .30901699437494742410229341718281905D0, - 2 -.95105651629515357211643933337938214D0, - 3 -.80901699437494742410229341718281906D0, - 4 -.58778525229247312916870595463907276D0/ - IF (IDO .NE. 2) GO TO 102 - DO 101 K=1,L1 - TI5 = CC(2,2,K)-CC(2,5,K) - TI2 = CC(2,2,K)+CC(2,5,K) - TI4 = CC(2,3,K)-CC(2,4,K) - TI3 = CC(2,3,K)+CC(2,4,K) - TR5 = CC(1,2,K)-CC(1,5,K) - TR2 = CC(1,2,K)+CC(1,5,K) - TR4 = CC(1,3,K)-CC(1,4,K) - TR3 = CC(1,3,K)+CC(1,4,K) - CH(1,K,1) = CC(1,1,K)+TR2+TR3 - CH(2,K,1) = CC(2,1,K)+TI2+TI3 - CR2 = CC(1,1,K)+TR11*TR2+TR12*TR3 - CI2 = CC(2,1,K)+TR11*TI2+TR12*TI3 - CR3 = CC(1,1,K)+TR12*TR2+TR11*TR3 - CI3 = CC(2,1,K)+TR12*TI2+TR11*TI3 - CR5 = TI11*TR5+TI12*TR4 - CI5 = TI11*TI5+TI12*TI4 - CR4 = TI12*TR5-TI11*TR4 - CI4 = TI12*TI5-TI11*TI4 - CH(1,K,2) = CR2-CI5 - CH(1,K,5) = CR2+CI5 - CH(2,K,2) = CI2+CR5 - CH(2,K,3) = CI3+CR4 - CH(1,K,3) = CR3-CI4 - CH(1,K,4) = CR3+CI4 - CH(2,K,4) = CI3-CR4 - CH(2,K,5) = CI2-CR5 - 101 CONTINUE - RETURN - 102 DO 104 K=1,L1 - DO 103 I=2,IDO,2 - TI5 = CC(I,2,K)-CC(I,5,K) - TI2 = CC(I,2,K)+CC(I,5,K) - TI4 = CC(I,3,K)-CC(I,4,K) - TI3 = CC(I,3,K)+CC(I,4,K) - TR5 = CC(I-1,2,K)-CC(I-1,5,K) - TR2 = CC(I-1,2,K)+CC(I-1,5,K) - TR4 = CC(I-1,3,K)-CC(I-1,4,K) - TR3 = CC(I-1,3,K)+CC(I-1,4,K) - CH(I-1,K,1) = CC(I-1,1,K)+TR2+TR3 - CH(I,K,1) = CC(I,1,K)+TI2+TI3 - CR2 = CC(I-1,1,K)+TR11*TR2+TR12*TR3 - CI2 = CC(I,1,K)+TR11*TI2+TR12*TI3 - CR3 = CC(I-1,1,K)+TR12*TR2+TR11*TR3 - CI3 = CC(I,1,K)+TR12*TI2+TR11*TI3 - CR5 = TI11*TR5+TI12*TR4 - CI5 = TI11*TI5+TI12*TI4 - CR4 = TI12*TR5-TI11*TR4 - CI4 = TI12*TI5-TI11*TI4 - DR3 = CR3-CI4 - DR4 = CR3+CI4 - DI3 = CI3+CR4 - DI4 = CI3-CR4 - DR5 = CR2+CI5 - DR2 = CR2-CI5 - DI5 = CI2-CR5 - DI2 = CI2+CR5 - CH(I-1,K,2) = WA1(I-1)*DR2+WA1(I)*DI2 - CH(I,K,2) = WA1(I-1)*DI2-WA1(I)*DR2 - CH(I-1,K,3) = WA2(I-1)*DR3+WA2(I)*DI3 - CH(I,K,3) = WA2(I-1)*DI3-WA2(I)*DR3 - CH(I-1,K,4) = WA3(I-1)*DR4+WA3(I)*DI4 - CH(I,K,4) = WA3(I-1)*DI4-WA3(I)*DR4 - CH(I-1,K,5) = WA4(I-1)*DR5+WA4(I)*DI5 - CH(I,K,5) = WA4(I-1)*DI5-WA4(I)*DR5 - 103 CONTINUE - 104 CONTINUE - RETURN - END - SUBROUTINE DRADB2 (IDO,L1,CC,CH,WA1) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,2,L1) ,CH(IDO,L1,2) , - 1 WA1(*) - DO 101 K=1,L1 - CH(1,K,1) = CC(1,1,K)+CC(IDO,2,K) - CH(1,K,2) = CC(1,1,K)-CC(IDO,2,K) - 101 CONTINUE - IF (IDO-2) 107,105,102 - 102 IDP2 = IDO+2 - DO 104 K=1,L1 - DO 103 I=3,IDO,2 - IC = IDP2-I - CH(I-1,K,1) = CC(I-1,1,K)+CC(IC-1,2,K) - TR2 = CC(I-1,1,K)-CC(IC-1,2,K) - CH(I,K,1) = CC(I,1,K)-CC(IC,2,K) - TI2 = CC(I,1,K)+CC(IC,2,K) - CH(I-1,K,2) = WA1(I-2)*TR2-WA1(I-1)*TI2 - CH(I,K,2) = WA1(I-2)*TI2+WA1(I-1)*TR2 - 103 CONTINUE - 104 CONTINUE - IF (MOD(IDO,2) .EQ. 1) RETURN - 105 DO 106 K=1,L1 - CH(IDO,K,1) = CC(IDO,1,K)+CC(IDO,1,K) - CH(IDO,K,2) = -(CC(1,2,K)+CC(1,2,K)) - 106 CONTINUE - 107 RETURN - END - SUBROUTINE DRADB3 (IDO,L1,CC,CH,WA1,WA2) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,3,L1) ,CH(IDO,L1,3) , - 1 WA1(*) ,WA2(*) - DATA TAUR,TAUI /-.5D0,.86602540378443864676372317075293618D0/ - DO 101 K=1,L1 - TR2 = CC(IDO,2,K)+CC(IDO,2,K) - CR2 = CC(1,1,K)+TAUR*TR2 - CH(1,K,1) = CC(1,1,K)+TR2 - CI3 = TAUI*(CC(1,3,K)+CC(1,3,K)) - CH(1,K,2) = CR2-CI3 - CH(1,K,3) = CR2+CI3 - 101 CONTINUE - IF (IDO .EQ. 1) RETURN - IDP2 = IDO+2 - DO 103 K=1,L1 - DO 102 I=3,IDO,2 - IC = IDP2-I - TR2 = CC(I-1,3,K)+CC(IC-1,2,K) - CR2 = CC(I-1,1,K)+TAUR*TR2 - CH(I-1,K,1) = CC(I-1,1,K)+TR2 - TI2 = CC(I,3,K)-CC(IC,2,K) - CI2 = CC(I,1,K)+TAUR*TI2 - CH(I,K,1) = CC(I,1,K)+TI2 - CR3 = TAUI*(CC(I-1,3,K)-CC(IC-1,2,K)) - CI3 = TAUI*(CC(I,3,K)+CC(IC,2,K)) - DR2 = CR2-CI3 - DR3 = CR2+CI3 - DI2 = CI2+CR3 - DI3 = CI2-CR3 - CH(I-1,K,2) = WA1(I-2)*DR2-WA1(I-1)*DI2 - CH(I,K,2) = WA1(I-2)*DI2+WA1(I-1)*DR2 - CH(I-1,K,3) = WA2(I-2)*DR3-WA2(I-1)*DI3 - CH(I,K,3) = WA2(I-2)*DI3+WA2(I-1)*DR3 - 102 CONTINUE - 103 CONTINUE - RETURN - END - SUBROUTINE DRADB4 (IDO,L1,CC,CH,WA1,WA2,WA3) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,4,L1) ,CH(IDO,L1,4) , - 1 WA1(*) ,WA2(*) ,WA3(*) - DATA SQRT2 /1.4142135623730950488016887242096980D0/ - DO 101 K=1,L1 - TR1 = CC(1,1,K)-CC(IDO,4,K) - TR2 = CC(1,1,K)+CC(IDO,4,K) - TR3 = CC(IDO,2,K)+CC(IDO,2,K) - TR4 = CC(1,3,K)+CC(1,3,K) - CH(1,K,1) = TR2+TR3 - CH(1,K,2) = TR1-TR4 - CH(1,K,3) = TR2-TR3 - CH(1,K,4) = TR1+TR4 - 101 CONTINUE - IF (IDO-2) 107,105,102 - 102 IDP2 = IDO+2 - DO 104 K=1,L1 - DO 103 I=3,IDO,2 - IC = IDP2-I - TI1 = CC(I,1,K)+CC(IC,4,K) - TI2 = CC(I,1,K)-CC(IC,4,K) - TI3 = CC(I,3,K)-CC(IC,2,K) - TR4 = CC(I,3,K)+CC(IC,2,K) - TR1 = CC(I-1,1,K)-CC(IC-1,4,K) - TR2 = CC(I-1,1,K)+CC(IC-1,4,K) - TI4 = CC(I-1,3,K)-CC(IC-1,2,K) - TR3 = CC(I-1,3,K)+CC(IC-1,2,K) - CH(I-1,K,1) = TR2+TR3 - CR3 = TR2-TR3 - CH(I,K,1) = TI2+TI3 - CI3 = TI2-TI3 - CR2 = TR1-TR4 - CR4 = TR1+TR4 - CI2 = TI1+TI4 - CI4 = TI1-TI4 - CH(I-1,K,2) = WA1(I-2)*CR2-WA1(I-1)*CI2 - CH(I,K,2) = WA1(I-2)*CI2+WA1(I-1)*CR2 - CH(I-1,K,3) = WA2(I-2)*CR3-WA2(I-1)*CI3 - CH(I,K,3) = WA2(I-2)*CI3+WA2(I-1)*CR3 - CH(I-1,K,4) = WA3(I-2)*CR4-WA3(I-1)*CI4 - CH(I,K,4) = WA3(I-2)*CI4+WA3(I-1)*CR4 - 103 CONTINUE - 104 CONTINUE - IF (MOD(IDO,2) .EQ. 1) RETURN - 105 CONTINUE - DO 106 K=1,L1 - TI1 = CC(1,2,K)+CC(1,4,K) - TI2 = CC(1,4,K)-CC(1,2,K) - TR1 = CC(IDO,1,K)-CC(IDO,3,K) - TR2 = CC(IDO,1,K)+CC(IDO,3,K) - CH(IDO,K,1) = TR2+TR2 - CH(IDO,K,2) = SQRT2*(TR1-TI1) - CH(IDO,K,3) = TI2+TI2 - CH(IDO,K,4) = -SQRT2*(TR1+TI1) - 106 CONTINUE - 107 RETURN - END - SUBROUTINE DRADB5 (IDO,L1,CC,CH,WA1,WA2,WA3,WA4) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,5,L1) ,CH(IDO,L1,5) , - 1 WA1(*) ,WA2(*) ,WA3(*) ,WA4(*) - DATA TR11,TI11,TR12,TI12 / - 1 .30901699437494742410229341718281905D0, - 2 .95105651629515357211643933337938214D0, - 3 -.80901699437494742410229341718281906D0, - 4 .58778525229247312916870595463907276D0/ - DO 101 K=1,L1 - TI5 = CC(1,3,K)+CC(1,3,K) - TI4 = CC(1,5,K)+CC(1,5,K) - TR2 = CC(IDO,2,K)+CC(IDO,2,K) - TR3 = CC(IDO,4,K)+CC(IDO,4,K) - CH(1,K,1) = CC(1,1,K)+TR2+TR3 - CR2 = CC(1,1,K)+TR11*TR2+TR12*TR3 - CR3 = CC(1,1,K)+TR12*TR2+TR11*TR3 - CI5 = TI11*TI5+TI12*TI4 - CI4 = TI12*TI5-TI11*TI4 - CH(1,K,2) = CR2-CI5 - CH(1,K,3) = CR3-CI4 - CH(1,K,4) = CR3+CI4 - CH(1,K,5) = CR2+CI5 - 101 CONTINUE - IF (IDO .EQ. 1) RETURN - IDP2 = IDO+2 - DO 103 K=1,L1 - DO 102 I=3,IDO,2 - IC = IDP2-I - TI5 = CC(I,3,K)+CC(IC,2,K) - TI2 = CC(I,3,K)-CC(IC,2,K) - TI4 = CC(I,5,K)+CC(IC,4,K) - TI3 = CC(I,5,K)-CC(IC,4,K) - TR5 = CC(I-1,3,K)-CC(IC-1,2,K) - TR2 = CC(I-1,3,K)+CC(IC-1,2,K) - TR4 = CC(I-1,5,K)-CC(IC-1,4,K) - TR3 = CC(I-1,5,K)+CC(IC-1,4,K) - CH(I-1,K,1) = CC(I-1,1,K)+TR2+TR3 - CH(I,K,1) = CC(I,1,K)+TI2+TI3 - CR2 = CC(I-1,1,K)+TR11*TR2+TR12*TR3 - CI2 = CC(I,1,K)+TR11*TI2+TR12*TI3 - CR3 = CC(I-1,1,K)+TR12*TR2+TR11*TR3 - CI3 = CC(I,1,K)+TR12*TI2+TR11*TI3 - CR5 = TI11*TR5+TI12*TR4 - CI5 = TI11*TI5+TI12*TI4 - CR4 = TI12*TR5-TI11*TR4 - CI4 = TI12*TI5-TI11*TI4 - DR3 = CR3-CI4 - DR4 = CR3+CI4 - DI3 = CI3+CR4 - DI4 = CI3-CR4 - DR5 = CR2+CI5 - DR2 = CR2-CI5 - DI5 = CI2-CR5 - DI2 = CI2+CR5 - CH(I-1,K,2) = WA1(I-2)*DR2-WA1(I-1)*DI2 - CH(I,K,2) = WA1(I-2)*DI2+WA1(I-1)*DR2 - CH(I-1,K,3) = WA2(I-2)*DR3-WA2(I-1)*DI3 - CH(I,K,3) = WA2(I-2)*DI3+WA2(I-1)*DR3 - CH(I-1,K,4) = WA3(I-2)*DR4-WA3(I-1)*DI4 - CH(I,K,4) = WA3(I-2)*DI4+WA3(I-1)*DR4 - CH(I-1,K,5) = WA4(I-2)*DR5-WA4(I-1)*DI5 - CH(I,K,5) = WA4(I-2)*DI5+WA4(I-1)*DR5 - 102 CONTINUE - 103 CONTINUE - RETURN - END - SUBROUTINE DRADBG (IDO,IP,L1,IDL1,CC,C1,C2,CH,CH2,WA) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CH(IDO,L1,IP) ,CC(IDO,IP,L1) , - 1 C1(IDO,L1,IP) ,C2(IDL1,IP), - 2 CH2(IDL1,IP) ,WA(*) - DATA TPI/6.2831853071795864769252867665590057D0/ - ARG = TPI/DBLE(IP) - DCP = DCOS(ARG) - DSP = DSIN(ARG) - IDP2 = IDO+2 - NBD = (IDO-1)/2 - IPP2 = IP+2 - IPPH = (IP+1)/2 - IF (IDO .LT. L1) GO TO 103 - DO 102 K=1,L1 - DO 101 I=1,IDO - CH(I,K,1) = CC(I,1,K) - 101 CONTINUE - 102 CONTINUE - GO TO 106 - 103 DO 105 I=1,IDO - DO 104 K=1,L1 - CH(I,K,1) = CC(I,1,K) - 104 CONTINUE - 105 CONTINUE - 106 DO 108 J=2,IPPH - JC = IPP2-J - J2 = J+J - DO 107 K=1,L1 - CH(1,K,J) = CC(IDO,J2-2,K)+CC(IDO,J2-2,K) - CH(1,K,JC) = CC(1,J2-1,K)+CC(1,J2-1,K) - 107 CONTINUE - 108 CONTINUE - IF (IDO .EQ. 1) GO TO 116 - IF (NBD .LT. L1) GO TO 112 - DO 111 J=2,IPPH - JC = IPP2-J - DO 110 K=1,L1 - DO 109 I=3,IDO,2 - IC = IDP2-I - CH(I-1,K,J) = CC(I-1,2*J-1,K)+CC(IC-1,2*J-2,K) - CH(I-1,K,JC) = CC(I-1,2*J-1,K)-CC(IC-1,2*J-2,K) - CH(I,K,J) = CC(I,2*J-1,K)-CC(IC,2*J-2,K) - CH(I,K,JC) = CC(I,2*J-1,K)+CC(IC,2*J-2,K) - 109 CONTINUE - 110 CONTINUE - 111 CONTINUE - GO TO 116 - 112 DO 115 J=2,IPPH - JC = IPP2-J - DO 114 I=3,IDO,2 - IC = IDP2-I - DO 113 K=1,L1 - CH(I-1,K,J) = CC(I-1,2*J-1,K)+CC(IC-1,2*J-2,K) - CH(I-1,K,JC) = CC(I-1,2*J-1,K)-CC(IC-1,2*J-2,K) - CH(I,K,J) = CC(I,2*J-1,K)-CC(IC,2*J-2,K) - CH(I,K,JC) = CC(I,2*J-1,K)+CC(IC,2*J-2,K) - 113 CONTINUE - 114 CONTINUE - 115 CONTINUE - 116 AR1 = 1.0D0 - AI1 = 0.0D0 - DO 120 L=2,IPPH - LC = IPP2-L - AR1H = DCP*AR1-DSP*AI1 - AI1 = DCP*AI1+DSP*AR1 - AR1 = AR1H - DO 117 IK=1,IDL1 - C2(IK,L) = CH2(IK,1)+AR1*CH2(IK,2) - C2(IK,LC) = AI1*CH2(IK,IP) - 117 CONTINUE - DC2 = AR1 - DS2 = AI1 - AR2 = AR1 - AI2 = AI1 - DO 119 J=3,IPPH - JC = IPP2-J - AR2H = DC2*AR2-DS2*AI2 - AI2 = DC2*AI2+DS2*AR2 - AR2 = AR2H - DO 118 IK=1,IDL1 - C2(IK,L) = C2(IK,L)+AR2*CH2(IK,J) - C2(IK,LC) = C2(IK,LC)+AI2*CH2(IK,JC) - 118 CONTINUE - 119 CONTINUE - 120 CONTINUE - DO 122 J=2,IPPH - DO 121 IK=1,IDL1 - CH2(IK,1) = CH2(IK,1)+CH2(IK,J) - 121 CONTINUE - 122 CONTINUE - DO 124 J=2,IPPH - JC = IPP2-J - DO 123 K=1,L1 - CH(1,K,J) = C1(1,K,J)-C1(1,K,JC) - CH(1,K,JC) = C1(1,K,J)+C1(1,K,JC) - 123 CONTINUE - 124 CONTINUE - IF (IDO .EQ. 1) GO TO 132 - IF (NBD .LT. L1) GO TO 128 - DO 127 J=2,IPPH - JC = IPP2-J - DO 126 K=1,L1 - DO 125 I=3,IDO,2 - CH(I-1,K,J) = C1(I-1,K,J)-C1(I,K,JC) - CH(I-1,K,JC) = C1(I-1,K,J)+C1(I,K,JC) - CH(I,K,J) = C1(I,K,J)+C1(I-1,K,JC) - CH(I,K,JC) = C1(I,K,J)-C1(I-1,K,JC) - 125 CONTINUE - 126 CONTINUE - 127 CONTINUE - GO TO 132 - 128 DO 131 J=2,IPPH - JC = IPP2-J - DO 130 I=3,IDO,2 - DO 129 K=1,L1 - CH(I-1,K,J) = C1(I-1,K,J)-C1(I,K,JC) - CH(I-1,K,JC) = C1(I-1,K,J)+C1(I,K,JC) - CH(I,K,J) = C1(I,K,J)+C1(I-1,K,JC) - CH(I,K,JC) = C1(I,K,J)-C1(I-1,K,JC) - 129 CONTINUE - 130 CONTINUE - 131 CONTINUE - 132 CONTINUE - IF (IDO .EQ. 1) RETURN - DO 133 IK=1,IDL1 - C2(IK,1) = CH2(IK,1) - 133 CONTINUE - DO 135 J=2,IP - DO 134 K=1,L1 - C1(1,K,J) = CH(1,K,J) - 134 CONTINUE - 135 CONTINUE - IF (NBD .GT. L1) GO TO 139 - IS = -IDO - DO 138 J=2,IP - IS = IS+IDO - IDIJ = IS - DO 137 I=3,IDO,2 - IDIJ = IDIJ+2 - DO 136 K=1,L1 - C1(I-1,K,J) = WA(IDIJ-1)*CH(I-1,K,J)-WA(IDIJ)*CH(I,K,J) - C1(I,K,J) = WA(IDIJ-1)*CH(I,K,J)+WA(IDIJ)*CH(I-1,K,J) - 136 CONTINUE - 137 CONTINUE - 138 CONTINUE - GO TO 143 - 139 IS = -IDO - DO 142 J=2,IP - IS = IS+IDO - DO 141 K=1,L1 - IDIJ = IS - DO 140 I=3,IDO,2 - IDIJ = IDIJ+2 - C1(I-1,K,J) = WA(IDIJ-1)*CH(I-1,K,J)-WA(IDIJ)*CH(I,K,J) - C1(I,K,J) = WA(IDIJ-1)*CH(I,K,J)+WA(IDIJ)*CH(I-1,K,J) - 140 CONTINUE - 141 CONTINUE - 142 CONTINUE - 143 RETURN - END - SUBROUTINE DRADF2 (IDO,L1,CC,CH,WA1) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CH(IDO,2,L1) ,CC(IDO,L1,2) , - 1 WA1(*) - DO 101 K=1,L1 - CH(1,1,K) = CC(1,K,1)+CC(1,K,2) - CH(IDO,2,K) = CC(1,K,1)-CC(1,K,2) - 101 CONTINUE - IF (IDO-2) 107,105,102 - 102 IDP2 = IDO+2 - DO 104 K=1,L1 - DO 103 I=3,IDO,2 - IC = IDP2-I - TR2 = WA1(I-2)*CC(I-1,K,2)+WA1(I-1)*CC(I,K,2) - TI2 = WA1(I-2)*CC(I,K,2)-WA1(I-1)*CC(I-1,K,2) - CH(I,1,K) = CC(I,K,1)+TI2 - CH(IC,2,K) = TI2-CC(I,K,1) - CH(I-1,1,K) = CC(I-1,K,1)+TR2 - CH(IC-1,2,K) = CC(I-1,K,1)-TR2 - 103 CONTINUE - 104 CONTINUE - IF (MOD(IDO,2) .EQ. 1) RETURN - 105 DO 106 K=1,L1 - CH(1,2,K) = -CC(IDO,K,2) - CH(IDO,1,K) = CC(IDO,K,1) - 106 CONTINUE - 107 RETURN - END - SUBROUTINE DRADF3 (IDO,L1,CC,CH,WA1,WA2) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CH(IDO,3,L1) ,CC(IDO,L1,3) , - 1 WA1(*) ,WA2(*) - DATA TAUR,TAUI /-.5D0,.86602540378443864676372317075293618D0/ - DO 101 K=1,L1 - CR2 = CC(1,K,2)+CC(1,K,3) - CH(1,1,K) = CC(1,K,1)+CR2 - CH(1,3,K) = TAUI*(CC(1,K,3)-CC(1,K,2)) - CH(IDO,2,K) = CC(1,K,1)+TAUR*CR2 - 101 CONTINUE - IF (IDO .EQ. 1) RETURN - IDP2 = IDO+2 - DO 103 K=1,L1 - DO 102 I=3,IDO,2 - IC = IDP2-I - DR2 = WA1(I-2)*CC(I-1,K,2)+WA1(I-1)*CC(I,K,2) - DI2 = WA1(I-2)*CC(I,K,2)-WA1(I-1)*CC(I-1,K,2) - DR3 = WA2(I-2)*CC(I-1,K,3)+WA2(I-1)*CC(I,K,3) - DI3 = WA2(I-2)*CC(I,K,3)-WA2(I-1)*CC(I-1,K,3) - CR2 = DR2+DR3 - CI2 = DI2+DI3 - CH(I-1,1,K) = CC(I-1,K,1)+CR2 - CH(I,1,K) = CC(I,K,1)+CI2 - TR2 = CC(I-1,K,1)+TAUR*CR2 - TI2 = CC(I,K,1)+TAUR*CI2 - TR3 = TAUI*(DI2-DI3) - TI3 = TAUI*(DR3-DR2) - CH(I-1,3,K) = TR2+TR3 - CH(IC-1,2,K) = TR2-TR3 - CH(I,3,K) = TI2+TI3 - CH(IC,2,K) = TI3-TI2 - 102 CONTINUE - 103 CONTINUE - RETURN - END - SUBROUTINE DRADF4 (IDO,L1,CC,CH,WA1,WA2,WA3) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,L1,4) ,CH(IDO,4,L1) , - 1 WA1(*) ,WA2(*) ,WA3(*) - DATA HSQT2 /0.70710678118654752440084436210484904D0/ - DO 101 K=1,L1 - TR1 = CC(1,K,2)+CC(1,K,4) - TR2 = CC(1,K,1)+CC(1,K,3) - CH(1,1,K) = TR1+TR2 - CH(IDO,4,K) = TR2-TR1 - CH(IDO,2,K) = CC(1,K,1)-CC(1,K,3) - CH(1,3,K) = CC(1,K,4)-CC(1,K,2) - 101 CONTINUE - IF (IDO-2) 107,105,102 - 102 IDP2 = IDO+2 - DO 104 K=1,L1 - DO 103 I=3,IDO,2 - IC = IDP2-I - CR2 = WA1(I-2)*CC(I-1,K,2)+WA1(I-1)*CC(I,K,2) - CI2 = WA1(I-2)*CC(I,K,2)-WA1(I-1)*CC(I-1,K,2) - CR3 = WA2(I-2)*CC(I-1,K,3)+WA2(I-1)*CC(I,K,3) - CI3 = WA2(I-2)*CC(I,K,3)-WA2(I-1)*CC(I-1,K,3) - CR4 = WA3(I-2)*CC(I-1,K,4)+WA3(I-1)*CC(I,K,4) - CI4 = WA3(I-2)*CC(I,K,4)-WA3(I-1)*CC(I-1,K,4) - TR1 = CR2+CR4 - TR4 = CR4-CR2 - TI1 = CI2+CI4 - TI4 = CI2-CI4 - TI2 = CC(I,K,1)+CI3 - TI3 = CC(I,K,1)-CI3 - TR2 = CC(I-1,K,1)+CR3 - TR3 = CC(I-1,K,1)-CR3 - CH(I-1,1,K) = TR1+TR2 - CH(IC-1,4,K) = TR2-TR1 - CH(I,1,K) = TI1+TI2 - CH(IC,4,K) = TI1-TI2 - CH(I-1,3,K) = TI4+TR3 - CH(IC-1,2,K) = TR3-TI4 - CH(I,3,K) = TR4+TI3 - CH(IC,2,K) = TR4-TI3 - 103 CONTINUE - 104 CONTINUE - IF (MOD(IDO,2) .EQ. 1) RETURN - 105 CONTINUE - DO 106 K=1,L1 - TI1 = -HSQT2*(CC(IDO,K,2)+CC(IDO,K,4)) - TR1 = HSQT2*(CC(IDO,K,2)-CC(IDO,K,4)) - CH(IDO,1,K) = TR1+CC(IDO,K,1) - CH(IDO,3,K) = CC(IDO,K,1)-TR1 - CH(1,2,K) = TI1-CC(IDO,K,3) - CH(1,4,K) = TI1+CC(IDO,K,3) - 106 CONTINUE - 107 RETURN - END - SUBROUTINE DRADF5 (IDO,L1,CC,CH,WA1,WA2,WA3,WA4) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CC(IDO,L1,5) ,CH(IDO,5,L1) , - 1 WA1(*) ,WA2(*) ,WA3(*) ,WA4(*) - DATA TR11,TI11,TR12,TI12 / - 1 .30901699437494742410229341718281905D0, - 2 .95105651629515357211643933337938214D0, - 3 -.80901699437494742410229341718281906D0, - 4 .58778525229247312916870595463907276D0/ - DO 101 K=1,L1 - CR2 = CC(1,K,5)+CC(1,K,2) - CI5 = CC(1,K,5)-CC(1,K,2) - CR3 = CC(1,K,4)+CC(1,K,3) - CI4 = CC(1,K,4)-CC(1,K,3) - CH(1,1,K) = CC(1,K,1)+CR2+CR3 - CH(IDO,2,K) = CC(1,K,1)+TR11*CR2+TR12*CR3 - CH(1,3,K) = TI11*CI5+TI12*CI4 - CH(IDO,4,K) = CC(1,K,1)+TR12*CR2+TR11*CR3 - CH(1,5,K) = TI12*CI5-TI11*CI4 - 101 CONTINUE - IF (IDO .EQ. 1) RETURN - IDP2 = IDO+2 - DO 103 K=1,L1 - DO 102 I=3,IDO,2 - IC = IDP2-I - DR2 = WA1(I-2)*CC(I-1,K,2)+WA1(I-1)*CC(I,K,2) - DI2 = WA1(I-2)*CC(I,K,2)-WA1(I-1)*CC(I-1,K,2) - DR3 = WA2(I-2)*CC(I-1,K,3)+WA2(I-1)*CC(I,K,3) - DI3 = WA2(I-2)*CC(I,K,3)-WA2(I-1)*CC(I-1,K,3) - DR4 = WA3(I-2)*CC(I-1,K,4)+WA3(I-1)*CC(I,K,4) - DI4 = WA3(I-2)*CC(I,K,4)-WA3(I-1)*CC(I-1,K,4) - DR5 = WA4(I-2)*CC(I-1,K,5)+WA4(I-1)*CC(I,K,5) - DI5 = WA4(I-2)*CC(I,K,5)-WA4(I-1)*CC(I-1,K,5) - CR2 = DR2+DR5 - CI5 = DR5-DR2 - CR5 = DI2-DI5 - CI2 = DI2+DI5 - CR3 = DR3+DR4 - CI4 = DR4-DR3 - CR4 = DI3-DI4 - CI3 = DI3+DI4 - CH(I-1,1,K) = CC(I-1,K,1)+CR2+CR3 - CH(I,1,K) = CC(I,K,1)+CI2+CI3 - TR2 = CC(I-1,K,1)+TR11*CR2+TR12*CR3 - TI2 = CC(I,K,1)+TR11*CI2+TR12*CI3 - TR3 = CC(I-1,K,1)+TR12*CR2+TR11*CR3 - TI3 = CC(I,K,1)+TR12*CI2+TR11*CI3 - TR5 = TI11*CR5+TI12*CR4 - TI5 = TI11*CI5+TI12*CI4 - TR4 = TI12*CR5-TI11*CR4 - TI4 = TI12*CI5-TI11*CI4 - CH(I-1,3,K) = TR2+TR5 - CH(IC-1,2,K) = TR2-TR5 - CH(I,3,K) = TI2+TI5 - CH(IC,2,K) = TI5-TI2 - CH(I-1,5,K) = TR3+TR4 - CH(IC-1,4,K) = TR3-TR4 - CH(I,5,K) = TI3+TI4 - CH(IC,4,K) = TI4-TI3 - 102 CONTINUE - 103 CONTINUE - RETURN - END - SUBROUTINE DRADFG (IDO,IP,L1,IDL1,CC,C1,C2,CH,CH2,WA) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CH(IDO,L1,IP) ,CC(IDO,IP,L1) , - 1 C1(IDO,L1,IP) ,C2(IDL1,IP), - 2 CH2(IDL1,IP) ,WA(*) - DATA TPI/6.2831853071795864769252867665590057D0/ - ARG = TPI/DBLE(IP) - DCP = DCOS(ARG) - DSP = DSIN(ARG) - IPPH = (IP+1)/2 - IPP2 = IP+2 - IDP2 = IDO+2 - NBD = (IDO-1)/2 - IF (IDO .EQ. 1) GO TO 119 - DO 101 IK=1,IDL1 - CH2(IK,1) = C2(IK,1) - 101 CONTINUE - DO 103 J=2,IP - DO 102 K=1,L1 - CH(1,K,J) = C1(1,K,J) - 102 CONTINUE - 103 CONTINUE - IF (NBD .GT. L1) GO TO 107 - IS = -IDO - DO 106 J=2,IP - IS = IS+IDO - IDIJ = IS - DO 105 I=3,IDO,2 - IDIJ = IDIJ+2 - DO 104 K=1,L1 - CH(I-1,K,J) = WA(IDIJ-1)*C1(I-1,K,J)+WA(IDIJ)*C1(I,K,J) - CH(I,K,J) = WA(IDIJ-1)*C1(I,K,J)-WA(IDIJ)*C1(I-1,K,J) - 104 CONTINUE - 105 CONTINUE - 106 CONTINUE - GO TO 111 - 107 IS = -IDO - DO 110 J=2,IP - IS = IS+IDO - DO 109 K=1,L1 - IDIJ = IS - DO 108 I=3,IDO,2 - IDIJ = IDIJ+2 - CH(I-1,K,J) = WA(IDIJ-1)*C1(I-1,K,J)+WA(IDIJ)*C1(I,K,J) - CH(I,K,J) = WA(IDIJ-1)*C1(I,K,J)-WA(IDIJ)*C1(I-1,K,J) - 108 CONTINUE - 109 CONTINUE - 110 CONTINUE - 111 IF (NBD .LT. L1) GO TO 115 - DO 114 J=2,IPPH - JC = IPP2-J - DO 113 K=1,L1 - DO 112 I=3,IDO,2 - C1(I-1,K,J) = CH(I-1,K,J)+CH(I-1,K,JC) - C1(I-1,K,JC) = CH(I,K,J)-CH(I,K,JC) - C1(I,K,J) = CH(I,K,J)+CH(I,K,JC) - C1(I,K,JC) = CH(I-1,K,JC)-CH(I-1,K,J) - 112 CONTINUE - 113 CONTINUE - 114 CONTINUE - GO TO 121 - 115 DO 118 J=2,IPPH - JC = IPP2-J - DO 117 I=3,IDO,2 - DO 116 K=1,L1 - C1(I-1,K,J) = CH(I-1,K,J)+CH(I-1,K,JC) - C1(I-1,K,JC) = CH(I,K,J)-CH(I,K,JC) - C1(I,K,J) = CH(I,K,J)+CH(I,K,JC) - C1(I,K,JC) = CH(I-1,K,JC)-CH(I-1,K,J) - 116 CONTINUE - 117 CONTINUE - 118 CONTINUE - GO TO 121 - 119 DO 120 IK=1,IDL1 - C2(IK,1) = CH2(IK,1) - 120 CONTINUE - 121 DO 123 J=2,IPPH - JC = IPP2-J - DO 122 K=1,L1 - C1(1,K,J) = CH(1,K,J)+CH(1,K,JC) - C1(1,K,JC) = CH(1,K,JC)-CH(1,K,J) - 122 CONTINUE - 123 CONTINUE -C - AR1 = 1.0D0 - AI1 = 0.0D0 - DO 127 L=2,IPPH - LC = IPP2-L - AR1H = DCP*AR1-DSP*AI1 - AI1 = DCP*AI1+DSP*AR1 - AR1 = AR1H - DO 124 IK=1,IDL1 - CH2(IK,L) = C2(IK,1)+AR1*C2(IK,2) - CH2(IK,LC) = AI1*C2(IK,IP) - 124 CONTINUE - DC2 = AR1 - DS2 = AI1 - AR2 = AR1 - AI2 = AI1 - DO 126 J=3,IPPH - JC = IPP2-J - AR2H = DC2*AR2-DS2*AI2 - AI2 = DC2*AI2+DS2*AR2 - AR2 = AR2H - DO 125 IK=1,IDL1 - CH2(IK,L) = CH2(IK,L)+AR2*C2(IK,J) - CH2(IK,LC) = CH2(IK,LC)+AI2*C2(IK,JC) - 125 CONTINUE - 126 CONTINUE - 127 CONTINUE - DO 129 J=2,IPPH - DO 128 IK=1,IDL1 - CH2(IK,1) = CH2(IK,1)+C2(IK,J) - 128 CONTINUE - 129 CONTINUE -C - IF (IDO .LT. L1) GO TO 132 - DO 131 K=1,L1 - DO 130 I=1,IDO - CC(I,1,K) = CH(I,K,1) - 130 CONTINUE - 131 CONTINUE - GO TO 135 - 132 DO 134 I=1,IDO - DO 133 K=1,L1 - CC(I,1,K) = CH(I,K,1) - 133 CONTINUE - 134 CONTINUE - 135 DO 137 J=2,IPPH - JC = IPP2-J - J2 = J+J - DO 136 K=1,L1 - CC(IDO,J2-2,K) = CH(1,K,J) - CC(1,J2-1,K) = CH(1,K,JC) - 136 CONTINUE - 137 CONTINUE - IF (IDO .EQ. 1) RETURN - IF (NBD .LT. L1) GO TO 141 - DO 140 J=2,IPPH - JC = IPP2-J - J2 = J+J - DO 139 K=1,L1 - DO 138 I=3,IDO,2 - IC = IDP2-I - CC(I-1,J2-1,K) = CH(I-1,K,J)+CH(I-1,K,JC) - CC(IC-1,J2-2,K) = CH(I-1,K,J)-CH(I-1,K,JC) - CC(I,J2-1,K) = CH(I,K,J)+CH(I,K,JC) - CC(IC,J2-2,K) = CH(I,K,JC)-CH(I,K,J) - 138 CONTINUE - 139 CONTINUE - 140 CONTINUE - RETURN - 141 DO 144 J=2,IPPH - JC = IPP2-J - J2 = J+J - DO 143 I=3,IDO,2 - IC = IDP2-I - DO 142 K=1,L1 - CC(I-1,J2-1,K) = CH(I-1,K,J)+CH(I-1,K,JC) - CC(IC-1,J2-2,K) = CH(I-1,K,J)-CH(I-1,K,JC) - CC(I,J2-1,K) = CH(I,K,J)+CH(I,K,JC) - CC(IC,J2-2,K) = CH(I,K,JC)-CH(I,K,J) - 142 CONTINUE - 143 CONTINUE - 144 CONTINUE - RETURN - END - - SUBROUTINE DFFTB1 (N,C,CH,WA,IFAC) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CH(*) ,C(*) ,WA(*) ,IFAC(*) - NF = IFAC(2) - NA = 0 - L1 = 1 - IW = 1 - DO 116 K1=1,NF - IP = IFAC(K1+2) - L2 = IP*L1 - IDO = N/L2 - IDL1 = IDO*L1 - IF (IP .NE. 4) GO TO 103 - IX2 = IW+IDO - IX3 = IX2+IDO - IF (NA .NE. 0) GO TO 101 - CALL DRADB4 (IDO,L1,C,CH,WA(IW),WA(IX2),WA(IX3)) - GO TO 102 - 101 CALL DRADB4 (IDO,L1,CH,C,WA(IW),WA(IX2),WA(IX3)) - 102 NA = 1-NA - GO TO 115 - 103 IF (IP .NE. 2) GO TO 106 - IF (NA .NE. 0) GO TO 104 - CALL DRADB2 (IDO,L1,C,CH,WA(IW)) - GO TO 105 - 104 CALL DRADB2 (IDO,L1,CH,C,WA(IW)) - 105 NA = 1-NA - GO TO 115 - 106 IF (IP .NE. 3) GO TO 109 - IX2 = IW+IDO - IF (NA .NE. 0) GO TO 107 - CALL DRADB3 (IDO,L1,C,CH,WA(IW),WA(IX2)) - GO TO 108 - 107 CALL DRADB3 (IDO,L1,CH,C,WA(IW),WA(IX2)) - 108 NA = 1-NA - GO TO 115 - 109 IF (IP .NE. 5) GO TO 112 - IX2 = IW+IDO - IX3 = IX2+IDO - IX4 = IX3+IDO - IF (NA .NE. 0) GO TO 110 - CALL DRADB5 (IDO,L1,C,CH,WA(IW),WA(IX2),WA(IX3),WA(IX4)) - GO TO 111 - 110 CALL DRADB5 (IDO,L1,CH,C,WA(IW),WA(IX2),WA(IX3),WA(IX4)) - 111 NA = 1-NA - GO TO 115 - 112 IF (NA .NE. 0) GO TO 113 - CALL DRADBG (IDO,IP,L1,IDL1,C,C,C,CH,CH,WA(IW)) - GO TO 114 - 113 CALL DRADBG (IDO,IP,L1,IDL1,CH,CH,CH,C,C,WA(IW)) - 114 IF (IDO .EQ. 1) NA = 1-NA - 115 L1 = L2 - IW = IW+(IP-1)*IDO - 116 CONTINUE - IF (NA .EQ. 0) RETURN - DO 117 I=1,N - C(I) = CH(I) - 117 CONTINUE - RETURN - END - - - SUBROUTINE DFFTB (N,R,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION R(*) ,WSAVE(*) - IF (N .EQ. 1) RETURN - CALL DFFTB1 (N,R,WSAVE,WSAVE(N+1),WSAVE(2*N+1)) - RETURN - END - - SUBROUTINE DFFTF1 (N,C,CH,WA,IFAC) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION CH(*) ,C(*) ,WA(*) ,IFAC(*) - NF = IFAC(2) - NA = 1 - L2 = N - IW = N - DO 111 K1=1,NF - KH = NF-K1 - IP = IFAC(KH+3) - L1 = L2/IP - IDO = N/L2 - IDL1 = IDO*L1 - IW = IW-(IP-1)*IDO - NA = 1-NA - IF (IP .NE. 4) GO TO 102 - IX2 = IW+IDO - IX3 = IX2+IDO - IF (NA .NE. 0) GO TO 101 - CALL DRADF4 (IDO,L1,C,CH,WA(IW),WA(IX2),WA(IX3)) - GO TO 110 - 101 CALL DRADF4 (IDO,L1,CH,C,WA(IW),WA(IX2),WA(IX3)) - GO TO 110 - 102 IF (IP .NE. 2) GO TO 104 - IF (NA .NE. 0) GO TO 103 - CALL DRADF2 (IDO,L1,C,CH,WA(IW)) - GO TO 110 - 103 CALL DRADF2 (IDO,L1,CH,C,WA(IW)) - GO TO 110 - 104 IF (IP .NE. 3) GO TO 106 - IX2 = IW+IDO - IF (NA .NE. 0) GO TO 105 - CALL DRADF3 (IDO,L1,C,CH,WA(IW),WA(IX2)) - GO TO 110 - 105 CALL DRADF3 (IDO,L1,CH,C,WA(IW),WA(IX2)) - GO TO 110 - 106 IF (IP .NE. 5) GO TO 108 - IX2 = IW+IDO - IX3 = IX2+IDO - IX4 = IX3+IDO - IF (NA .NE. 0) GO TO 107 - CALL DRADF5 (IDO,L1,C,CH,WA(IW),WA(IX2),WA(IX3),WA(IX4)) - GO TO 110 - 107 CALL DRADF5 (IDO,L1,CH,C,WA(IW),WA(IX2),WA(IX3),WA(IX4)) - GO TO 110 - 108 IF (IDO .EQ. 1) NA = 1-NA - IF (NA .NE. 0) GO TO 109 - CALL DRADFG (IDO,IP,L1,IDL1,C,C,C,CH,CH,WA(IW)) - NA = 1 - GO TO 110 - 109 CALL DRADFG (IDO,IP,L1,IDL1,CH,CH,CH,C,C,WA(IW)) - NA = 0 - 110 L2 = L1 - 111 CONTINUE - IF (NA .EQ. 1) RETURN - DO 112 I=1,N - C(I) = CH(I) - 112 CONTINUE - RETURN - END - - - SUBROUTINE DFFTF (N,R,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION R(*) ,WSAVE(*) - IF (N .EQ. 1) RETURN - CALL DFFTF1 (N,R,WSAVE,WSAVE(N+1),WSAVE(2*N+1)) - RETURN - END - - SUBROUTINE DFFTI1 (N,WA,IFAC) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION WA(*) ,IFAC(*) ,NTRYH(4) - DATA NTRYH(1),NTRYH(2),NTRYH(3),NTRYH(4)/4,2,3,5/ - NL = N - NF = 0 - J = 0 - 101 J = J+1 - IF (J-4) 102,102,103 - 102 NTRY = NTRYH(J) - GO TO 104 - 103 NTRY = NTRY+2 - 104 NQ = NL/NTRY - NR = NL-NTRY*NQ - IF (NR) 101,105,101 - 105 NF = NF+1 - IFAC(NF+2) = NTRY - NL = NQ - IF (NTRY .NE. 2) GO TO 107 - IF (NF .EQ. 1) GO TO 107 - DO 106 I=2,NF - IB = NF-I+2 - IFAC(IB+2) = IFAC(IB+1) - 106 CONTINUE - IFAC(3) = 2 - 107 IF (NL .NE. 1) GO TO 104 - IFAC(1) = N - IFAC(2) = NF - TPI = 6.2831853071795864769252867665590057D0 - ARGH = TPI/DBLE(N) - IS = 0 - NFM1 = NF-1 - L1 = 1 - IF (NFM1 .EQ. 0) RETURN - DO 110 K1=1,NFM1 - IP = IFAC(K1+2) - LD = 0 - L2 = L1*IP - IDO = N/L2 - IPM = IP-1 - DO 109 J=1,IPM - LD = LD+L1 - I = IS - ARGLD = DBLE(LD)*ARGH - FI = 0.0D0 - DO 108 II=3,IDO,2 - I = I+2 - FI = FI+1.0D0 - ARG = FI*ARGLD - WA(I-1) = DCOS(ARG) - WA(I) = DSIN(ARG) - 108 CONTINUE - IS = IS+IDO - 109 CONTINUE - L1 = L2 - 110 CONTINUE - RETURN - END - - SUBROUTINE DFFTI (N,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION WSAVE(*) - IF (N .EQ. 1) RETURN - CALL DFFTI1 (N,WSAVE(N+1),WSAVE(2*N+1)) - RETURN - END - SUBROUTINE DSINQB (N,X,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION X(*) ,WSAVE(*) - IF (N .GT. 1) GO TO 101 - X(1) = 4.0D0*X(1) - RETURN - 101 NS2 = N/2 - DO 102 K=2,N,2 - X(K) = -X(K) - 102 CONTINUE - CALL DCOSQB (N,X,WSAVE) - DO 103 K=1,NS2 - KC = N-K - XHOLD = X(K) - X(K) = X(KC+1) - X(KC+1) = XHOLD - 103 CONTINUE - RETURN - END - SUBROUTINE DSINQF (N,X,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION X(*) ,WSAVE(*) - IF (N .EQ. 1) RETURN - NS2 = N/2 - DO 101 K=1,NS2 - KC = N-K - XHOLD = X(K) - X(K) = X(KC+1) - X(KC+1) = XHOLD - 101 CONTINUE - CALL DCOSQF (N,X,WSAVE) - DO 102 K=2,N,2 - X(K) = -X(K) - 102 CONTINUE - RETURN - END - SUBROUTINE DSINQI (N,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION WSAVE(*) - CALL DCOSQI (N,WSAVE) - RETURN - END - - SUBROUTINE DSINT1(N,WAR,WAS,XH,X,IFAC) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION WAR(*),WAS(*),X(*),XH(*),IFAC(*) - DATA SQRT3 /1.7320508075688772935274463415058723D0/ - DO 100 I=1,N - XH(I) = WAR(I) - WAR(I) = X(I) - 100 CONTINUE - IF (N-2) 101,102,103 - 101 XH(1) = XH(1)+XH(1) - GO TO 106 - 102 XHOLD = SQRT3*(XH(1)+XH(2)) - XH(2) = SQRT3*(XH(1)-XH(2)) - XH(1) = XHOLD - GO TO 106 - 103 NP1 = N+1 - NS2 = N/2 - X(1) = 0.0D0 - DO 104 K=1,NS2 - KC = NP1-K - T1 = XH(K)-XH(KC) - T2 = WAS(K)*(XH(K)+XH(KC)) - X(K+1) = T1+T2 - X(KC+1) = T2-T1 - 104 CONTINUE - MODN = MOD(N,2) - IF (MODN .NE. 0) X(NS2+2) = 4.0D0*XH(NS2+1) - CALL DFFTF1 (NP1,X,XH,WAR,IFAC) - XH(1) = .5D0*X(1) - DO 105 I=3,N,2 - XH(I-1) = -X(I) - XH(I) = XH(I-2)+X(I-1) - 105 CONTINUE - IF (MODN .NE. 0) GO TO 106 - XH(N) = -X(N+1) - 106 DO 107 I=1,N - X(I) = WAR(I) - WAR(I) = XH(I) - 107 CONTINUE - RETURN - END - - SUBROUTINE DSINT (N,X,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION X(*) ,WSAVE(*) - NP1 = N+1 - IW1 = N/2+1 - IW2 = IW1+NP1 - IW3 = IW2+NP1 - CALL DSINT1(N,X,WSAVE,WSAVE(IW1),WSAVE(IW2),WSAVE(IW3)) - RETURN - END - - SUBROUTINE DSINTI (N,WSAVE) - IMPLICIT DOUBLE PRECISION (A-H,O-Z) - DIMENSION WSAVE(*) - DATA PI /3.1415926535897932384626433832795028D0/ - IF (N .LE. 1) RETURN - NS2 = N/2 - NP1 = N+1 - DT = PI/DBLE(NP1) - DO 101 K=1,NS2 - WSAVE(K) = 2.0D0*DSIN(K*DT) - 101 CONTINUE - CALL DFFTI (NP1,WSAVE(NS2+1)) - RETURN - END diff --git a/scipy/linalg/src/id_dist/src/id_rand.f b/scipy/linalg/src/id_dist/src/id_rand.f deleted file mode 100644 index b49d2ef1f141..000000000000 --- a/scipy/linalg/src/id_dist/src/id_rand.f +++ /dev/null @@ -1,379 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine id_frand generates pseudorandom numbers -c drawn uniformly from [0,1]. id_frand is more -c efficient that id_srand, but cannot generate -c fewer than 55 pseudorandom numbers per call. -c -c routine id_srand generates pseudorandom numbers -c drawn uniformly from [0,1]. id_srand is less -c efficient that id_frand, but can generate -c fewer than 55 pseudorandom numbers per call. -c -c entry id_frandi initializes the seed values -c for routine id_frand. -c -c entry id_srandi initializes the seed values -c for routine id_srand. -c -c entry id_frando initializes the seed values -c for routine id_frand to their original values. -c -c entry id_srando initializes the seed values -c for routine id_srand to their original values. -c -c routine id_randperm generates a uniformly random permutation. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine id_frand(n,r) -c -c generates n pseudorandom numbers drawn uniformly from [0,1], -c via a very efficient lagged Fibonnaci method. -c Unlike routine id_srand, the present routine requires that -c n be at least 55. -c -c input: -c n -- number of pseudorandom numbers to generate -c -c output: -c r -- array of pseudorandom numbers -c -c _N.B._: n must be at least 55. -c -c reference: -c Press, Teukolsky, Vetterling, Flannery, "Numerical Recipes," -c 3rd edition, Cambridge University Press, 2007, -c Section 7.1.5. -c - implicit none - integer n,k - real*8 r(n),s(55),t(55),s0(55),x - save -c - data s/ - 1 0.2793574644042651d0, 0.1882566493961346d0, - 2 0.5202478134503912d0, 0.7568505373052146d0, - 3 0.5682465992936152d0, 0.5153148754383294d0, - 4 0.7806554095454596d0, 1.982474428974643d-2, - 5 0.2520464262278498d0, 0.6423784715775962d0, - 6 0.5802024387972178d0, 0.3784471040388249d0, - 7 7.839919528229308d-2, 0.6334519212594525d0, - 8 3.387627157788001d-2, 0.1709066283884670d0, - 9 0.4801610983518325d0, 0.8983424668099422d0, - * 5.358948687598758d-2, 0.1265377231771848d0, - 1 0.8979988627693677d0, 0.6470084038238917d0, - 2 0.3031709395541237d0, 0.6674702804438126d0, - 3 0.6318240977112699d0, 0.2235229633873050d0, - 4 0.2784629939177633d0, 0.2365462014457445d0, - 5 0.7226213454977284d0, 0.8986523045307989d0, - 6 0.5488233229247885d0, 0.3924605412141200d0, - 7 0.6288356378374988d0, 0.6370664115760445d0, - 8 0.5925600062791174d0, 0.4322113919396362d0, - 9 0.9766098520360393d0, 0.5168619893947437d0, - * 0.6799970440779681d0, 0.4196004604766881d0, - 1 0.2324473089903044d0, 0.1439046416143282d0, - 2 0.4670307948601256d0, 0.7076498261128343d0, - 3 0.9458030397562582d0, 0.4557892460080424d0, - 4 0.3905930854589403d0, 0.3361770064397268d0, - 5 0.8303274937900278d0, 0.3041110304032945d0, - 6 0.5752684022049654d0, 7.985703137991175d-2, - 7 0.5522643936454465d0, 1.956754937251801d-2, - 8 0.9920272858340107d0/ -c - data s0/ - 1 0.2793574644042651d0, 0.1882566493961346d0, - 2 0.5202478134503912d0, 0.7568505373052146d0, - 3 0.5682465992936152d0, 0.5153148754383294d0, - 4 0.7806554095454596d0, 1.982474428974643d-2, - 5 0.2520464262278498d0, 0.6423784715775962d0, - 6 0.5802024387972178d0, 0.3784471040388249d0, - 7 7.839919528229308d-2, 0.6334519212594525d0, - 8 3.387627157788001d-2, 0.1709066283884670d0, - 9 0.4801610983518325d0, 0.8983424668099422d0, - * 5.358948687598758d-2, 0.1265377231771848d0, - 1 0.8979988627693677d0, 0.6470084038238917d0, - 2 0.3031709395541237d0, 0.6674702804438126d0, - 3 0.6318240977112699d0, 0.2235229633873050d0, - 4 0.2784629939177633d0, 0.2365462014457445d0, - 5 0.7226213454977284d0, 0.8986523045307989d0, - 6 0.5488233229247885d0, 0.3924605412141200d0, - 7 0.6288356378374988d0, 0.6370664115760445d0, - 8 0.5925600062791174d0, 0.4322113919396362d0, - 9 0.9766098520360393d0, 0.5168619893947437d0, - * 0.6799970440779681d0, 0.4196004604766881d0, - 1 0.2324473089903044d0, 0.1439046416143282d0, - 2 0.4670307948601256d0, 0.7076498261128343d0, - 3 0.9458030397562582d0, 0.4557892460080424d0, - 4 0.3905930854589403d0, 0.3361770064397268d0, - 5 0.8303274937900278d0, 0.3041110304032945d0, - 6 0.5752684022049654d0, 7.985703137991175d-2, - 7 0.5522643936454465d0, 1.956754937251801d-2, - 8 0.9920272858340107d0/ -c -c - do k = 1,24 -c - x = s(k+31)-s(k) - if(x .lt. 0) x = x+1 - r(k) = x -c - enddo ! k -c -c - do k = 25,55 -c - x = r(k-24)-s(k) - if(x .lt. 0) x = x+1 - r(k) = x -c - enddo ! k -c -c - do k = 56,n -c - x = r(k-24)-r(k-55) - if(x .lt. 0) x = x+1 - r(k) = x -c - enddo ! k -c -c - do k = 1,55 - s(k) = r(n-55+k) - enddo ! k -c -c - return -c -c -c - entry id_frandi(t) -c -c initializes the seed values in s -c (any appropriately random numbers will do). -c -c input: -c t -- values to copy into s -c - do k = 1,55 - s(k) = t(k) - enddo ! k -c - return -c -c -c - entry id_frando() -c -c initializes the seed values in s to their original values. -c - do k = 1,55 - s(k) = s0(k) - enddo ! k -c - return - end -c -c -c -c - subroutine id_srand(n,r) -c -c generates n pseudorandom numbers drawn uniformly from [0,1], -c via a very efficient lagged Fibonnaci method. -c Unlike routine id_frand, the present routine does not requires -c that n be at least 55. -c -c input: -c n -- number of pseudorandom numbers to generate -c -c output: -c r -- array of pseudorandom numbers -c -c reference: -c Press, Teukolsky, Vetterling, Flannery, "Numerical Recipes," -c 3rd edition, Cambridge University Press, 2007, -c Section 7.1.5. -c - implicit none - integer n,k,l,m - real*8 s(55),r(n),s0(55),t(55),x - save -c - data l/55/,m/24/ -c - data s/ - 1 0.8966049453474352d0, 0.7789471911260157d0, - 2 0.6071529762908476d0, 0.8287077988663865d0, - 3 0.8249336255502409d0, 0.5735259423199479d0, - 4 0.2436346323812991d0, 0.2656149927259701d0, - 5 0.6594784809929011d0, 0.3432392503145575d0, - 6 0.5051287353012308d0, 0.1444493249757482d0, - 7 0.7643753221285416d0, 0.4843422506977382d0, - 8 0.4427513254774826d0, 0.2965991475108561d0, - 9 0.2650513544474467d0, 2.768759325778929d-2, - * 0.6106305243078063d0, 0.4246918885003141d0, - 1 0.2863757386932874d0, 0.6211983878375777d0, - 2 0.7534336463880467d0, 0.7471458603576737d0, - 3 0.2017455446928328d0, 0.9334235874832779d0, - 4 0.6343440435422822d0, 0.8819824804812527d0, - 5 1.994761401222460d-2, 0.7023693520374801d0, - 6 0.6010088924817263d0, 6.498095955562046d-2, - 7 0.3090915456102685d0, 0.3014924769096677d0, - 8 0.5820726822705102d0, 0.3630527222866207d0, - 9 0.3787166916242271d0, 0.3932772088505305d0, - * 0.5570720335382000d0, 0.9712062146993835d0, - 1 0.1338293907964648d0, 0.1857441593107195d0, - 2 0.9102503893692572d0, 0.2623337538798778d0, - 3 0.3542828591321135d0, 2.246286032456513d-2, - 4 0.7935703170405717d0, 6.051464729640567d-2, - 5 0.7271929955172147d0, 1.968513010678739d-3, - 6 0.4914223624495486d0, 0.8730023176789450d0, - 7 0.9639777091743168d0, 0.1084256187532446d0, - 8 0.8539399636754000d0/ -c - data s0/ - 1 0.8966049453474352d0, 0.7789471911260157d0, - 2 0.6071529762908476d0, 0.8287077988663865d0, - 3 0.8249336255502409d0, 0.5735259423199479d0, - 4 0.2436346323812991d0, 0.2656149927259701d0, - 5 0.6594784809929011d0, 0.3432392503145575d0, - 6 0.5051287353012308d0, 0.1444493249757482d0, - 7 0.7643753221285416d0, 0.4843422506977382d0, - 8 0.4427513254774826d0, 0.2965991475108561d0, - 9 0.2650513544474467d0, 2.768759325778929d-2, - * 0.6106305243078063d0, 0.4246918885003141d0, - 1 0.2863757386932874d0, 0.6211983878375777d0, - 2 0.7534336463880467d0, 0.7471458603576737d0, - 3 0.2017455446928328d0, 0.9334235874832779d0, - 4 0.6343440435422822d0, 0.8819824804812527d0, - 5 1.994761401222460d-2, 0.7023693520374801d0, - 6 0.6010088924817263d0, 6.498095955562046d-2, - 7 0.3090915456102685d0, 0.3014924769096677d0, - 8 0.5820726822705102d0, 0.3630527222866207d0, - 9 0.3787166916242271d0, 0.3932772088505305d0, - * 0.5570720335382000d0, 0.9712062146993835d0, - 1 0.1338293907964648d0, 0.1857441593107195d0, - 2 0.9102503893692572d0, 0.2623337538798778d0, - 3 0.3542828591321135d0, 2.246286032456513d-2, - 4 0.7935703170405717d0, 6.051464729640567d-2, - 5 0.7271929955172147d0, 1.968513010678739d-3, - 6 0.4914223624495486d0, 0.8730023176789450d0, - 7 0.9639777091743168d0, 0.1084256187532446d0, - 8 0.8539399636754000d0/ -c -c - do k = 1,n -c -c Run one step of the recurrence. -c - x = s(m)-s(l) - if(x .lt. 0) x = x+1 - s(l) = x - r(k) = x -c -c Decrement l and m. -c - l = l-1 - m = m-1 -c -c Circle back to the end if required. -c - if(l .eq. 0) l = 55 - if(m .eq. 0) m = 55 -c - enddo ! k -c -c - return -c -c -c - entry id_srandi(t) -c -c initializes the seed values in s -c (any appropriately random numbers will do). -c -c input: -c t -- values to copy into s -c - do k = 1,55 - s(k) = t(k) - enddo ! k -c - l = 55 - m = 24 -c - return -c -c -c - entry id_srando() -c -c initializes the seed values in s to their original values. -c - do k = 1,55 - s(k) = s0(k) - enddo ! k -c - l = 55 - m = 24 -c - return - end -c -c -c -c - subroutine id_randperm(n,ind) -c -c draws a permutation ind uniformly at random from the group -c of all permutations of n objects. -c -c input: -c n -- length of ind -c -c output: -c ind -- random permutation of length n -c - implicit none - integer n,ind(n),m,j,iswap - real*8 r -c -c -c Initialize ind. -c - do j = 1,n - ind(j) = j - enddo ! j -c -c -c Shuffle ind via the Fisher-Yates (Knuth/Durstenfeld) algorithm. -c - do m = n,2,-1 -c -c Draw an integer uniformly at random from 1, 2, ..., m. -c - call id_srand(1,r) - j = m*r+1 -c -c Uncomment the following line if r could equal 1: -c if(j .eq. m+1) j = m -c -c Swap ind(j) and ind(m). -c - iswap = ind(j) - ind(j) = ind(m) - ind(m) = iswap -c - enddo ! m -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/id_rtrans.f b/scipy/linalg/src/id_dist/src/id_rtrans.f deleted file mode 100644 index a970d7fb5317..000000000000 --- a/scipy/linalg/src/id_dist/src/id_rtrans.f +++ /dev/null @@ -1,746 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idd_random_transf applies rapidly -c a random orthogonal matrix to a user-supplied vector. -c -c routine idd_random_transf_inverse applies rapidly -c the inverse of the operator applied -c by routine idd_random_transf. -c -c routine idz_random_transf applies rapidly -c a random unitary matrix to a user-supplied vector. -c -c routine idz_random_transf_inverse applies rapidly -c the inverse of the operator applied -c by routine idz_random_transf. -c -c routine idd_random_transf_init initializes data -c for routines idd_random_transf and idd_random_transf_inverse. -c -c routine idz_random_transf_init initializes data -c for routines idz_random_transf and idz_random_transf_inverse. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c -c - subroutine idd_random_transf_init(nsteps,n,w,keep) - implicit real *8 (a-h,o-z) - save - dimension w(*) -c -c prepares and stores in array w the data used -c by the routines idd_random_transf and idd_random_transf_inverse -c to apply rapidly a random orthogonal matrix -c to an arbitrary user-specified vector. -c -c input: -c nsteps -- the degree of randomness of the operator -c to be applied -c n -- the size of the matrix to be applied -c -c output: -c w -- the first keep elements of w contain all the data -c to be used by routines idd_random_tranf -c and idd_random_transf_inverse. Please note that -c the number of elements used by the present routine -c is also equal to keep. This array should be at least -c 3*nsteps*n + 2*n + n/4 + 50 real*8 elements long. -c keep - the number of elements in w actually used -c by the present routine; keep is also the number -c of elements that must not be changed between the call -c to this routine and subsequent calls to routines -c idd_random_transf and idd_random_transf_inverse. -c -c -c . . . allocate memory -c - ninire=2 -c - ialbetas=10 - lalbetas=2*n*nsteps+10 -c - iixs=ialbetas+lalbetas - lixs=n*nsteps/ninire+10 -c - iww=iixs+lixs - lww=2*n+n/4+20 -c - keep=iww+lww -c - w(1)=ialbetas+0.1 - w(2)=iixs+0.1 - w(3)=nsteps+0.1 - w(4)=iww+0.1 - w(5)=n+0.1 -c - call idd_random_transf_init0(nsteps,n,w(ialbetas),w(iixs)) -c - return - end -c -c -c -c -c - subroutine idz_random_transf_init(nsteps,n,w,keep) - implicit real *8 (a-h,o-z) - save - dimension w(*) -c -c prepares and stores in array w the data used -c by routines idz_random_transf and idz_random_transf_inverse -c to apply rapidly a random unitary matrix -c to an arbitrary user-specified vector. -c -c input: -c nsteps -- the degree of randomness of the operator -c to be applied -c n -- the size of the matrix to be applied -c -c output: -c w -- the first keep elements of w contain all the data -c to be used by routines idz_random_transf -c and idz_random_transf_inverse. Please note that -c the number of elements used by the present routine -c is also equal to keep. This array should be at least -c 5*nsteps*n + 2*n + n/4 + 60 real*8 elements long. -c keep - the number of elements in w actually used -c by the present routine; keep is also the number -c of elements that must not be changed between the call -c to this routine and subsequent calls to routines -c idz_random_transf and idz_random_transf_inverse. -c -c -c . . . allocate memory -c - ninire=2 -c - ialbetas=10 - lalbetas=2*n*nsteps+10 -c - igammas=ialbetas+lalbetas - lgammas=2*n*nsteps+10 -c - iixs=igammas+lgammas - lixs=n*nsteps/ninire+10 -c - iww=iixs+lixs - lww=2*n+n/4+20 -c - keep=iww+lww -c - w(1)=ialbetas+0.1 - w(2)=iixs+0.1 - w(3)=nsteps+0.1 - w(4)=iww+0.1 - w(5)=n+0.1 - w(6)=igammas+0.1 -c - call idz_random_transf_init0(nsteps,n,w(ialbetas), - 1 w(igammas),w(iixs)) -c - return - end -c -c -c -c -c - subroutine idd_random_transf(x,y,w) - implicit real *8 (a-h,o-z) - save - dimension x(*),y(*),w(*) -c -c applies rapidly a random orthogonal matrix -c to the user-specified real vector x, -c using the data in array w stored there by a preceding -c call to routine idd_random_transf_init. -c -c input: -c x -- the vector of length n to which the random matrix is -c to be applied -c w -- array containing all initialization data -c -c output: -c y -- the result of applying the random matrix to x -c -c -c . . . allocate memory -c - ialbetas=w(1) - iixs=w(2) - nsteps=w(3) - iww=w(4) - n=w(5) -c - call idd_random_transf0(nsteps,x,y,n,w(iww), - 1 w(ialbetas),w(iixs)) -c - return - end -c -c -c -c -c - subroutine idd_random_transf_inverse(x,y,w) - implicit real *8 (a-h,o-z) - save - dimension x(*),y(*),w(*) -c -c applies rapidly a random orthogonal matrix -c to the user-specified real vector x, -c using the data in array w stored there by a preceding -c call to routine idd_random_transf_init. -c The transformation applied by the present routine is -c the inverse of the transformation applied -c by routine idd_random_transf. -c -c input: -c x -- the vector of length n to which the random matrix is -c to be applied -c w -- array containing all initialization data -c -c output: -c y -- the result of applying the random matrix to x -c -c -c . . . allocate memory -c - ialbetas=w(1) - iixs=w(2) - nsteps=w(3) - iww=w(4) - n=w(5) -c - call idd_random_transf0_inv(nsteps,x,y,n,w(iww), - 1 w(ialbetas),w(iixs)) -c - return - end -c -c -c -c -c - subroutine idz_random_transf(x,y,w) - implicit real *8 (a-h,o-z) - save - complex *16 x(*),y(*) - dimension w(*) -c -c applies rapidly a random unitary matrix -c to the user-specified vector x, -c using the data in array w stored there by a preceding -c call to routine idz_random_transf_init. -c -c input: -c x -- the vector of length n to which the random matrix is -c to be applied -c w -- array containing all initialization data -c -c output: -c y -- the result of applying the random matrix to x -c -c -c . . . allocate memory -c - ialbetas=w(1) - iixs=w(2) - nsteps=w(3) - iww=w(4) - n=w(5) - igammas=w(6) -c - call idz_random_transf0(nsteps,x,y,n,w(iww),w(ialbetas), - 1 w(igammas),w(iixs)) -c - return - end -c -c -c -c -c - subroutine idz_random_transf_inverse(x,y,w) - implicit real *8 (a-h,o-z) - save - complex *16 x(*),y(*) - dimension w(*) -c -c applies rapidly a random unitary matrix -c to the user-specified vector x, -c using the data in array w stored there by a preceding -c call to routine idz_random_transf_init. -c The transformation applied by the present routine is -c the inverse of the transformation applied -c by routine idz_random_transf. -c -c input: -c x -- the vector of length n to which the random matrix is -c to be applied -c w -- array containing all initialization data -c -c output: -c y -- the result of applying the random matrix to x -c -c -c . . . allocate memory -c - ialbetas=w(1) - iixs=w(2) - nsteps=w(3) - iww=w(4) - n=w(5) - igammas=w(6) -c - call idz_random_transf0_inv(nsteps,x,y,n,w(iww), - 1 w(ialbetas),w(igammas),w(iixs)) -c - return - end -c -c -c -c -c - subroutine idd_random_transf0_inv(nsteps,x,y,n,w2,albetas,iixs) - implicit real *8 (a-h,o-z) - save - dimension x(*),y(*),w2(*),albetas(2,n,*),iixs(n,*) -c -c routine idd_random_transf_inverse serves as a memory wrapper -c for the present routine; see routine idd_random_transf_inverse -c for documentation. -c - do 1200 i=1,n -c - w2(i)=x(i) - 1200 continue -c - do 2000 ijk=nsteps,1,-1 -c - call idd_random_transf00_inv(w2,y,n,albetas(1,1,ijk), - 1 iixs(1,ijk) ) -c - do 1400 j=1,n -c - w2(j)=y(j) - 1400 continue - 2000 continue -c - return - end -c -c -c -c -c - subroutine idd_random_transf00_inv(x,y,n,albetas,ixs) - implicit real *8 (a-h,o-z) - save - dimension x(*),y(*),albetas(2,*),ixs(*) -c -c implements one step of the random transform required -c by routine idd_random_transf0_inv (please see the latter). -c -c -c implement 2 \times 2 matrices -c - do 1600 i=1,n - y(i)=x(i) - 1600 continue -c - do 1800 i=n-1,1,-1 -c - alpha=albetas(1,i) - beta=albetas(2,i) -c - a=y(i) - b=y(i+1) -c - y(i)=alpha*a-beta*b - y(i+1)=beta*a+alpha*b - 1800 continue -c -c implement the permutation -c - do 2600 i=1,n -c - j=ixs(i) - x(j)=y(i) - 2600 continue -c - do 2800 i=1,n -c - y(i)=x(i) - 2800 continue -c - return - end -c -c -c -c -c - subroutine idz_random_transf0_inv(nsteps,x,y,n,w2,albetas, - 1 gammas,iixs) - implicit real *8 (a-h,o-z) - save - complex *16 x(*),y(*),w2(*),gammas(n,*) - dimension albetas(2,n,*),iixs(n,*) -c -c routine idz_random_transf_inverse serves as a memory wrapper -c for the present routine; please see routine -c idz_random_transf_inverse for documentation. -c - do 1200 i=1,n -c - w2(i)=x(i) - 1200 continue -c - do 2000 ijk=nsteps,1,-1 -c - call idz_random_transf00_inv(w2,y,n,albetas(1,1,ijk), - 1 gammas(1,ijk),iixs(1,ijk) ) -c - do 1400 j=1,n -c - w2(j)=y(j) - 1400 continue - 2000 continue -c - return - end -c -c -c -c -c - subroutine idz_random_transf00_inv(x,y,n,albetas,gammas,ixs) - implicit real *8 (a-h,o-z) - save - complex *16 x(*),y(*),gammas(*),a,b - dimension albetas(2,*),ixs(*) -c -c implements one step of the random transform -c required by routine idz_random_transf0_inv -c (please see the latter). -c -c implement 2 \times 2 matrices -c - do 1600 i=n-1,1,-1 -c - alpha=albetas(1,i) - beta=albetas(2,i) -c - a=x(i) - b=x(i+1) -c - x(i)=alpha*a-beta*b - x(i+1)=beta*a+alpha*b - 1600 continue -c -c implement the permutation -c and divide by the random numbers on the unit circle -c (or, equivalently, multiply by their conjugates) -c - do 1800 i=1,n -c - j=ixs(i) - y(j)=x(i)*conjg(gammas(i)) - 1800 continue -c - return - end -c -c -c -c -c - subroutine idd_random_transf0(nsteps,x,y,n,w2,albetas,iixs) - implicit real *8 (a-h,o-z) - save - dimension x(*),y(*),w2(*),albetas(2,n,*),iixs(n,*) -c -c routine idd_random_transf serves as a memory wrapper -c for the present routine; please see routine idd_random_transf -c for documentation. -c - do 1200 i=1,n -c - w2(i)=x(i) - 1200 continue -c - do 2000 ijk=1,nsteps -c - call idd_random_transf00(w2,y,n,albetas(1,1,ijk),iixs(1,ijk) ) -c - do 1400 j=1,n -c - w2(j)=y(j) - 1400 continue - 2000 continue -c - return - end -c -c -c -c -c - subroutine idd_random_transf00(x,y,n,albetas,ixs) - implicit real *8 (a-h,o-z) - save - dimension x(*),y(*),albetas(2,*),ixs(*) -c -c implements one step of the random transform -c required by routine idd_random_transf0 (please see the latter). -c -c implement the permutation -c - do 1600 i=1,n -c - j=ixs(i) - y(i)=x(j) - 1600 continue -c -c implement 2 \times 2 matrices -c - do 1800 i=1,n-1 -c - alpha=albetas(1,i) - beta=albetas(2,i) -c - a=y(i) - b=y(i+1) -c - y(i)=alpha*a+beta*b - y(i+1)=-beta*a+alpha*b - 1800 continue -c - return - end -c -c -c -c -c - subroutine idz_random_transf_init0(nsteps,n,albetas,gammas,ixs) - implicit real *8 (a-h,o-z) - save - dimension albetas(2,n,*),ixs(n,*) - complex *16 gammas(n,*) -c -c routine idz_random_transf_init serves as a memory wrapper -c for the present routine; please see routine -c idz_random_transf_init for documentation. -c - do 2000 ijk=1,nsteps -c - call idz_random_transf_init00(n,albetas(1,1,ijk), - 1 gammas(1,ijk),ixs(1,ijk) ) - 2000 continue - return - end -c -c -c -c -c - subroutine idz_random_transf_init00(n,albetas,gammas,ixs) - implicit real *8 (a-h,o-z) - save - dimension albetas(2,*),gammas(*),ixs(*) -c -c constructs one stage of the random transform -c initialized by routine idz_random_transf_init0 -c (please see the latter). -c - done=1 - twopi=2*4*atan(done) -c -c construct the random permutation -c - ifrepeat=0 - call id_randperm(n,ixs) -c -c construct the random variables -c - call id_srand(2*n,albetas) - call id_srand(2*n,gammas) -c - do 1300 i=1,n -c - albetas(1,i)=2*albetas(1,i)-1 - albetas(2,i)=2*albetas(2,i)-1 - gammas(2*i-1)=2*gammas(2*i-1)-1 - gammas(2*i)=2*gammas(2*i)-1 - 1300 continue -c -c construct the random 2 \times 2 transformations -c - do 1400 i=1,n -c - d=albetas(1,i)**2+albetas(2,i)**2 - d=1/sqrt(d) - albetas(1,i)=albetas(1,i)*d - albetas(2,i)=albetas(2,i)*d - 1400 continue -c -c construct the random multipliers on the unit circle -c - do 1500 i=1,n -c - d=gammas(2*i-1)**2+gammas(2*i)**2 - d=1/sqrt(d) -c -c fill the real part -c - gammas(2*i-1)=gammas(2*i-1)*d -c -c fill the imaginary part -c - gammas(2*i)=gammas(2*i)*d - 1500 continue -c - return - end -c -c -c -c -c - subroutine idz_random_transf0(nsteps,x,y,n,w2,albetas, - 1 gammas,iixs) - implicit real *8 (a-h,o-z) - save - complex *16 x(*),y(*),w2(*),gammas(n,*) - dimension albetas(2,n,*),iixs(n,*) -c -c routine idz_random_transf serves as a memory wrapper -c for the present routine; please see routine idz_random_transf -c for documentation. -c - do 1200 i=1,n -c - w2(i)=x(i) - 1200 continue -c - do 2000 ijk=1,nsteps -c - call idz_random_transf00(w2,y,n,albetas(1,1,ijk), - 1 gammas(1,ijk),iixs(1,ijk) ) - do 1400 j=1,n -c - w2(j)=y(j) - 1400 continue - 2000 continue -c - return - end -c -c -c -c -c - subroutine idz_random_transf00(x,y,n,albetas,gammas,ixs) - implicit real *8 (a-h,o-z) - save - complex *16 x(*),y(*),gammas(*),a,b - dimension albetas(2,*),ixs(*) -c -c implements one step of the random transform -c required by routine idz_random_transf0 (please see the latter). -c -c implement the permutation -c and multiply by the random numbers -c on the unit circle -c - do 1600 i=1,n -c - j=ixs(i) - y(i)=x(j)*gammas(i) - 1600 continue -c -c implement 2 \times 2 matrices -c - do 2600 i=1,n-1 -c - alpha=albetas(1,i) - beta=albetas(2,i) -c - a=y(i) - b=y(i+1) -c - y(i)=alpha*a+beta*b - y(i+1)=-beta*a+alpha*b - 2600 continue -c - return - end -c -c -c -c -c - subroutine idd_random_transf_init0(nsteps,n,albetas,ixs) - implicit real *8 (a-h,o-z) - save - dimension albetas(2,n,*),ixs(n,*) -c -c routine idd_random_transf_init serves as a memory wrapper -c for the present routine; please see routine -c idd_random_transf_init for documentation. -c - do 2000 ijk=1,nsteps -c - call idd_random_transf_init00(n,albetas(1,1,ijk),ixs(1,ijk) ) - 2000 continue - return - end -c -c -c -c -c - subroutine idd_random_transf_init00(n,albetas,ixs) - implicit real *8 (a-h,o-z) - save - dimension albetas(2,*),ixs(*) -c -c constructs one stage of the random transform -c initialized by routine idd_random_transf_init0 -c (please see the latter). -c -c construct the random permutation -c - ifrepeat=0 - call id_randperm(n,ixs) -c -c construct the random variables -c - call id_srand(2*n,albetas) -c - do 1300 i=1,n -c - albetas(1,i)=2*albetas(1,i)-1 - albetas(2,i)=2*albetas(2,i)-1 - 1300 continue -c -c construct the random 2 \times 2 transformations -c - do 1400 i=1,n -c - d=albetas(1,i)**2+albetas(2,i)**2 - d=1/sqrt(d) - albetas(1,i)=albetas(1,i)*d - albetas(2,i)=albetas(2,i)*d - 1400 continue - return - end diff --git a/scipy/linalg/src/id_dist/src/idd_frm.f b/scipy/linalg/src/id_dist/src/idd_frm.f deleted file mode 100644 index 0a13112eb8e0..000000000000 --- a/scipy/linalg/src/id_dist/src/idd_frm.f +++ /dev/null @@ -1,525 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idd_frm transforms a vector via a composition -c of Rokhlin's random transform, random subselection, and an FFT. -c -c routine idd_sfrm transforms a vector into a vector -c of specified length via a composition -c of Rokhlin's random transform, random subselection, and an FFT. -c -c routine idd_frmi initializes routine idd_frm. -c -c routine idd_sfrmi initializes routine idd_sfrm. -c -c routine idd_pairsamps calculates the indices of the pairs -c of integers to which the individual integers -c in a specified set belong. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idd_frm(m,n,w,x,y) -c -c transforms x into y via a composition -c of Rokhlin's random transform, random subselection, and an FFT. -c In contrast to routine idd_sfrm, the present routine works best -c when the length of the transformed vector is the integer n -c output by routine idd_frmi, or when the length -c is not specified, but instead determined a posteriori -c using the output of the present routine. The transformed vector -c output by the present routine is randomly permuted. -c -c input: -c m -- length of x -c n -- greatest integer expressible as a positive integer power -c of 2 that is less than or equal to m, as obtained -c from the routine idd_frmi; n is the length of y -c w -- initialization array constructed by routine idd_frmi -c x -- vector to be transformed -c -c output: -c y -- transform of x -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,iw,n,k - real*8 w(17*m+70),x(m),y(n) -c -c -c Apply Rokhlin's random transformation to x, obtaining -c w(16*m+71 : 17*m+70). -c - iw = w(3+m+n) - call idd_random_transf(x,w(16*m+70+1),w(iw)) -c -c -c Subselect from w(16*m+71 : 17*m+70) to obtain y. -c - call idd_subselect(n,w(3),m,w(16*m+70+1),y) -c -c -c Copy y into w(16*m+71 : 16*m+n+70). -c - do k = 1,n - w(16*m+70+k) = y(k) - enddo ! k -c -c -c Fourier transform w(16*m+71 : 16*m+n+70). -c - call dfftf(n,w(16*m+70+1),w(4+m+n)) -c -c -c Permute w(16*m+71 : 16*m+n+70) to obtain y. -c - call idd_permute(n,w(3+m),w(16*m+70+1),y) -c -c - return - end -c -c -c -c - subroutine idd_sfrm(l,m,n,w,x,y) -c -c transforms x into y via a composition -c of Rokhlin's random transform, random subselection, and an FFT. -c In contrast to routine idd_frm, the present routine works best -c when the length l of the transformed vector is known a priori. -c -c input: -c l -- length of y; l must be less than or equal to n -c m -- length of x -c n -- greatest integer expressible as a positive integer power -c of 2 that is less than or equal to m, as obtained -c from the routine idd_sfrmi -c w -- initialization array constructed by routine idd_sfrmi -c x -- vector to be transformed -c -c output: -c y -- transform of x -c -c _N.B._: l must be less than or equal to n. -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,iw,n,l,l2 - real*8 w(27*m+90),x(m),y(l) -c -c -c Retrieve the number of pairs of outputs to be calculated -c via sfft. -c - l2 = w(3) -c -c -c Apply Rokhlin's random transformation to x, obtaining -c w(25*m+91 : 26*m+90). -c - iw = w(4+m+l+l2) - call idd_random_transf(x,w(25*m+90+1),w(iw)) -c -c -c Subselect from w(25*m+91 : 26*m+90) to obtain -c w(26*m+91 : 26*m+n+90). -c - call idd_subselect(n,w(4),m,w(25*m+90+1),w(26*m+90+1)) -c -c -c Fourier transform w(26*m+91 : 26*m+n+90). -c - call idd_sfft(l2,w(4+m+l),n,w(5+m+l+l2),w(26*m+90+1)) -c -c -c Copy the desired entries from w(26*m+91 : 26*m+n+90) -c to y. -c - call idd_subselect(l,w(4+m),n,w(26*m+90+1),y) -c -c - return - end -c -c -c -c - subroutine idd_pairsamps(n,l,ind,l2,ind2,marker) -c -c calculates the indices of the l2 pairs of integers -c to which the l individual integers from ind belong. -c The integers in ind may range from 1 to n. -c -c input: -c n -- upper bound on the integers in ind -c (the number 1 must be a lower bound); -c n must be even -c l -- length of ind -c ind -- integers selected from 1 to n -c -c output: -c l2 -- length of ind2 -c ind2 -- indices in the range from 1 to n/2 of the pairs -c of integers to which the entries of ind belong -c -c work: -c marker -- must be at least n/2 integer elements long -c -c _N.B._: n must be even. -c - implicit none - integer l,n,ind(l),ind2(l),marker(n/2),l2,k -c -c -c Unmark all pairs. -c - do k = 1,n/2 - marker(k) = 0 - enddo ! k -c -c -c Mark the required pairs. -c - do k = 1,l - marker((ind(k)+1)/2) = marker((ind(k)+1)/2)+1 - enddo ! k -c -c -c Record the required pairs in indpair. -c - l2 = 0 -c - do k = 1,n/2 -c - if(marker(k) .ne. 0) then - l2 = l2+1 - ind2(l2) = k - endif -c - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_permute(n,ind,x,y) -c -c copy the entries of x into y, rearranged according -c to the permutation specified by ind. -c -c input: -c n -- length of ind, x, and y -c ind -- permutation of n objects -c x -- vector to be permuted -c -c output: -c y -- permutation of x -c - implicit none - integer n,ind(n),k - real*8 x(n),y(n) -c -c - do k = 1,n - y(k) = x(ind(k)) - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_subselect(n,ind,m,x,y) -c -c copies into y the entries of x indicated by ind. -c -c input: -c n -- number of entries of x to copy into y -c ind -- indices of the entries in x to copy into y -c m -- length of x -c x -- vector whose entries are to be copied -c -c output: -c y -- collection of entries of x specified by ind -c - implicit none - integer n,ind(n),m,k - real*8 x(m),y(n) -c -c - do k = 1,n - y(k) = x(ind(k)) - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_frmi(m,n,w) -c -c initializes data for the routine idd_frm. -c -c input: -c m -- length of the vector to be transformed -c -c output: -c n -- greatest integer expressible as a positive integer power -c of 2 that is less than or equal to m -c w -- initialization array to be used by routine idd_frm -c -c -c glossary for the fully initialized w: -c -c w(1) = m -c w(2) = n -c w(3:2+m) stores a permutation of m objects -c w(3+m:2+m+n) stores a permutation of n objects -c w(3+m+n) = address in w of the initialization array -c for idd_random_transf -c w(4+m+n:int(w(3+m+n))-1) stores the initialization array -c for dfft -c w(int(w(3+m+n)):16*m+70) stores the initialization array -c for idd_random_transf -c -c -c _N.B._: n is an output of the present routine; -c this routine changes n. -c -c - implicit none - integer m,n,l,nsteps,keep,lw,ia - real*8 w(17*m+70) -c -c -c Find the greatest integer less than or equal to m -c which is a power of two. -c - call idd_poweroftwo(m,l,n) -c -c -c Store m and n in w. -c - w(1) = m - w(2) = n -c -c -c Store random permutations of m and n objects in w. -c - call id_randperm(m,w(3)) - call id_randperm(n,w(3+m)) -c -c -c Store the address within w of the idd_random_transf_init -c initialization data. -c - ia = 4+m+n+2*n+15 - w(3+m+n) = ia -c -c -c Store the initialization data for dfft in w. -c - call dffti(n,w(4+m+n)) -c -c -c Store the initialization data for idd_random_transf_init in w. -c - nsteps = 3 - call idd_random_transf_init(nsteps,m,w(ia),keep) -c -c -c Calculate the total number of elements used in w. -c - lw = 3+m+n+2*n+15 + 3*nsteps*m+2*m+m/4+50 -c - if(16*m+70 .lt. lw) then - call prinf('lw = *',lw,1) - call prinf('16m+70 = *',16*m+70,1) - stop - endif -c -c - return - end -c -c -c -c - subroutine idd_sfrmi(l,m,n,w) -c -c initializes data for the routine idd_sfrm. -c -c input: -c l -- length of the transformed (output) vector -c m -- length of the vector to be transformed -c -c output: -c n -- greatest integer expressible as a positive integer power -c of 2 that is less than or equal to m -c w -- initialization array to be used by routine idd_sfrm -c -c -c glossary for the fully initialized w: -c -c w(1) = m -c w(2) = n -c w(3) = l2 -c w(4:3+m) stores a permutation of m objects -c w(4+m:3+m+l) stores the indices of the l outputs which idd_sfft -c calculates -c w(4+m+l:3+m+l+l2) stores the indices of the l2 pairs of outputs -c which idd_sfft calculates -c w(4+m+l+l2) = address in w of the initialization array -c for idd_random_transf -c w(5+m+l+l2:int(w(4+m+l+l2))-1) stores the initialization array -c for idd_sfft -c w(int(w(4+m+l+l2)):25*m+90) stores the initialization array -c for idd_random_transf -c -c -c _N.B._: n is an output of the present routine; -c this routine changes n. -c -c - implicit none - integer l,m,n,idummy,nsteps,keep,lw,l2,ia - real*8 w(27*m+90) -c -c -c Find the greatest integer less than or equal to m -c which is a power of two. -c - call idd_poweroftwo(m,idummy,n) -c -c -c Store m and n in w. -c - w(1) = m - w(2) = n -c -c -c Store random permutations of m and n objects in w. -c - call id_randperm(m,w(4)) - call id_randperm(n,w(4+m)) -c -c -c Find the pairs of integers covering the integers in -c w(4+m : 3+m+(l+1)/2). -c - call idd_pairsamps(n,l,w(4+m),l2,w(4+m+2*l),w(4+m+3*l)) - w(3) = l2 - call idd_copyints(l2,w(4+m+2*l),w(4+m+l)) -c -c -c Store the address within w of the idd_random_transf_init -c initialization data. -c - ia = 5+m+l+l2+4*l2+30+8*n - w(4+m+l+l2) = ia -c -c -c Store the initialization data for idd_sfft in w. -c - call idd_sffti(l2,w(4+m+l),n,w(5+m+l+l2)) -c -c -c Store the initialization data for idd_random_transf_init in w. -c - nsteps = 3 - call idd_random_transf_init(nsteps,m,w(ia),keep) -c -c -c Calculate the total number of elements used in w. -c - lw = 4+m+l+l2+4*l2+30+8*n + 3*nsteps*m+2*m+m/4+50 -c - if(25*m+90 .lt. lw) then - call prinf('lw = *',lw,1) - call prinf('25m+90 = *',25*m+90,1) - stop - endif -c -c - return - end -c -c -c -c - subroutine idd_copyints(n,ia,ib) -c -c copies ia into ib. -c -c input: -c n -- length of ia and ib -c ia -- array to be copied -c -c output: -c ib -- copy of ia -c - implicit none - integer n,ia(n),ib(n),k -c -c - do k = 1,n - ib(k) = ia(k) - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_poweroftwo(m,l,n) -c -c computes l = floor(log_2(m)) and n = 2**l. -c -c input: -c m -- integer whose log_2 is to be taken -c -c output: -c l -- floor(log_2(m)) -c n -- 2**l -c - implicit none - integer l,m,n -c -c - l = 0 - n = 1 -c - 1000 continue - l = l+1 - n = n*2 - if(n .le. m) goto 1000 -c - l = l-1 - n = n/2 -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idd_house.f b/scipy/linalg/src/id_dist/src/idd_house.f deleted file mode 100644 index 71503711713a..000000000000 --- a/scipy/linalg/src/id_dist/src/idd_house.f +++ /dev/null @@ -1,288 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idd_house calculates the vector and scalar -c needed to apply the Householder transformation reflecting -c a given vector into its first component. -c -c routine idd_houseapp applies a Householder matrix to a vector. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idd_houseapp(n,vn,u,ifrescal,scal,v) -c -c applies the Householder matrix -c identity_matrix - scal * vn * transpose(vn) -c to the vector u, yielding the vector v; -c -c scal = 2/(1 + vn(2)^2 + ... + vn(n)^2) -c when vn(2), ..., vn(n) don't all vanish; -c -c scal = 0 -c when vn(2), ..., vn(n) do all vanish -c (including when n = 1). -c -c input: -c n -- size of vn, u, and v, though the indexing on vn goes -c from 2 to n -c vn -- components 2 to n of the Householder vector vn; -c vn(1) is assumed to be 1 -c u -- vector to be transformed -c ifrescal -- set to 1 to recompute scal from vn(2), ..., vn(n); -c set to 0 to use scal as input -c scal -- see the entry for ifrescal in the decription -c of the input -c -c output: -c scal -- see the entry for ifrescal in the decription -c of the input -c v -- result of applying the Householder matrix to u; -c it's O.K. to have v be the same as u -c in order to apply the matrix to the vector in place -c -c reference: -c Golub and Van Loan, "Matrix Computations," 3rd edition, -c Johns Hopkins University Press, 1996, Chapter 5. -c - implicit none - save - integer n,k,ifrescal - real*8 vn(2:*),scal,u(n),v(n),fact,sum -c -c -c Get out of this routine if n = 1. -c - if(n .eq. 1) then - v(1) = u(1) - return - endif -c -c - if(ifrescal .eq. 1) then -c -c -c Calculate (vn(2))^2 + ... + (vn(n))^2. -c - sum = 0 - do k = 2,n - sum = sum+vn(k)**2 - enddo ! k -c -c -c Calculate scal. -c - if(sum .eq. 0) scal = 0 - if(sum .ne. 0) scal = 2/(1+sum) -c -c - endif -c -c -c Calculate fact = scal * transpose(vn) * u. -c - fact = u(1) -c - do k = 2,n - fact = fact+vn(k)*u(k) - enddo ! k -c - fact = fact*scal -c -c -c Subtract fact*vn from u, yielding v. -c - v(1) = u(1) - fact -c - do k = 2,n - v(k) = u(k) - fact*vn(k) - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_house(n,x,rss,vn,scal) -c -c constructs the vector vn with vn(1) = 1 -c and the scalar scal such that -c H := identity_matrix - scal * vn * transpose(vn) is orthogonal -c and Hx = +/- e_1 * the root-sum-square of the entries of x -c (H is the Householder matrix corresponding to x). -c -c input: -c n -- size of x and vn, though the indexing on vn goes -c from 2 to n -c x -- vector to reflect into its first component -c -c output: -c rss -- first entry of the vector resulting from the application -c of the Householder matrix to x; -c its absolute value is the root-sum-square -c of the entries of x -c vn -- entries 2 to n of the Householder vector vn; -c vn(1) is assumed to be 1 -c scal -- scalar multiplying vn * transpose(vn); -c -c scal = 2/(1 + vn(2)^2 + ... + vn(n)^2) -c when vn(2), ..., vn(n) don't all vanish; -c -c scal = 0 -c when vn(2), ..., vn(n) do all vanish -c (including when n = 1) -c -c reference: -c Golub and Van Loan, "Matrix Computations," 3rd edition, -c Johns Hopkins University Press, 1996, Chapter 5. -c - implicit none - save - integer n,k - real*8 x(n),rss,sum,v1,scal,vn(2:*),x1 -c -c - x1 = x(1) -c -c -c Get out of this routine if n = 1. -c - if(n .eq. 1) then - rss = x1 - scal = 0 - return - endif -c -c -c Calculate (x(2))^2 + ... (x(n))^2 -c and the root-sum-square value of the entries in x. -c -c - sum = 0 - do k = 2,n - sum = sum+x(k)**2 - enddo ! k -c -c -c Get out of this routine if sum = 0; -c flag this case as such by setting v(2), ..., v(n) all to 0. -c - if(sum .eq. 0) then -c - rss = x1 - do k = 2,n - vn(k) = 0 - enddo ! k - scal = 0 -c - return -c - endif -c -c - rss = x1**2 + sum - rss = sqrt(rss) -c -c -c Determine the first component v1 -c of the unnormalized Householder vector -c v = x - rss * (1 0 0 ... 0 0)^T. -c -c If x1 <= 0, then form x1-rss directly, -c since that expression cannot involve any cancellation. -c - if(x1 .le. 0) v1 = x1-rss -c -c If x1 > 0, then use the fact that -c x1-rss = -sum / (x1+rss), -c in order to avoid potential cancellation. -c - if(x1 .gt. 0) v1 = -sum / (x1+rss) -c -c -c Compute the vector vn and the scalar scal such that vn(1) = 1 -c in the Householder transformation -c identity_matrix - scal * vn * transpose(vn). -c - do k = 2,n - vn(k) = x(k)/v1 - enddo ! k -c -c scal = 2 -c / ( vn(1)^2 + vn(2)^2 + ... + vn(n)^2 ) -c -c = 2 -c / ( 1 + vn(2)^2 + ... + vn(n)^2 ) -c -c = 2*v(1)^2 -c / ( v(1)^2 + (v(1)*vn(2))^2 + ... + (v(1)*vn(n))^2 ) -c -c = 2*v(1)^2 -c / ( v(1)^2 + (v(2)^2 + ... + v(n)^2) ) -c - scal = 2*v1**2 / (v1**2+sum) -c -c - return - end -c -c -c -c - subroutine idd_housemat(n,vn,scal,h) -c -c fills h with the Householder matrix -c identity_matrix - scal * vn * transpose(vn). -c -c input: -c n -- size of vn and h, though the indexing of vn goes -c from 2 to n -c vn -- entries 2 to n of the vector vn; -c vn(1) is assumed to be 1 -c scal -- scalar multiplying vn * transpose(vn) -c -c output: -c h -- identity_matrix - scal * vn * transpose(vn) -c - implicit none - save - integer n,j,k - real*8 vn(2:*),h(n,n),scal,factor1,factor2 -c -c -c Fill h with the identity matrix. -c - do j = 1,n - do k = 1,n -c - if(j .eq. k) h(k,j) = 1 - if(j .ne. k) h(k,j) = 0 -c - enddo ! k - enddo ! j -c -c -c Subtract from h the matrix scal*vn*transpose(vn). -c - do j = 1,n - do k = 1,n -c - if(j .eq. 1) factor1 = 1 - if(j .ne. 1) factor1 = vn(j) -c - if(k .eq. 1) factor2 = 1 - if(k .ne. 1) factor2 = vn(k) -c - h(k,j) = h(k,j) - scal*factor1*factor2 -c - enddo ! k - enddo ! j -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idd_id.f b/scipy/linalg/src/id_dist/src/idd_id.f deleted file mode 100644 index 640ff455b2d9..000000000000 --- a/scipy/linalg/src/id_dist/src/idd_id.f +++ /dev/null @@ -1,560 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine iddp_id computes the ID of a matrix, -c to a specified precision. -c -c routine iddr_id computes the ID of a matrix, -c to a specified rank. -c -c routine idd_reconid reconstructs a matrix from its ID. -c -c routine idd_copycols collects together selected columns -c of a matrix. -c -c routine idd_getcols collects together selected columns -c of a matrix specified by a routine for applying the matrix -c to arbitrary vectors. -c -c routine idd_reconint constructs p in the ID a = b p, -c where the columns of b are a subset of the columns of a, -c and p is the projection coefficient matrix, -c given list, krank, and proj output by routines iddr_id -c or iddp_id. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine iddp_id(eps,m,n,a,krank,list,rnorms) -c -c computes the ID of a, i.e., lists in list the indices -c of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c krank -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank) (*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon dimensioned epsilon(m,n-krank) -c such that the greatest singular value of epsilon -c <= the greatest singular value of a * eps. -c The present routine stores the krank x (n-krank) matrix proj -c in the memory initially occupied by a. -c -c input: -c eps -- relative precision of the resulting ID -c m -- first dimension of a -c n -- second dimension of a, as well as the dimension required -c of list -c a -- matrix to be ID'd -c -c output: -c a -- the first krank*(n-krank) elements of a constitute -c the krank x (n-krank) interpolation matrix proj -c krank -- numerical rank -c list -- list of the indices of the krank columns of a -c through which the other columns of a are expressed; -c also, list describes the permutation of proj -c required to reconstruct a as indicated in (*) above -c rnorms -- absolute values of the entries on the diagonal -c of the triangular matrix used to compute the ID -c (these may be used to check the stability of the ID) -c -c _N.B._: This routine changes a. -c -c reference: -c Cheng, Gimbutas, Martinsson, Rokhlin, "On the compression of -c low-rank matrices," SIAM Journal on Scientific Computing, -c 26 (4): 1389-1404, 2005. -c - implicit none - integer m,n,krank,k,list(n),iswap - real*8 a(m,n),eps,rnorms(n) -c -c -c QR decompose a. -c - call iddp_qrpiv(eps,m,n,a,krank,list,rnorms) -c -c -c Build the list of columns chosen in a -c by multiplying together the permutations in list, -c with the permutation swapping 1 and list(1) taken rightmost -c in the product, that swapping 2 and list(2) taken next -c rightmost, ..., that swapping krank and list(krank) taken -c leftmost. -c - do k = 1,n - rnorms(k) = k - enddo ! k -c - if(krank .gt. 0) then - do k = 1,krank -c -c Swap rnorms(k) and rnorms(list(k)). -c - iswap = rnorms(k) - rnorms(k) = rnorms(list(k)) - rnorms(list(k)) = iswap -c - enddo ! k - endif -c - do k = 1,n - list(k) = rnorms(k) - enddo ! k -c -c -c Fill rnorms for the output. -c - if(krank .gt. 0) then -c - do k = 1,krank - rnorms(k) = a(k,k) - enddo ! k -c - endif -c -c -c Backsolve for proj, storing it at the beginning of a. -c - if(krank .gt. 0) then - call idd_lssolve(m,n,a,krank) - endif -c -c - return - end -c -c -c -c - subroutine iddr_id(m,n,a,krank,list,rnorms) -c -c computes the ID of a, i.e., lists in list the indices -c of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c krank -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank) (*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon, dimensioned epsilon(m,n-krank), -c whose norm is (hopefully) minimized by the pivoting procedure. -c The present routine stores the krank x (n-krank) matrix proj -c in the memory initially occupied by a. -c -c input: -c m -- first dimension of a -c n -- second dimension of a, as well as the dimension required -c of list -c a -- matrix to be ID'd -c krank -- desired rank of the output matrix -c (please note that if krank > m or krank > n, -c then the rank of the output matrix will be -c less than krank) -c -c output: -c a -- the first krank*(n-krank) elements of a constitute -c the krank x (n-krank) interpolation matrix proj -c list -- list of the indices of the krank columns of a -c through which the other columns of a are expressed; -c also, list describes the permutation of proj -c required to reconstruct a as indicated in (*) above -c rnorms -- absolute values of the entries on the diagonal -c of the triangular matrix used to compute the ID -c (these may be used to check the stability of the ID) -c -c _N.B._: This routine changes a. -c -c reference: -c Cheng, Gimbutas, Martinsson, Rokhlin, "On the compression of -c low-rank matrices," SIAM Journal on Scientific Computing, -c 26 (4): 1389-1404, 2005. -c - implicit none - integer m,n,krank,j,k,list(n),iswap - real*8 a(m,n),rnorms(n),ss -c -c -c QR decompose a. -c - call iddr_qrpiv(m,n,a,krank,list,rnorms) -c -c -c Build the list of columns chosen in a -c by multiplying together the permutations in list, -c with the permutation swapping 1 and list(1) taken rightmost -c in the product, that swapping 2 and list(2) taken next -c rightmost, ..., that swapping krank and list(krank) taken -c leftmost. -c - do k = 1,n - rnorms(k) = k - enddo ! k -c - if(krank .gt. 0) then - do k = 1,krank -c -c Swap rnorms(k) and rnorms(list(k)). -c - iswap = rnorms(k) - rnorms(k) = rnorms(list(k)) - rnorms(list(k)) = iswap -c - enddo ! k - endif -c - do k = 1,n - list(k) = rnorms(k) - enddo ! k -c -c -c Fill rnorms for the output. -c - ss = 0 -c - do k = 1,krank - rnorms(k) = a(k,k) - ss = ss+rnorms(k)**2 - enddo ! k -c -c -c Backsolve for proj, storing it at the beginning of a. -c - if(krank .gt. 0 .and. ss .gt. 0) then - call idd_lssolve(m,n,a,krank) - endif -c - if(ss .eq. 0) then -c - do k = 1,n - do j = 1,m -c - a(j,k) = 0 -c - enddo ! j - enddo ! k -c - endif -c -c - return - end -c -c -c -c - subroutine idd_reconid(m,krank,col,n,list,proj,approx) -c -c reconstructs the matrix that the routine iddp_id -c or iddr_id has decomposed, using the columns col -c of the reconstructed matrix whose indices are listed in list, -c in addition to the interpolation matrix proj. -c -c input: -c m -- first dimension of cols and approx -c krank -- first dimension of cols and proj; also, -c n-krank is the second dimension of proj -c col -- columns of the matrix to be reconstructed -c n -- second dimension of approx; also, -c n-krank is the second dimension of proj -c list(k) -- index of col(1:m,k) in the reconstructed matrix -c when k <= krank; in general, list describes -c the permutation required for reconstruction -c via cols and proj -c proj -- interpolation matrix -c -c output: -c approx -- reconstructed matrix -c - implicit none - integer m,n,krank,j,k,l,list(n) - real*8 col(m,krank),proj(krank,n-krank),approx(m,n) -c -c - do j = 1,m - do k = 1,n -c - approx(j,list(k)) = 0 -c -c Add in the contributions due to the identity matrix. -c - if(k .le. krank) then - approx(j,list(k)) = approx(j,list(k)) + col(j,k) - endif -c -c Add in the contributions due to proj. -c - if(k .gt. krank) then - if(krank .gt. 0) then -c - do l = 1,krank - approx(j,list(k)) = approx(j,list(k)) - 1 + col(j,l)*proj(l,k-krank) - enddo ! l -c - endif - endif -c - enddo ! k - enddo ! j -c -c - return - end -c -c -c -c - subroutine idd_lssolve(m,n,a,krank) -c -c backsolves for proj satisfying R_11 proj ~ R_12, -c where R_11 = a(1:krank,1:krank) -c and R_12 = a(1:krank,krank+1:n). -c This routine overwrites the beginning of a with proj. -c -c input: -c m -- first dimension of a -c n -- second dimension of a; also, -c n-krank is the second dimension of proj -c a -- trapezoidal input matrix -c krank -- first dimension of proj; also, -c n-krank is the second dimension of proj -c -c output: -c a -- the first krank*(n-krank) elements of a constitute -c the krank x (n-krank) matrix proj -c - implicit none - integer m,n,krank,j,k,l - real*8 a(m,n),sum -c -c -c Overwrite a(1:krank,krank+1:n) with proj. -c - do k = 1,n-krank - do j = krank,1,-1 -c - sum = 0 -c - do l = j+1,krank - sum = sum+a(j,l)*a(l,krank+k) - enddo ! l -c - a(j,krank+k) = a(j,krank+k)-sum -c -c Make sure that the entry in proj won't be too big; -c set the entry to 0 when roundoff would make it too big -c (in which case a(j,j) is so small that the contribution -c from this entry in proj to the overall matrix approximation -c is supposed to be negligible). -c - if(abs(a(j,krank+k)) .lt. 2**20*abs(a(j,j))) then - a(j,krank+k) = a(j,krank+k)/a(j,j) - else - a(j,krank+k) = 0 - endif -c - enddo ! j - enddo ! k -c -c -c Move proj from a(1:krank,krank+1:n) to the beginning of a. -c - call idd_moverup(m,n,krank,a) -c -c - return - end -c -c -c -c - subroutine idd_moverup(m,n,krank,a) -c -c moves the krank x (n-krank) matrix in a(1:krank,krank+1:n), -c where a is initially dimensioned m x n, to the beginning of a. -c (This is not the most natural way to code the move, -c but one of my usually well-behaved compilers chokes -c on more natural ways.) -c -c input: -c m -- initial first dimension of a -c n -- initial second dimension of a -c krank -- number of rows to move -c a -- m x n matrix whose krank x (n-krank) block -c a(1:krank,krank+1:n) is to be moved -c -c output: -c a -- array starting with the moved krank x (n-krank) block -c - implicit none - integer m,n,krank,j,k - real*8 a(m*n) -c -c - do k = 1,n-krank - do j = 1,krank - a(j+krank*(k-1)) = a(j+m*(krank+k-1)) - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_getcols(m,n,matvec,p1,p2,p3,p4,krank,list, - 1 col,x) -c -c collects together the columns of the matrix a indexed by list -c into the matrix col, where routine matvec applies a -c to an arbitrary vector. -c -c input: -c m -- first dimension of a -c n -- second dimension of a -c matvec -- routine which applies a to an arbitrary vector; -c this routine must have a calling sequence of the form -c -c matvec(m,x,n,y,p1,p2,p3,p4) -c -c where m is the length of x, -c x is the vector to which the matrix is to be applied, -c n is the length of y, -c y is the product of the matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvec -c p2 -- parameter to be passed to routine matvec -c p3 -- parameter to be passed to routine matvec -c p4 -- parameter to be passed to routine matvec -c krank -- number of columns to be extracted -c list -- indices of the columns to be extracted -c -c output: -c col -- columns of a indexed by list -c -c work: -c x -- must be at least n real*8 elements long -c - implicit none - integer m,n,krank,list(krank),j,k - real*8 col(m,krank),x(n),p1,p2,p3,p4 - external matvec -c -c - do j = 1,krank -c - do k = 1,n - x(k) = 0 - enddo ! k -c - x(list(j)) = 1 -c - call matvec(n,x,m,col(1,j),p1,p2,p3,p4) -c - enddo ! j -c -c - return - end -c -c -c -c - subroutine idd_reconint(n,list,krank,proj,p) -c -c constructs p in the ID a = b p, -c where the columns of b are a subset of the columns of a, -c and p is the projection coefficient matrix, -c given list, krank, and proj output -c by routines iddp_id or iddr_id. -c -c input: -c n -- part of the second dimension of proj and p -c list -- list of columns retained from the original matrix -c in the ID -c krank -- rank of the ID -c proj -- matrix of projection coefficients in the ID -c -c output: -c p -- projection matrix in the ID -c - implicit none - integer n,krank,list(n),j,k - real*8 proj(krank,n-krank),p(krank,n) -c -c - do k = 1,krank - do j = 1,n -c - if(j .le. krank) then - if(j .eq. k) p(k,list(j)) = 1 - if(j .ne. k) p(k,list(j)) = 0 - endif -c - if(j .gt. krank) then - p(k,list(j)) = proj(k,j-krank) - endif -c - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_copycols(m,n,a,krank,list,col) -c -c collects together the columns of the matrix a indexed by list -c into the matrix col. -c -c input: -c m -- first dimension of a -c n -- second dimension of a -c a -- matrix whose columns are to be extracted -c krank -- number of columns to be extracted -c list -- indices of the columns to be extracted -c -c output: -c col -- columns of a indexed by list -c - implicit none - integer m,n,krank,list(krank),j,k - real*8 a(m,n),col(m,krank) -c -c - do k = 1,krank - do j = 1,m -c - col(j,k) = a(j,list(k)) -c - enddo ! j - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idd_id2svd.f b/scipy/linalg/src/id_dist/src/idd_id2svd.f deleted file mode 100644 index 42e1f23cd0c4..000000000000 --- a/scipy/linalg/src/id_dist/src/idd_id2svd.f +++ /dev/null @@ -1,384 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idd_id2svd converts an approximation to a matrix -c in the form of an ID to an approximation in the form of an SVD. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idd_id2svd(m,krank,b,n,list,proj,u,v,s,ier,w) -c -c converts an approximation to a matrix in the form of an ID -c to an approximation in the form of an SVD. -c -c input: -c m -- first dimension of b -c krank -- rank of the ID -c b -- columns of the original matrix in the ID -c list -- list of columns chosen from the original matrix -c in the ID -c n -- length of list and part of the second dimension of proj -c proj -- projection coefficients in the ID -c -c output: -c u -- left singular vectors -c v -- right singular vectors -c s -- singular values -c ier -- 0 when the routine terminates successfully; -c nonzero otherwise -c -c work: -c w -- must be at least (krank+1)*(m+3*n)+26*krank**2 real*8 -c elements long -c -c _N.B._: This routine destroys b. -c - implicit none - integer m,krank,n,list(n),iwork,lwork,ip,lp,it,lt,ir,lr, - 1 ir2,lr2,ir3,lr3,iind,lind,iindt,lindt,lw,ier - real*8 b(m,krank),proj(krank,n-krank),u(m,krank),v(n,krank), - 1 w((krank+1)*(m+3*n)+26*krank**2),s(krank) -c -c - lw = 0 -c - iwork = lw+1 - lwork = 25*krank**2 - lw = lw+lwork -c - ip = lw+1 - lp = krank*n - lw = lw+lp -c - it = lw+1 - lt = n*krank - lw = lw+lt -c - ir = lw+1 - lr = krank*n - lw = lw+lr -c - ir2 = lw+1 - lr2 = krank*m - lw = lw+lr2 -c - ir3 = lw+1 - lr3 = krank*krank - lw = lw+lr3 -c - iind = lw+1 - lind = n/2+1 - lw = lw+1 -c - iindt = lw+1 - lindt = m/2+1 - lw = lw+1 -c -c - call idd_id2svd0(m,krank,b,n,list,proj,u,v,s,ier, - 1 w(iwork),w(ip),w(it),w(ir),w(ir2),w(ir3), - 2 w(iind),w(iindt)) -c -c - return - end -c -c -c -c - subroutine idd_id2svd0(m,krank,b,n,list,proj,u,v,s,ier, - 1 work,p,t,r,r2,r3,ind,indt) -c -c routine idd_id2svd serves as a memory wrapper -c for the present routine (please see routine idd_id2svd -c for further documentation). -c - implicit none -c - character*1 jobz - integer m,n,krank,list(n),ind(n),indt(m),iftranspose, - 1 lwork,ldu,ldvt,ldr,info,j,k,ier - real*8 b(m,krank),proj(krank,n-krank),p(krank,n), - 1 r(krank,n),r2(krank,m),t(n,krank),r3(krank,krank), - 2 u(m,krank),v(n,krank),s(krank),work(25*krank**2) -c -c -c - ier = 0 -c -c -c -c Construct the projection matrix p from the ID. -c - call idd_reconint(n,list,krank,proj,p) -c -c -c -c Compute a pivoted QR decomposition of b. -c - call iddr_qrpiv(m,krank,b,krank,ind,r) -c -c -c Extract r from the QR decomposition. -c - call idd_rinqr(m,krank,b,krank,r) -c -c -c Rearrange r according to ind. -c - call idd_rearr(krank,ind,krank,krank,r) -c -c -c -c Transpose p to obtain t. -c - call idd_mattrans(krank,n,p,t) -c -c -c Compute a pivoted QR decomposition of t. -c - call iddr_qrpiv(n,krank,t,krank,indt,r2) -c -c -c Extract r2 from the QR decomposition. -c - call idd_rinqr(n,krank,t,krank,r2) -c -c -c Rearrange r2 according to indt. -c - call idd_rearr(krank,indt,krank,krank,r2) -c -c -c -c Multiply r and r2^T to obtain r3. -c - call idd_matmultt(krank,krank,r,krank,r2,r3) -c -c -c -c Use LAPACK to SVD r3. -c - jobz = 'S' - ldr = krank - lwork = 25*krank**2-krank**2-4*krank - ldu = krank - ldvt = krank -c - call dgesdd(jobz,krank,krank,r3,ldr,s,work,ldu,r,ldvt, - 1 work(krank**2+4*krank+1),lwork, - 2 work(krank**2+1),info) -c - if(info .ne. 0) then - ier = info - return - endif -c -c -c -c Multiply the u from r3 from the left by the q from b -c to obtain the u for a. -c - do k = 1,krank -c - do j = 1,krank - u(j,k) = work(j+krank*(k-1)) - enddo ! j -c - do j = krank+1,m - u(j,k) = 0 - enddo ! j -c - enddo ! k -c - iftranspose = 0 - call idd_qmatmat(iftranspose,m,krank,b,krank,krank,u,r2) -c -c -c -c Transpose r to obtain r2. -c - call idd_mattrans(krank,krank,r,r2) -c -c -c Multiply the v from r3 from the left by the q from p^T -c to obtain the v for a. -c - do k = 1,krank -c - do j = 1,krank - v(j,k) = r2(j,k) - enddo ! j -c - do j = krank+1,n - v(j,k) = 0 - enddo ! j -c - enddo ! k -c - iftranspose = 0 - call idd_qmatmat(iftranspose,n,krank,t,krank,krank,v,r2) -c -c - return - end -c -c -c -c - subroutine idd_mattrans(m,n,a,at) -c -c transposes a to obtain at. -c -c input: -c m -- first dimension of a, and second dimension of at -c n -- second dimension of a, and first dimension of at -c a -- matrix to be transposed -c -c output: -c at -- transpose of a -c - implicit none - integer m,n,j,k - real*8 a(m,n),at(n,m) -c -c - do k = 1,n - do j = 1,m - at(k,j) = a(j,k) - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_matmultt(l,m,a,n,b,c) -c -c multiplies a and b^T to obtain c. -c -c input: -c l -- first dimension of a and c -c m -- second dimension of a and b -c a -- leftmost matrix in the product c = a b^T -c n -- first dimension of b and second dimension of c -c b -- rightmost matrix in the product c = a b^T -c -c output: -c c -- product of a and b^T -c - implicit none - integer l,m,n,i,j,k - real*8 a(l,m),b(n,m),c(l,n),sum -c -c - do i = 1,l - do k = 1,n -c - sum = 0 -c - do j = 1,m - sum = sum+a(i,j)*b(k,j) - enddo ! j -c - c(i,k) = sum -c - enddo ! k - enddo ! i -c -c - return - end -c -c -c -c - subroutine idd_rearr(krank,ind,m,n,a) -c -c rearranges a according to ind obtained -c from routines iddr_qrpiv or iddp_qrpiv, -c assuming that a = q r, where q and r are from iddr_qrpiv -c or iddp_qrpiv. -c -c input: -c krank -- rank obtained from routine iddp_qrpiv, -c or provided to routine iddr_qrpiv -c ind -- indexing array obtained from routine iddr_qrpiv -c or iddp_qrpiv -c m -- first dimension of a -c n -- second dimension of a -c a -- matrix to be rearranged -c -c output: -c a -- rearranged matrix -c - implicit none - integer k,krank,m,n,j,ind(krank) - real*8 rswap,a(m,n) -c -c - do k = krank,1,-1 - do j = 1,m -c - rswap = a(j,k) - a(j,k) = a(j,ind(k)) - a(j,ind(k)) = rswap -c - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_rinqr(m,n,a,krank,r) -c -c extracts R in the QR decomposition specified by the output a -c of the routine iddr_qrpiv or iddp_qrpiv. -c -c input: -c m -- first dimension of a -c n -- second dimension of a and r -c a -- output of routine iddr_qrpiv or iddp_qrpiv -c krank -- rank output by routine iddp_qrpiv (or specified -c to routine iddr_qrpiv) -c -c output: -c r -- triangular factor in the QR decomposition specified -c by the output a of the routine iddr_qrpiv or iddp_qrpiv -c - implicit none - integer m,n,j,k,krank - real*8 a(m,n),r(krank,n) -c -c -c Copy a into r and zero out the appropriate -c Householder vectors that are stored in one triangle of a. -c - do k = 1,n - do j = 1,krank - r(j,k) = a(j,k) - enddo ! j - enddo ! k -c - do k = 1,n - if(k .lt. krank) then - do j = k+1,krank - r(j,k) = 0 - enddo ! j - endif - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idd_qrpiv.f b/scipy/linalg/src/id_dist/src/idd_qrpiv.f deleted file mode 100644 index b1dd88e1503d..000000000000 --- a/scipy/linalg/src/id_dist/src/idd_qrpiv.f +++ /dev/null @@ -1,893 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine iddp_qrpiv computes the pivoted QR decomposition -c of a matrix via Householder transformations, -c stopping at a specified precision of the decomposition. -c -c routine iddr_qrpiv computes the pivoted QR decomposition -c of a matrix via Householder transformations, -c stopping at a specified rank of the decomposition. -c -c routine idd_qmatvec applies to a single vector -c the Q matrix (or its transpose) in the QR decomposition -c of a matrix, as described by the output of iddp_qrpiv -c or iddr_qrpiv. If you're concerned about efficiency -c and want to apply Q (or its transpose) to multiple vectors, -c use idd_qmatmat instead. -c -c routine idd_qmatmat applies -c to multiple vectors collected together -c as a matrix the Q matrix (or its transpose) -c in the QR decomposition of a matrix, as described -c by the output of iddp_qrpiv or iddr_qrpiv. If you don't want -c to provide a work array and want to apply Q (or its transpose) -c to a single vector, use idd_qmatvec instead. -c -c routine idd_qinqr reconstructs the Q matrix -c in a QR decomposition from the data generated -c by iddp_qrpiv or iddr_qrpiv. -c -c routine idd_permmult multiplies together a bunch -c of permutations. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - - subroutine idd_permmult(m,ind,n,indprod) -c -c multiplies together the series of permutations in ind. -c -c input: -c m -- length of ind -c ind(k) -- number of the slot with which to swap -c the k^th slot -c n -- length of indprod and indprodinv -c -c output: -c indprod -- product of the permutations in ind, -c with the permutation swapping 1 and ind(1) -c taken leftmost in the product, -c that swapping 2 and ind(2) taken next leftmost, -c ..., that swapping krank and ind(krank) -c taken rightmost; indprod(k) is the number -c of the slot with which to swap the k^th slot -c in the product permutation -c - implicit none - integer m,n,ind(m),indprod(n),k,iswap -c -c - do k = 1,n - indprod(k) = k - enddo ! k -c - do k = m,1,-1 -c -c Swap indprod(k) and indprod(ind(k)). -c - iswap = indprod(k) - indprod(k) = indprod(ind(k)) - indprod(ind(k)) = iswap -c - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_qinqr(m,n,a,krank,q) -c -c constructs the matrix q from iddp_qrpiv or iddr_qrpiv -c (see the routine iddp_qrpiv or iddr_qrpiv -c for more information). -c -c input: -c m -- first dimension of a; also, right now, q is m x m -c n -- second dimension of a -c a -- matrix output by iddp_qrpiv or iddr_qrpiv -c (and denoted the same there) -c krank -- numerical rank output by iddp_qrpiv or iddr_qrpiv -c (and denoted the same there) -c -c output: -c q -- orthogonal matrix implicitly specified by the data in a -c from iddp_qrpiv or iddr_qrpiv -c -c Note: -c Right now, this routine simply multiplies -c one after another the krank Householder matrices -c in the full QR decomposition of a, -c in order to obtain the complete m x m Q factor in the QR. -c This routine should instead use the following -c (more elaborate but more efficient) scheme -c to construct a q dimensioned q(krank,m); this scheme -c was introduced by Robert Schreiber and Charles Van Loan -c in "A Storage-Efficient _WY_ Representation -c for Products of Householder Transformations," -c _SIAM Journal on Scientific and Statistical Computing_, -c Vol. 10, No. 1, pp. 53-57, January, 1989: -c -c Theorem 1. Suppose that Q = _1_ + YTY^T is -c an m x m orthogonal real matrix, -c where Y is an m x k real matrix -c and T is a k x k upper triangular real matrix. -c Suppose also that P = _1_ - 2 v v^T is -c a real Householder matrix and Q_+ = QP, -c where v is an m x 1 real vector, -c normalized so that v^T v = 1. -c Then, Q_+ = _1_ + Y_+ T_+ Y_+^T, -c where Y_+ = (Y v) is the m x (k+1) matrix -c formed by adjoining v to the right of Y, -c ( T z ) -c and T_+ = ( ) is -c ( 0 -2 ) -c the (k+1) x (k+1) upper triangular matrix -c formed by adjoining z to the right of T -c and the vector (0 ... 0 -2) with k zeroes below (T z), -c where z = -2 T Y^T v. -c -c Now, suppose that A is a (rank-deficient) matrix -c whose complete QR decomposition has -c the blockwise partioned form -c ( Q_11 Q_12 ) ( R_11 R_12 ) ( Q_11 ) -c A = ( ) ( ) = ( ) (R_11 R_12). -c ( Q_21 Q_22 ) ( 0 0 ) ( Q_21 ) -c Then, the only blocks of the orthogonal factor -c in the above QR decomposition of A that matter are -c ( Q_11 ) -c Q_11 and Q_21, _i.e._, only the block of columns ( ) -c ( Q_21 ) -c interests us. -c Suppose in addition that Q_11 is a k x k matrix, -c Q_21 is an (m-k) x k matrix, and that -c ( Q_11 Q_12 ) -c ( ) = _1_ + YTY^T, as in Theorem 1 above. -c ( Q_21 Q_22 ) -c Then, Q_11 = _1_ + Y_1 T Y_1^T -c and Q_21 = Y_2 T Y_1^T, -c where Y_1 is the k x k matrix and Y_2 is the (m-k) x k matrix -c ( Y_1 ) -c so that Y = ( ). -c ( Y_2 ) -c -c So, you can calculate T and Y via the above recursions, -c and then use these to compute the desired Q_11 and Q_21. -c -c - implicit none - integer m,n,krank,j,k,mm,ifrescal - real*8 a(m,n),q(m,m),scal -c -c -c Zero all of the entries of q. -c - do k = 1,m - do j = 1,m - q(j,k) = 0 - enddo ! j - enddo ! k -c -c -c Place 1's along the diagonal of q. -c - do k = 1,m - q(k,k) = 1 - enddo ! k -c -c -c Apply the krank Householder transformations stored in a. -c - do k = krank,1,-1 - do j = k,m - mm = m-k+1 - ifrescal = 1 - if(k .lt. m) - 1 call idd_houseapp(mm,a(k+1,k),q(k,j),ifrescal,scal,q(k,j)) - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_qmatvec(iftranspose,m,n,a,krank,v) -c -c applies to a single vector the Q matrix (or its transpose) -c which the routine iddp_qrpiv or iddr_qrpiv has stored -c in a triangle of the matrix it produces (stored, incidentally, -c as data for applying a bunch of Householder reflections). -c Use the routine qmatmat to apply the Q matrix -c (or its transpose) -c to a bunch of vectors collected together as a matrix, -c if you're concerned about efficiency. -c -c input: -c iftranspose -- set to 0 for applying Q; -c set to 1 for applying the transpose of Q -c m -- first dimension of a and length of v -c n -- second dimension of a -c a -- data describing the qr decomposition of a matrix, -c as produced by iddp_qrpiv or iddr_qrpiv -c krank -- numerical rank -c v -- vector to which Q (or its transpose) is to be applied -c -c output: -c v -- vector to which Q (or its transpose) has been applied -c - implicit none - save - integer m,n,krank,k,ifrescal,mm,iftranspose - real*8 a(m,n),v(m),scal -c -c - ifrescal = 1 -c -c - if(iftranspose .eq. 0) then -c - do k = krank,1,-1 - mm = m-k+1 - if(k .lt. m) - 1 call idd_houseapp(mm,a(k+1,k),v(k),ifrescal,scal,v(k)) - enddo ! k -c - endif -c -c - if(iftranspose .eq. 1) then -c - do k = 1,krank - mm = m-k+1 - if(k .lt. m) - 1 call idd_houseapp(mm,a(k+1,k),v(k),ifrescal,scal,v(k)) - enddo ! k -c - endif -c -c - return - end -c -c -c -c - subroutine idd_qmatmat(iftranspose,m,n,a,krank,l,b,work) -c -c applies to a bunch of vectors collected together as a matrix -c the Q matrix (or its transpose) which the routine iddp_qrpiv or -c iddr_qrpiv has stored in a triangle of the matrix it produces -c (stored, incidentally, as data for applying a bunch -c of Householder reflections). -c Use the routine qmatvec to apply the Q matrix -c (or its transpose) -c to a single vector, if you'd rather not provide a work array. -c -c input: -c iftranspose -- set to 0 for applying Q; -c set to 1 for applying the transpose of Q -c m -- first dimension of both a and b -c n -- second dimension of a -c a -- data describing the qr decomposition of a matrix, -c as produced by iddp_qrpiv or iddr_qrpiv -c krank -- numerical rank -c l -- second dimension of b -c b -- matrix to which Q (or its transpose) is to be applied -c -c output: -c b -- matrix to which Q (or its transpose) has been applied -c -c work: -c work -- must be at least krank real*8 elements long -c - implicit none - save - integer l,m,n,krank,j,k,ifrescal,mm,iftranspose - real*8 a(m,n),b(m,l),work(krank) -c -c - if(iftranspose .eq. 0) then -c -c -c Handle the first iteration, j = 1, -c calculating all scals (ifrescal = 1). -c - ifrescal = 1 -c - j = 1 -c - do k = krank,1,-1 - if(k .lt. m) then - mm = m-k+1 - call idd_houseapp(mm,a(k+1,k),b(k,j),ifrescal, - 1 work(k),b(k,j)) - endif - enddo ! k -c -c - if(l .gt. 1) then -c -c Handle the other iterations, j > 1, -c using the scals just computed (ifrescal = 0). -c - ifrescal = 0 -c - do j = 2,l -c - do k = krank,1,-1 - if(k .lt. m) then - mm = m-k+1 - call idd_houseapp(mm,a(k+1,k),b(k,j),ifrescal, - 1 work(k),b(k,j)) - endif - enddo ! k -c - enddo ! j -c - endif ! j .gt. 1 -c -c - endif ! iftranspose .eq. 0 -c -c - if(iftranspose .eq. 1) then -c -c -c Handle the first iteration, j = 1, -c calculating all scals (ifrescal = 1). -c - ifrescal = 1 -c - j = 1 -c - do k = 1,krank - if(k .lt. m) then - mm = m-k+1 - call idd_houseapp(mm,a(k+1,k),b(k,j),ifrescal, - 1 work(k),b(k,j)) - endif - enddo ! k -c -c - if(l .gt. 1) then -c -c Handle the other iterations, j > 1, -c using the scals just computed (ifrescal = 0). -c - ifrescal = 0 -c - do j = 2,l -c - do k = 1,krank - if(k .lt. m) then - mm = m-k+1 - call idd_houseapp(mm,a(k+1,k),b(k,j),ifrescal, - 1 work(k),b(k,j)) - endif - enddo ! k -c - enddo ! j -c - endif ! j .gt. 1 -c -c - endif ! iftranspose .eq. 1 -c -c - return - end -c -c -c -c - subroutine iddp_qrpiv(eps,m,n,a,krank,ind,ss) -c -c computes the pivoted QR decomposition -c of the matrix input into a, using Householder transformations, -c _i.e._, transforms the matrix a from its input value in -c to the matrix out with entry -c -c m -c out(j,indprod(k)) = Sigma q(l,j) * in(l,k), -c l=1 -c -c for all j = 1, ..., krank, and k = 1, ..., n, -c -c where in = the a from before the routine runs, -c out = the a from after the routine runs, -c out(j,k) = 0 when j > k (so that out is triangular), -c q(1:m,1), ..., q(1:m,krank) are orthonormal, -c indprod is the product of the permutations given by ind, -c (as computable via the routine permmult, -c with the permutation swapping 1 and ind(1) taken leftmost -c in the product, that swapping 2 and ind(2) taken next leftmost, -c ..., that swapping krank and ind(krank) taken rightmost), -c and with the matrix out satisfying -c -c krank -c in(j,k) = Sigma q(j,l) * out(l,indprod(k)) + epsilon(j,k), -c l=1 -c -c for all j = 1, ..., m, and k = 1, ..., n, -c -c for some matrix epsilon such that -c the root-sum-square of the entries of epsilon -c <= the root-sum-square of the entries of in * eps. -c Well, technically, this routine outputs the Householder vectors -c (or, rather, their second through last entries) -c in the part of a that is supposed to get zeroed, that is, -c in a(j,k) with m >= j > k >= 1. -c -c input: -c eps -- relative precision of the resulting QR decomposition -c m -- first dimension of a and q -c n -- second dimension of a -c a -- matrix whose QR decomposition gets computed -c -c output: -c a -- triangular (R) factor in the QR decompositon -c of the matrix input into the same storage locations, -c with the Householder vectors stored in the part of a -c that would otherwise consist entirely of zeroes, that is, -c in a(j,k) with m >= j > k >= 1 -c krank -- numerical rank -c ind(k) -- index of the k^th pivot vector; -c the following code segment will correctly rearrange -c the product b of q and the upper triangle of out -c so that b matches the input matrix in -c to relative precision eps: -c -c copy the non-rearranged product of q and out into b -c set k to krank -c [start of loop] -c swap b(1:m,k) and b(1:m,ind(k)) -c decrement k by 1 -c if k > 0, then go to [start of loop] -c -c work: -c ss -- must be at least n real*8 words long -c -c _N.B._: This routine outputs the Householder vectors -c (or, rather, their second through last entries) -c in the part of a that is supposed to get zeroed, that is, -c in a(j,k) with m >= j > k >= 1. -c -c reference: -c Golub and Van Loan, "Matrix Computations," 3rd edition, -c Johns Hopkins University Press, 1996, Chapter 5. -c - implicit none - integer n,m,ind(n),krank,k,j,kpiv,mm,nupdate,ifrescal - real*8 a(m,n),ss(n),eps,feps,ssmax,scal,ssmaxin,rswap -c -c - feps = .1d-16 -c -c -c Compute the sum of squares of the entries in each column of a, -c the maximum of all such sums, and find the first pivot -c (column with the greatest such sum). -c - ssmax = 0 - kpiv = 1 -c - do k = 1,n -c - ss(k) = 0 - do j = 1,m - ss(k) = ss(k)+a(j,k)**2 - enddo ! j -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - ssmaxin = ssmax -c - nupdate = 0 -c -c -c While ssmax > eps**2*ssmaxin, krank < m, and krank < n, -c do the following block of code, -c which ends at the statement labeled 2000. -c - krank = 0 - 1000 continue -c - if(ssmax .le. eps**2*ssmaxin - 1 .or. krank .ge. m .or. krank .ge. n) goto 2000 - krank = krank+1 -c -c - mm = m-krank+1 -c -c -c Perform the pivoting. -c - ind(krank) = kpiv -c -c Swap a(1:m,krank) and a(1:m,kpiv). -c - do j = 1,m - rswap = a(j,krank) - a(j,krank) = a(j,kpiv) - a(j,kpiv) = rswap - enddo ! j -c -c Swap ss(krank) and ss(kpiv). -c - rswap = ss(krank) - ss(krank) = ss(kpiv) - ss(kpiv) = rswap -c -c - if(krank .lt. m) then -c -c -c Compute the data for the Householder transformation -c which will zero a(krank+1,krank), ..., a(m,krank) -c when applied to a, replacing a(krank,krank) -c with the first entry of the result of the application -c of the Householder matrix to a(krank:m,krank), -c and storing entries 2 to mm of the Householder vector -c in a(krank+1,krank), ..., a(m,krank) -c (which otherwise would get zeroed upon application -c of the Householder transformation). -c - call idd_house(mm,a(krank,krank),a(krank,krank), - 1 a(krank+1,krank),scal) - ifrescal = 0 -c -c -c Apply the Householder transformation -c to the lower right submatrix of a -c with upper leftmost entry at position (krank,krank+1). -c - if(krank .lt. n) then - do k = krank+1,n - call idd_houseapp(mm,a(krank+1,krank),a(krank,k), - 1 ifrescal,scal,a(krank,k)) - enddo ! k - endif -c -c -c Update the sums-of-squares array ss. -c - do k = krank,n - ss(k) = ss(k)-a(krank,k)**2 - enddo ! k -c -c -c Find the pivot (column with the greatest sum of squares -c of its entries). -c - ssmax = 0 - kpiv = krank+1 -c - if(krank .lt. n) then -c - do k = krank+1,n -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - endif ! krank .lt. n -c -c -c Recompute the sums-of-squares and the pivot -c when ssmax first falls below -c sqrt((1000*feps)^2) * ssmaxin -c and when ssmax first falls below -c ((1000*feps)^2) * ssmaxin. -c - if( - 1 (ssmax .lt. sqrt((1000*feps)**2) * ssmaxin - 2 .and. nupdate .eq. 0) .or. - 3 (ssmax .lt. ((1000*feps)**2) * ssmaxin - 4 .and. nupdate .eq. 1) - 5 ) then -c - nupdate = nupdate+1 -c - ssmax = 0 - kpiv = krank+1 -c - if(krank .lt. n) then -c - do k = krank+1,n -c - ss(k) = 0 - do j = krank+1,m - ss(k) = ss(k)+a(j,k)**2 - enddo ! j -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - endif ! krank .lt. n -c - endif -c -c - endif ! krank .lt. m -c -c - goto 1000 - 2000 continue -c -c - return - end -c -c -c -c - subroutine iddr_qrpiv(m,n,a,krank,ind,ss) -c -c computes the pivoted QR decomposition -c of the matrix input into a, using Householder transformations, -c _i.e._, transforms the matrix a from its input value in -c to the matrix out with entry -c -c m -c out(j,indprod(k)) = Sigma q(l,j) * in(l,k), -c l=1 -c -c for all j = 1, ..., krank, and k = 1, ..., n, -c -c where in = the a from before the routine runs, -c out = the a from after the routine runs, -c out(j,k) = 0 when j > k (so that out is triangular), -c q(1:m,1), ..., q(1:m,krank) are orthonormal, -c indprod is the product of the permutations given by ind, -c (as computable via the routine permmult, -c with the permutation swapping 1 and ind(1) taken leftmost -c in the product, that swapping 2 and ind(2) taken next leftmost, -c ..., that swapping krank and ind(krank) taken rightmost), -c and with the matrix out satisfying -c -c min(krank,m,n) -c in(j,k) = Sigma q(j,l) * out(l,indprod(k)) -c l=1 -c -c + epsilon(j,k), -c -c for all j = 1, ..., m, and k = 1, ..., n, -c -c for some matrix epsilon whose norm is (hopefully) minimized -c by the pivoting procedure. -c Well, technically, this routine outputs the Householder vectors -c (or, rather, their second through last entries) -c in the part of a that is supposed to get zeroed, that is, -c in a(j,k) with m >= j > k >= 1. -c -c input: -c m -- first dimension of a and q -c n -- second dimension of a -c a -- matrix whose QR decomposition gets computed -c krank -- desired rank of the output matrix -c (please note that if krank > m or krank > n, -c then the rank of the output matrix will be -c less than krank) -c -c output: -c a -- triangular (R) factor in the QR decompositon -c of the matrix input into the same storage locations, -c with the Householder vectors stored in the part of a -c that would otherwise consist entirely of zeroes, that is, -c in a(j,k) with m >= j > k >= 1 -c ind(k) -- index of the k^th pivot vector; -c the following code segment will correctly rearrange -c the product b of q and the upper triangle of out -c so that b best matches the input matrix in: -c -c copy the non-rearranged product of q and out into b -c set k to krank -c [start of loop] -c swap b(1:m,k) and b(1:m,ind(k)) -c decrement k by 1 -c if k > 0, then go to [start of loop] -c -c work: -c ss -- must be at least n real*8 words long -c -c _N.B._: This routine outputs the Householder vectors -c (or, rather, their second through last entries) -c in the part of a that is supposed to get zeroed, that is, -c in a(j,k) with m >= j > k >= 1. -c -c reference: -c Golub and Van Loan, "Matrix Computations," 3rd edition, -c Johns Hopkins University Press, 1996, Chapter 5. -c - implicit none - integer n,m,ind(n),krank,k,j,kpiv,mm,nupdate,ifrescal, - 1 loops,loop - real*8 a(m,n),ss(n),ssmax,scal,ssmaxin,rswap,feps -c -c - feps = .1d-16 -c -c -c Compute the sum of squares of the entries in each column of a, -c the maximum of all such sums, and find the first pivot -c (column with the greatest such sum). -c - ssmax = 0 - kpiv = 1 -c - do k = 1,n -c - ss(k) = 0 - do j = 1,m - ss(k) = ss(k)+a(j,k)**2 - enddo ! j -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - ssmaxin = ssmax -c - nupdate = 0 -c -c -c Set loops = min(krank,m,n). -c - loops = krank - if(m .lt. loops) loops = m - if(n .lt. loops) loops = n -c - do loop = 1,loops -c -c - mm = m-loop+1 -c -c -c Perform the pivoting. -c - ind(loop) = kpiv -c -c Swap a(1:m,loop) and a(1:m,kpiv). -c - do j = 1,m - rswap = a(j,loop) - a(j,loop) = a(j,kpiv) - a(j,kpiv) = rswap - enddo ! j -c -c Swap ss(loop) and ss(kpiv). -c - rswap = ss(loop) - ss(loop) = ss(kpiv) - ss(kpiv) = rswap -c -c - if(loop .lt. m) then -c -c -c Compute the data for the Householder transformation -c which will zero a(loop+1,loop), ..., a(m,loop) -c when applied to a, replacing a(loop,loop) -c with the first entry of the result of the application -c of the Householder matrix to a(loop:m,loop), -c and storing entries 2 to mm of the Householder vector -c in a(loop+1,loop), ..., a(m,loop) -c (which otherwise would get zeroed upon application -c of the Householder transformation). -c - call idd_house(mm,a(loop,loop),a(loop,loop), - 1 a(loop+1,loop),scal) - ifrescal = 0 -c -c -c Apply the Householder transformation -c to the lower right submatrix of a -c with upper leftmost entry at position (loop,loop+1). -c - if(loop .lt. n) then - do k = loop+1,n - call idd_houseapp(mm,a(loop+1,loop),a(loop,k), - 1 ifrescal,scal,a(loop,k)) - enddo ! k - endif -c -c -c Update the sums-of-squares array ss. -c - do k = loop,n - ss(k) = ss(k)-a(loop,k)**2 - enddo ! k -c -c -c Find the pivot (column with the greatest sum of squares -c of its entries). -c - ssmax = 0 - kpiv = loop+1 -c - if(loop .lt. n) then -c - do k = loop+1,n -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - endif ! loop .lt. n -c -c -c Recompute the sums-of-squares and the pivot -c when ssmax first falls below -c sqrt((1000*feps)^2) * ssmaxin -c and when ssmax first falls below -c ((1000*feps)^2) * ssmaxin. -c - if( - 1 (ssmax .lt. sqrt((1000*feps)**2) * ssmaxin - 2 .and. nupdate .eq. 0) .or. - 3 (ssmax .lt. ((1000*feps)**2) * ssmaxin - 4 .and. nupdate .eq. 1) - 5 ) then -c - nupdate = nupdate+1 -c - ssmax = 0 - kpiv = loop+1 -c - if(loop .lt. n) then -c - do k = loop+1,n -c - ss(k) = 0 - do j = loop+1,m - ss(k) = ss(k)+a(j,k)**2 - enddo ! j -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - endif ! loop .lt. n -c - endif -c -c - endif ! loop .lt. m -c -c - enddo ! loop -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idd_sfft.f b/scipy/linalg/src/id_dist/src/idd_sfft.f deleted file mode 100644 index e46045ac28da..000000000000 --- a/scipy/linalg/src/id_dist/src/idd_sfft.f +++ /dev/null @@ -1,443 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idd_sffti initializes routine idd_sfft. -c -c routine idd_sfft rapidly computes a subset of the entries -c of the DFT of a vector, composed with permutation matrices -c both on input and on output. -c -c routine idd_ldiv finds the greatest integer less than or equal -c to a specified integer, that is divisible by another (larger) -c specified integer. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idd_ldiv(l,n,m) -c -c finds the greatest integer less than or equal to l -c that divides n. -c -c input: -c l -- integer at least as great as m -c n -- integer divisible by m -c -c output: -c m -- greatest integer less than or equal to l that divides n -c - implicit none - integer n,l,m -c -c - m = l -c - 1000 continue - if(m*(n/m) .eq. n) goto 2000 -c - m = m-1 - goto 1000 -c - 2000 continue -c -c - return - end -c -c -c -c - subroutine idd_sffti(l,ind,n,wsave) -c -c initializes wsave for using routine idd_sfft. -c -c input: -c l -- number of pairs of entries in the output of idd_sfft -c to compute -c ind -- indices of the pairs of entries in the output -c of idd_sfft to compute; the indices must be chosen -c in the range from 1 to n/2 -c n -- length of the vector to be transformed -c -c output: -c wsave -- array needed by routine idd_sfft for processing -c (the present routine does not use the last n elements -c of wsave, but routine idd_sfft does) -c - implicit none - integer l,ind(l),n - complex*16 wsave(2*l+15+4*n) -c -c - if(l .eq. 1) call idd_sffti1(ind,n,wsave) - if(l .gt. 1) call idd_sffti2(l,ind,n,wsave) -c -c - return - end -c -c -c -c - subroutine idd_sffti1(ind,n,wsave) -c -c routine idd_sffti serves as a wrapper around -c the present routine; please see routine idd_sffti -c for documentation. -c - implicit none - integer ind,n,k - real*8 r1,twopi,wsave(2*(2+15+4*n)),fact -c - r1 = 1 - twopi = 2*4*atan(r1) -c -c - fact = 1/sqrt(r1*n) -c -c - do k = 1,n - wsave(k) = cos(twopi*(k-1)*ind/(r1*n))*fact - enddo ! k -c - do k = 1,n - wsave(n+k) = -sin(twopi*(k-1)*ind/(r1*n))*fact - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_sffti2(l,ind,n,wsave) -c -c routine idd_sffti serves as a wrapper around -c the present routine; please see routine idd_sffti -c for documentation. -c - implicit none - integer l,ind(l),n,nblock,ii,m,idivm,imodm,i,j,k - real*8 r1,twopi,fact - complex*16 wsave(2*l+15+4*n),ci,twopii -c - ci = (0,1) - r1 = 1 - twopi = 2*4*atan(r1) - twopii = twopi*ci -c -c -c Determine the block lengths for the FFTs. -c - call idd_ldiv(l,n,nblock) - m = n/nblock -c -c -c Initialize wsave for using routine dfftf. -c - call dffti(nblock,wsave) -c -c -c Calculate the coefficients in the linear combinations -c needed for the direct portion of the calculation. -c - fact = 1/sqrt(r1*n) -c - ii = 2*l+15 -c - do j = 1,l -c -c - i = ind(j) -c -c - if(i .le. n/2-m/2) then -c - idivm = (i-1)/m - imodm = (i-1)-m*idivm -c - do k = 1,m - wsave(ii+m*(j-1)+k) = exp(-twopii*(k-1)*imodm/(r1*m)) - 1 * exp(-twopii*(k-1)*(idivm+1)/(r1*n)) * fact - enddo ! k -c - endif ! i .le. n/2-m/2 -c -c - if(i .gt. n/2-m/2) then -c - idivm = i/(m/2) - imodm = i-(m/2)*idivm -c - do k = 1,m - wsave(ii+m*(j-1)+k) = exp(-twopii*(k-1)*imodm/(r1*m)) - 1 * fact - enddo ! k -c - endif ! i .gt. n/2-m/2 -c -c - enddo ! j -c -c - return - end -c -c -c -c - subroutine idd_sfft(l,ind,n,wsave,v) -c -c computes a subset of the entries of the DFT of v, -c composed with permutation matrices both on input and on output, -c via a two-stage procedure (debugging code routine dfftf2 above -c is supposed to calculate the full vector from which idd_sfft -c returns a subset of the entries, when dfftf2 has -c the same parameter nblock as in the present routine). -c -c input: -c l -- number of pairs of entries in the output to compute -c ind -- indices of the pairs of entries in the output -c to compute; the indices must be chosen -c in the range from 1 to n/2 -c n -- length of v; n must be a positive integer power of 2 -c v -- vector to be transformed -c wsave -- processing array initialized by routine idd_sffti -c -c output: -c v -- pairs of entries indexed by ind are given -c their appropriately transformed values -c -c _N.B._: n must be a positive integer power of 2. -c -c references: -c Sorensen and Burrus, "Efficient computation of the DFT with -c only a subset of input or output points," -c IEEE Transactions on Signal Processing, 41 (3): 1184-1200, -c 1993. -c Woolfe, Liberty, Rokhlin, Tygert, "A fast randomized algorithm -c for the approximation of matrices," Applied and -c Computational Harmonic Analysis, 25 (3): 335-366, 2008; -c Section 3.3. -c - implicit none - integer l,ind(l),n - real*8 v(n) - complex*16 wsave(2*l+15+4*n) -c -c - if(l .eq. 1) call idd_sfft1(ind,n,v,wsave) - if(l .gt. 1) call idd_sfft2(l,ind,n,v,wsave) -c -c - return - end -c -c -c -c - subroutine idd_sfft1(ind,n,v,wsave) -c -c routine idd_sfft serves as a wrapper around -c the present routine; please see routine idd_sfft -c for documentation. -c - implicit none - integer ind,n,k - real*8 v(n),r1,twopi,sumr,sumi,fact,wsave(2*(2+15+4*n)) -c - r1 = 1 - twopi = 2*4*atan(r1) -c -c - if(ind .lt. n/2) then -c -c - sumr = 0 -c - do k = 1,n - sumr = sumr+wsave(k)*v(k) - enddo ! k -c -c - sumi = 0 -c - do k = 1,n - sumi = sumi+wsave(n+k)*v(k) - enddo ! k -c -c - endif ! ind .lt. n/2 -c -c - if(ind .eq. n/2) then -c -c - fact = 1/sqrt(r1*n) -c -c - sumr = 0 -c - do k = 1,n - sumr = sumr+v(k) - enddo ! k -c - sumr = sumr*fact -c -c - sumi = 0 -c - do k = 1,n/2 - sumi = sumi+v(2*k-1) - sumi = sumi-v(2*k) - enddo ! k -c - sumi = sumi*fact -c -c - endif ! ind .eq. n/2 -c -c - v(2*ind-1) = sumr - v(2*ind) = sumi -c -c - return - end -c -c -c -c - subroutine idd_sfft2(l,ind,n,v,wsave) -c -c routine idd_sfft serves as a wrapper around -c the present routine; please see routine idd_sfft -c for documentation. -c - implicit none - integer n,m,l,k,j,ind(l),i,idivm,nblock,ii,iii,imodm - real*8 r1,twopi,v(n),rsum,fact - complex*16 wsave(2*l+15+4*n),ci,sum -c - ci = (0,1) - r1 = 1 - twopi = 2*4*atan(r1) -c -c -c Determine the block lengths for the FFTs. -c - call idd_ldiv(l,n,nblock) -c -c - m = n/nblock -c -c -c FFT each block of length nblock of v. -c - do k = 1,m - call dfftf(nblock,v(nblock*(k-1)+1),wsave) - enddo ! k -c -c -c Transpose v to obtain wsave(2*l+15+2*n+1 : 2*l+15+3*n). -c - iii = 2*l+15+2*n -c - do k = 1,m - do j = 1,nblock/2-1 - wsave(iii+m*(j-1)+k) = v(nblock*(k-1)+2*j) - 1 + ci*v(nblock*(k-1)+2*j+1) - enddo ! j - enddo ! k -c -c Handle the purely real frequency components separately. -c - do k = 1,m - wsave(iii+m*(nblock/2-1)+k) = v(nblock*(k-1)+nblock) - wsave(iii+m*(nblock/2)+k) = v(nblock*(k-1)+1) - enddo ! k -c -c -c Directly calculate the desired entries of v. -c - ii = 2*l+15 -c - do j = 1,l -c -c - i = ind(j) -c -c - if(i .le. n/2-m/2) then -c - idivm = (i-1)/m - imodm = (i-1)-m*idivm -c - sum = 0 -c - do k = 1,m - sum = sum + wsave(iii+m*idivm+k) * wsave(ii+m*(j-1)+k) - enddo ! k -c - v(2*i-1) = sum - v(2*i) = -ci*sum -c - endif ! i .le. n/2-m/2 -c -c - if(i .gt. n/2-m/2) then -c - if(i .lt. n/2) then -c - idivm = i/(m/2) - imodm = i-(m/2)*idivm -c - sum = 0 -c - do k = 1,m - sum = sum + wsave(iii+m*(nblock/2)+k) - 1 * wsave(ii+m*(j-1)+k) - enddo ! k -c - v(2*i-1) = sum - v(2*i) = -ci*sum -c - endif -c - if(i .eq. n/2) then -c - fact = 1/sqrt(r1*n) -c -c - rsum = 0 -c - do k = 1,m - rsum = rsum + wsave(iii+m*(nblock/2)+k) - enddo ! k -c - v(n-1) = rsum*fact -c -c - rsum = 0 -c - do k = 1,m/2 - rsum = rsum + wsave(iii+m*(nblock/2)+2*k-1) - rsum = rsum - wsave(iii+m*(nblock/2)+2*k) - enddo ! k -c - v(n) = rsum*fact -c - endif -c - endif ! i .gt. n/2-m/2 -c -c - enddo ! j -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idd_snorm.f b/scipy/linalg/src/id_dist/src/idd_snorm.f deleted file mode 100644 index c718ce12f629..000000000000 --- a/scipy/linalg/src/id_dist/src/idd_snorm.f +++ /dev/null @@ -1,400 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idd_snorm estimates the spectral norm -c of a matrix specified by routines for applying the matrix -c and its transpose to arbitrary vectors. This routine uses -c the power method with a random starting vector. -c -c routine idd_diffsnorm estimates the spectral norm -c of the difference between two matrices specified by routines -c for applying the matrices and their transposes -c to arbitrary vectors. This routine uses -c the power method with a random starting vector. -c -c routine idd_enorm calculates the Euclidean norm of a vector. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idd_snorm(m,n,matvect,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,its,snorm,v,u) -c -c estimates the spectral norm of a matrix a specified -c by a routine matvec for applying a to an arbitrary vector, -c and by a routine matvect for applying a^T -c to an arbitrary vector. This routine uses the power method -c with a random starting vector. -c -c input: -c m -- number of rows in a -c n -- number of columns in a -c matvect -- routine which applies the transpose of a -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvect(m,x,n,y,p1t,p2t,p3t,p4t), -c -c where m is the length of x, -c x is the vector to which the transpose of a -c is to be applied, -c n is the length of y, -c y is the product of the transpose of a and x, -c and p1t, p2t, p3t, and p4t are user-specified -c parameters -c p1t -- parameter to be passed to routine matvect -c p2t -- parameter to be passed to routine matvect -c p3t -- parameter to be passed to routine matvect -c p4t -- parameter to be passed to routine matvect -c matvec -- routine which applies the matrix a -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvec(n,x,m,y,p1,p2,p3,p4), -c -c where n is the length of x, -c x is the vector to which a is to be applied, -c m is the length of y, -c y is the product of a and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvec -c p2 -- parameter to be passed to routine matvec -c p3 -- parameter to be passed to routine matvec -c p4 -- parameter to be passed to routine matvec -c its -- number of iterations of the power method to conduct -c -c output: -c snorm -- estimate of the spectral norm of a -c v -- estimate of a normalized right singular vector -c corresponding to the greatest singular value of a -c -c work: -c u -- must be at least m real*8 elements long -c -c reference: -c Kuczynski and Wozniakowski, "Estimating the largest eigenvalue -c by the power and Lanczos algorithms with a random start," -c SIAM Journal on Matrix Analysis and Applications, -c 13 (4): 1992, 1094-1122. -c - implicit none - integer m,n,its,it,k - real*8 snorm,enorm,p1t,p2t,p3t,p4t,p1,p2,p3,p4,u(m),v(n) - external matvect,matvec -c -c -c Fill the real and imaginary parts of each entry -c of the initial vector v with i.i.d. random variables -c drawn uniformly from [-1,1]. -c - call id_srand(n,v) -c - do k = 1,n - v(k) = 2*v(k)-1 - enddo ! k -c -c -c Normalize v. -c - call idd_enorm(n,v,enorm) -c - do k = 1,n - v(k) = v(k)/enorm - enddo ! k -c -c - do it = 1,its -c -c Apply a to v, obtaining u. -c - call matvec(n,v,m,u,p1,p2,p3,p4) -c -c Apply a^T to u, obtaining v. -c - call matvect(m,u,n,v,p1t,p2t,p3t,p4t) -c -c Normalize v. -c - call idd_enorm(n,v,snorm) -c - if(snorm .gt. 0) then -c - do k = 1,n - v(k) = v(k)/snorm - enddo ! k -c - endif -c - snorm = sqrt(snorm) -c - enddo ! it -c -c - return - end -c -c -c -c - subroutine idd_enorm(n,v,enorm) -c -c computes the Euclidean norm of v, the square root -c of the sum of the squares of the entries of v. -c -c input: -c n -- length of v -c v -- vector whose Euclidean norm is to be calculated -c -c output: -c enorm -- Euclidean norm of v -c - implicit none - integer n,k - real*8 enorm,v(n) -c -c - enorm = 0 -c - do k = 1,n - enorm = enorm+v(k)**2 - enddo ! k -c - enorm = sqrt(enorm) -c -c - return - end -c -c -c -c - subroutine idd_diffsnorm(m,n,matvect,p1t,p2t,p3t,p4t, - 1 matvect2,p1t2,p2t2,p3t2,p4t2, - 2 matvec,p1,p2,p3,p4, - 3 matvec2,p12,p22,p32,p42,its,snorm,w) -c -c estimates the spectral norm of the difference between matrices -c a and a2, where a is specified by routines matvec and matvect -c for applying a and a^T to arbitrary vectors, -c and a2 is specified by routines matvec2 and matvect2 -c for applying a2 and (a2)^T to arbitrary vectors. -c This routine uses the power method -c with a random starting vector. -c -c input: -c m -- number of rows in a, as well as the number of rows in a2 -c n -- number of columns in a, as well as the number of columns -c in a2 -c matvect -- routine which applies the transpose of a -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvect(m,x,n,y,p1t,p2t,p3t,p4t), -c -c where m is the length of x, -c x is the vector to which the transpose of a -c is to be applied, -c n is the length of y, -c y is the product of the transpose of a and x, -c and p1t, p2t, p3t, and p4t are user-specified -c parameters -c p1t -- parameter to be passed to routine matvect -c p2t -- parameter to be passed to routine matvect -c p3t -- parameter to be passed to routine matvect -c p4t -- parameter to be passed to routine matvect -c matvect2 -- routine which applies the transpose of a2 -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvect2(m,x,n,y,p1t2,p2t2,p3t2,p4t2), -c -c where m is the length of x, -c x is the vector to which the transpose of a2 -c is to be applied, -c n is the length of y, -c y is the product of the transpose of a2 and x, -c and p1t2, p2t2, p3t2, and p4t2 are user-specified -c parameters -c p1t2 -- parameter to be passed to routine matvect2 -c p2t2 -- parameter to be passed to routine matvect2 -c p3t2 -- parameter to be passed to routine matvect2 -c p4t2 -- parameter to be passed to routine matvect2 -c matvec -- routine which applies the matrix a -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvec(n,x,m,y,p1,p2,p3,p4), -c -c where n is the length of x, -c x is the vector to which a is to be applied, -c m is the length of y, -c y is the product of a and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvec -c p2 -- parameter to be passed to routine matvec -c p3 -- parameter to be passed to routine matvec -c p4 -- parameter to be passed to routine matvec -c matvec2 -- routine which applies the matrix a2 -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvec2(n,x,m,y,p12,p22,p32,p42), -c -c where n is the length of x, -c x is the vector to which a2 is to be applied, -c m is the length of y, -c y is the product of a2 and x, and -c p12, p22, p32, and p42 are user-specified parameters -c p12 -- parameter to be passed to routine matvec2 -c p22 -- parameter to be passed to routine matvec2 -c p32 -- parameter to be passed to routine matvec2 -c p42 -- parameter to be passed to routine matvec2 -c its -- number of iterations of the power method to conduct -c -c output: -c snorm -- estimate of the spectral norm of a-a2 -c -c work: -c w -- must be at least 3*m+3*n real*8 elements long -c -c reference: -c Kuczynski and Wozniakowski, "Estimating the largest eigenvalue -c by the power and Lanczos algorithms with a random start," -c SIAM Journal on Matrix Analysis and Applications, -c 13 (4): 1992, 1094-1122. -c - implicit none - integer m,n,its,lw,iu,lu,iu1,lu1,iu2,lu2, - 1 iv,lv,iv1,lv1,iv2,lv2 - real*8 snorm,p1t,p2t,p3t,p4t,p1t2,p2t2,p3t2,p4t2, - 1 p1,p2,p3,p4,p12,p22,p32,p42,w(3*m+3*n) - external matvect,matvec,matvect2,matvec2 -c -c -c Allocate memory in w. -c - lw = 0 -c - iu = lw+1 - lu = m - lw = lw+lu -c - iu1 = lw+1 - lu1 = m - lw = lw+lu1 -c - iu2 = lw+1 - lu2 = m - lw = lw+lu2 -c - iv = lw+1 - lv = n - lw = lw+1 -c - iv1 = lw+1 - lv1 = n - lw = lw+lv1 -c - iv2 = lw+1 - lv2 = n - lw = lw+lv2 -c -c - call idd_diffsnorm0(m,n,matvect,p1t,p2t,p3t,p4t, - 1 matvect2,p1t2,p2t2,p3t2,p4t2, - 2 matvec,p1,p2,p3,p4, - 3 matvec2,p12,p22,p32,p42, - 4 its,snorm,w(iu),w(iu1),w(iu2), - 5 w(iv),w(iv1),w(iv2)) -c -c - return - end -c -c -c -c - subroutine idd_diffsnorm0(m,n,matvect,p1t,p2t,p3t,p4t, - 1 matvect2,p1t2,p2t2,p3t2,p4t2, - 2 matvec,p1,p2,p3,p4, - 3 matvec2,p12,p22,p32,p42, - 4 its,snorm,u,u1,u2,v,v1,v2) -c -c routine idd_diffsnorm serves as a memory wrapper -c for the present routine. (Please see routine idd_diffsnorm -c for further documentation.) -c - implicit none - integer m,n,its,it,k - real*8 snorm,enorm,p1t,p2t,p3t,p4t,p1t2,p2t2,p3t2,p4t2, - 1 p1,p2,p3,p4,p12,p22,p32,p42,u(m),u1(m),u2(m), - 2 v(n),v1(n),v2(n) - external matvect,matvec,matvect2,matvec2 -c -c -c Fill the real and imaginary parts of each entry -c of the initial vector v with i.i.d. random variables -c drawn uniformly from [-1,1]. -c - call id_srand(n,v) -c - do k = 1,n - v(k) = 2*v(k)-1 - enddo ! k -c -c -c Normalize v. -c - call idd_enorm(n,v,enorm) -c - do k = 1,n - v(k) = v(k)/enorm - enddo ! k -c -c - do it = 1,its -c -c Apply a and a2 to v, obtaining u1 and u2. -c - call matvec(n,v,m,u1,p1,p2,p3,p4) - call matvec2(n,v,m,u2,p12,p22,p32,p42) -c -c Form u = u1-u2. -c - do k = 1,m - u(k) = u1(k)-u2(k) - enddo ! k -c -c Apply a^T and (a2)^T to u, obtaining v1 and v2. -c - call matvect(m,u,n,v1,p1t,p2t,p3t,p4t) - call matvect2(m,u,n,v2,p1t2,p2t2,p3t2,p4t2) -c -c Form v = v1-v2. -c - do k = 1,n - v(k) = v1(k)-v2(k) - enddo ! k -c -c Normalize v. -c - call idd_enorm(n,v,snorm) -c - if(snorm .gt. 0) then -c - do k = 1,n - v(k) = v(k)/snorm - enddo ! k -c - endif -c - snorm = sqrt(snorm) -c - enddo ! it -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idd_svd.f b/scipy/linalg/src/id_dist/src/idd_svd.f deleted file mode 100644 index 969422b8c738..000000000000 --- a/scipy/linalg/src/id_dist/src/idd_svd.f +++ /dev/null @@ -1,409 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine iddr_svd computes an approximation of specified rank -c to a given matrix, in the usual SVD form U S V^T, -c where U has orthonormal columns, V has orthonormal columns, -c and S is diagonal. -c -c routine iddp_svd computes an approximation of specified -c precision to a given matrix, in the usual SVD form U S V^T, -c where U has orthonormal columns, V has orthonormal columns, -c and S is diagonal. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine iddr_svd(m,n,a,krank,u,v,s,ier,r) -c -c constructs a rank-krank SVD u diag(s) v^T approximating a, -c where u is an m x krank matrix whose columns are orthonormal, -c v is an n x krank matrix whose columns are orthonormal, -c and diag(s) is a diagonal krank x krank matrix whose entries -c are all nonnegative. This routine combines a QR code -c (which is based on plane/Householder reflections) -c with the LAPACK routine dgesdd. -c -c input: -c m -- first dimension of a and u -c n -- second dimension of a, and first dimension of v -c a -- matrix to be SVD'd -c krank -- desired rank of the approximation to a -c -c output: -c u -- left singular vectors of a corresponding -c to the k greatest singular values of a -c v -- right singular vectors of a corresponding -c to the k greatest singular values of a -c s -- k greatest singular values of a -c ier -- 0 when the routine terminates successfully; -c nonzero when the routine encounters an error -c -c work: -c r -- must be at least -c (krank+2)*n+8*min(m,n)+15*krank**2+8*krank -c real*8 elements long -c -c _N.B._: This routine destroys a. Also, please beware that -c the source code for this routine could be clearer. -c - implicit none - character*1 jobz - integer m,n,k,krank,iftranspose,ldr,ldu,ldvt,lwork, - 1 info,j,ier,io - real*8 a(m,n),u(m,krank),v(n*krank),s(krank),r(*) -c -c - io = 8*min(m,n) -c -c - ier = 0 -c -c -c Compute a pivoted QR decomposition of a. -c - call iddr_qrpiv(m,n,a,krank,r,r(io+1)) -c -c -c Extract R from the QR decomposition. -c - call idd_retriever(m,n,a,krank,r(io+1)) -c -c -c Rearrange R according to ind (which is stored in r). -c - call idd_permuter(krank,r,krank,n,r(io+1)) -c -c -c Use LAPACK to SVD R, -c storing the krank (krank x 1) left singular vectors -c in r(io+krank*n+1 : io+krank*n+krank*krank). -c - jobz = 'S' - ldr = krank - lwork = 2*(3*krank**2+n+4*krank**2+4*krank) - ldu = krank - ldvt = krank -c - call dgesdd(jobz,krank,n,r(io+1),ldr,s,r(io+krank*n+1),ldu, - 1 v,ldvt,r(io+krank*n+krank*krank+1),lwork,r,info) -c - if(info .ne. 0) then - ier = info - return - endif -c -c -c Multiply the U from R from the left by Q to obtain the U -c for A. -c - do k = 1,krank -c - do j = 1,krank - u(j,k) = r(io+krank*n+j+krank*(k-1)) - enddo ! j -c - do j = krank+1,m - u(j,k) = 0 - enddo ! j -c - enddo ! k -c - iftranspose = 0 - call idd_qmatmat(iftranspose,m,n,a,krank,krank,u,r) -c -c -c Transpose v to obtain r. -c - call idd_transer(krank,n,v,r) -c -c -c Copy r into v. -c - do k = 1,n*krank - v(k) = r(k) - enddo ! k -c -c - return - end -c -c -c -c - subroutine iddp_svd(lw,eps,m,n,a,krank,iu,iv,is,w,ier) -c -c constructs a rank-krank SVD U Sigma V^T approximating a -c to precision eps, where U is an m x krank matrix whose -c columns are orthonormal, V is an n x krank matrix whose -c columns are orthonormal, and Sigma is a diagonal krank x krank -c matrix whose entries are all nonnegative. -c The entries of U are stored in w, starting at w(iu); -c the entries of V are stored in w, starting at w(iv). -c The diagonal entries of Sigma are stored in w, -c starting at w(is). This routine combines a QR code -c (which is based on plane/Householder reflections) -c with the LAPACK routine dgesdd. -c -c input: -c lw -- maximum usable length of w (in real*8 elements) -c eps -- precision to which the SVD approximates a -c m -- first dimension of a and u -c n -- second dimension of a, and first dimension of v -c a -- matrix to be SVD'd -c -c output: -c krank -- rank of the approximation to a -c iu -- index in w of the first entry of the matrix -c of orthonormal left singular vectors of a -c iv -- index in w of the first entry of the matrix -c of orthonormal right singular vectors of a -c is -- index in w of the first entry of the array -c of singular values of a -c w -- array containing the singular values and singular vectors -c of a; w doubles as a work array, and so must be at least -c (krank+1)*(m+2*n+9)+8*min(m,n)+15*krank**2 -c real*8 elements long, where krank is the rank -c output by the present routine -c ier -- 0 when the routine terminates successfully; -c -1000 when lw is too small; -c other nonzero values when dgesdd bombs -c -c _N.B._: This routine destroys a. Also, please beware that -c the source code for this routine could be clearer. -c w must be at least -c (krank+1)*(m+2*n+9)+8*min(m,n)+15*krank**2 -c real*8 elements long, where krank is the rank -c output by the present routine. -c - implicit none - character*1 jobz - integer m,n,k,krank,iftranspose,ldr,ldu,ldvt,lwork, - 1 info,j,ier,io,iu,iv,is,ivi,isi,lw,lu,lv,ls - real*8 a(m,n),w(*),eps -c -c - io = 8*min(m,n) -c -c - ier = 0 -c -c -c Compute a pivoted QR decomposition of a. -c - call iddp_qrpiv(eps,m,n,a,krank,w,w(io+1)) -c -c - if(krank .gt. 0) then -c -c -c Extract R from the QR decomposition. -c - call idd_retriever(m,n,a,krank,w(io+1)) -c -c -c Rearrange R according to ind (which is stored in w). -c - call idd_permuter(krank,w,krank,n,w(io+1)) -c -c -c Use LAPACK to SVD R, -c storing the krank (krank x 1) left singular vectors -c in w(io+krank*n+1 : io+krank*n+krank*krank). -c - jobz = 'S' - ldr = krank - lwork = 2*(3*krank**2+n+4*krank**2+4*krank) - ldu = krank - ldvt = krank -c - ivi = io+krank*n+krank*krank+lwork+1 - lv = n*krank -c - isi = ivi+lv - ls = krank -c - if(lw .lt. isi+ls+m*krank-1) then - ier = -1000 - return - endif -c - call dgesdd(jobz,krank,n,w(io+1),ldr,w(isi),w(io+krank*n+1), - 1 ldu,w(ivi),ldvt,w(io+krank*n+krank*krank+1), - 2 lwork,w,info) -c - if(info .ne. 0) then - ier = info - return - endif -c -c -c Transpose w(ivi:ivi+lv-1) to obtain V. -c - iv = 1 - call idd_transer(krank,n,w(ivi),w(iv)) -c -c -c Copy w(isi:isi+ls-1) into w(is:is+ls-1). -c - is = iv+lv -c - do k = 1,ls - w(is+k-1) = w(isi+k-1) - enddo ! k -c -c -c Multiply the U from R from the left by Q to obtain the U -c for A. -c - iu = is+ls - lu = m*krank -c - do k = 1,krank -c - do j = 1,krank - w(iu-1+j+krank*(k-1)) = w(io+krank*n+j+krank*(k-1)) - enddo ! j -c - enddo ! k -c - do k = krank,1,-1 -c - do j = m,krank+1,-1 - w(iu-1+j+m*(k-1)) = 0 - enddo ! j -c - do j = krank,1,-1 - w(iu-1+j+m*(k-1)) = w(iu-1+j+krank*(k-1)) - enddo ! j -c - enddo ! k -c - iftranspose = 0 - call idd_qmatmat(iftranspose,m,n,a,krank,krank,w(iu), - 1 w(iu+lu+1)) -c -c - endif ! krank .gt. 0 -c -c - return - end -c -c -c -c - subroutine idd_permuter(krank,ind,m,n,a) -c -c permutes the columns of a according to ind obtained -c from routine iddr_qrpiv or iddp_qrpiv, assuming that -c a = q r from iddr_qrpiv or iddp_qrpiv. -c -c input: -c krank -- rank specified to routine iddr_qrpiv -c or obtained from routine iddp_qrpiv -c ind -- indexing array obtained from routine iddr_qrpiv -c or iddp_qrpiv -c m -- first dimension of a -c n -- second dimension of a -c a -- matrix to be rearranged -c -c output: -c a -- rearranged matrix -c - implicit none - integer k,krank,m,n,j,ind(krank) - real*8 rswap,a(m,n) -c -c - do k = krank,1,-1 - do j = 1,m -c - rswap = a(j,k) - a(j,k) = a(j,ind(k)) - a(j,ind(k)) = rswap -c - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_retriever(m,n,a,krank,r) -c -c extracts R in the QR decomposition specified by the output a -c of the routine iddr_qrpiv or iddp_qrpiv -c -c input: -c m -- first dimension of a -c n -- second dimension of a and r -c a -- output of routine iddr_qrpiv or iddp_qrpiv -c krank -- rank specified to routine iddr_qrpiv, -c or output by routine iddp_qrpiv -c -c output: -c r -- triangular factor in the QR decomposition specified -c by the output a of the routine iddr_qrpiv or iddp_qrpiv -c - implicit none - integer m,n,j,k,krank - real*8 a(m,n),r(krank,n) -c -c -c Copy a into r and zero out the appropriate -c Householder vectors that are stored in one triangle of a. -c - do k = 1,n - do j = 1,krank - r(j,k) = a(j,k) - enddo ! j - enddo ! k -c - do k = 1,n - if(k .lt. krank) then - do j = k+1,krank - r(j,k) = 0 - enddo ! j - endif - enddo ! k -c -c - return - end -c -c -c -c - subroutine idd_transer(m,n,a,at) -c -c forms the transpose at of a. -c -c input: -c m -- first dimension of a and second dimension of at -c n -- second dimension of a and first dimension of at -c a -- matrix to be transposed -c -c output: -c at -- transpose of a -c - implicit none - integer m,n,j,k - real*8 a(m,n),at(n,m) -c -c - do k = 1,n - do j = 1,m - at(k,j) = a(j,k) - enddo ! j - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/iddp_aid.f b/scipy/linalg/src/id_dist/src/iddp_aid.f deleted file mode 100644 index f3f9ddfdd597..000000000000 --- a/scipy/linalg/src/id_dist/src/iddp_aid.f +++ /dev/null @@ -1,386 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine iddp_aid computes the ID, to a specified precision, -c of an arbitrary matrix. This routine is randomized. -c -c routine idd_estrank estimates the numerical rank, -c to a specified precision, of an arbitrary matrix. -c This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine iddp_aid(eps,m,n,a,work,krank,list,proj) -c -c computes the ID of the matrix a, i.e., lists in list -c the indices of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c krank -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank) (*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon dimensioned epsilon(m,n-krank) -c such that the greatest singular value of epsilon -c <= the greatest singular value of a * eps. -c -c input: -c eps -- precision to which the ID is to be computed -c m -- first dimension of a -c n -- second dimension of a -c a -- matrix to be decomposed; the present routine does not -c alter a -c work -- initialization array that has been constructed -c by routine idd_frmi -c -c output: -c krank -- numerical rank of a to precision eps -c list -- indices of the columns in the ID -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns -c in the original matrix being ID'd; -c proj doubles as a work array in the present routine, so -c proj must be at least n*(2*n2+1)+n2+1 real*8 elements -c long, where n2 is the greatest integer less than -c or equal to m, such that n2 is a positive integer -c power of two. -c -c _N.B._: The algorithm used by this routine is randomized. -c proj must be at least n*(2*n2+1)+n2+1 real*8 elements -c long, where n2 is the greatest integer less than -c or equal to m, such that n2 is a positive integer -c power of two. -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,n,list(n),krank,kranki,n2 - real*8 eps,a(m,n),proj(*),work(17*m+70) -c -c -c Allocate memory in proj. -c - n2 = work(2) -c -c -c Find the rank of a. -c - call idd_estrank(eps,m,n,a,work,kranki,proj) -c -c - if(kranki .eq. 0) call iddp_aid0(eps,m,n,a,krank,list,proj, - 1 proj(m*n+1)) -c - if(kranki .ne. 0) call iddp_aid1(eps,n2,n,kranki,proj, - 1 krank,list,proj(n2*n+1)) -c -c - return - end -c -c -c -c - subroutine iddp_aid0(eps,m,n,a,krank,list,proj,rnorms) -c -c uses routine iddp_id to ID a without modifying its entries -c (in contrast to the usual behavior of iddp_id). -c -c input: -c eps -- precision of the decomposition to be constructed -c m -- first dimension of a -c n -- second dimension of a -c -c output: -c krank -- numerical rank of the ID -c list -- indices of the columns in the ID -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns in a; -c proj doubles as a work array in the present routine, so -c must be at least m*n real*8 elements long -c -c work: -c rnorms -- must be at least n real*8 elements long -c -c _N.B._: proj must be at least m*n real*8 elements long -c - implicit none - integer m,n,krank,list(n),j,k - real*8 eps,a(m,n),proj(m,n),rnorms(n) -c -c -c Copy a into proj. -c - do k = 1,n - do j = 1,m - proj(j,k) = a(j,k) - enddo ! j - enddo ! k -c -c -c ID proj. -c - call iddp_id(eps,m,n,proj,krank,list,rnorms) -c -c - return - end -c -c -c -c - subroutine iddp_aid1(eps,n2,n,kranki,proj,krank,list,rnorms) -c -c IDs the uppermost kranki x n block of the n2 x n matrix -c input as proj. -c -c input: -c eps -- precision of the decomposition to be constructed -c n2 -- first dimension of proj as input -c n -- second dimension of proj as input -c kranki -- number of rows to extract from proj -c proj -- matrix containing the kranki x n block to be ID'd -c -c output: -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns -c in the original matrix being ID'd -c krank -- numerical rank of the ID -c list -- indices of the columns in the ID -c -c work: -c rnorms -- must be at least n real*8 elements long -c - implicit none - integer n,n2,kranki,krank,list(n),j,k - real*8 eps,proj(n2*n),rnorms(n) -c -c -c Move the uppermost kranki x n block of the n2 x n matrix proj -c to the beginning of proj. -c - do k = 1,n - do j = 1,kranki - proj(j+kranki*(k-1)) = proj(j+n2*(k-1)) - enddo ! j - enddo ! k -c -c -c ID proj. -c - call iddp_id(eps,kranki,n,proj,krank,list,rnorms) -c -c - return - end -c -c -c -c - subroutine idd_estrank(eps,m,n,a,w,krank,ra) -c -c estimates the numerical rank krank of an m x n matrix a -c to precision eps. This routine applies n2 random vectors -c to a, obtaining ra, where n2 is the greatest integer -c less than or equal to m such that n2 is a positive integer -c power of two. krank is typically about 8 higher than -c the actual numerical rank. -c -c input: -c eps -- precision defining the numerical rank -c m -- first dimension of a -c n -- second dimension of a -c a -- matrix whose rank is to be estimated -c w -- initialization array that has been constructed -c by routine idd_frmi -c -c output: -c krank -- estimate of the numerical rank of a; -c this routine returns krank = 0 when the actual -c numerical rank is nearly full (that is, -c greater than n - 8 or n2 - 8) -c ra -- product of an n2 x m random matrix and the m x n matrix -c a, where n2 is the greatest integer less than or equal -c to m such that n2 is a positive integer power of two; -c ra doubles as a work array in the present routine, and so -c must be at least n*n2+(n+1)*(n2+1) real*8 elements long -c -c _N.B._: ra must be at least n*n2+(n2+1)*(n+1) real*8 -c elements long for use in the present routine -c (here, n2 is the greatest integer less than or equal -c to m, such that n2 is a positive integer power of two). -c This routine returns krank = 0 when the actual -c numerical rank is nearly full. -c - implicit none - integer m,n,krank,n2,irat,lrat,iscal,lscal,ira,lra,lra2 - real*8 eps,a(m,n),ra(*),w(17*m+70) -c -c -c Extract from the array w initialized by routine idd_frmi -c the greatest integer less than or equal to m that is -c a positive integer power of two. -c - n2 = w(2) -c -c -c Allocate memory in ra. -c - lra = 0 -c - ira = lra+1 - lra2 = n2*n - lra = lra+lra2 -c - irat = lra+1 - lrat = n*(n2+1) - lra = lra+lrat -c - iscal = lra+1 - lscal = n2+1 - lra = lra+lscal -c - call idd_estrank0(eps,m,n,a,w,n2,krank,ra(ira),ra(irat), - 1 ra(iscal)) -c -c - return - end -c -c -c -c - subroutine idd_estrank0(eps,m,n,a,w,n2,krank,ra,rat,scal) -c -c routine idd_estrank serves as a memory wrapper -c for the present routine. (Please see routine idd_estrank -c for further documentation.) -c - implicit none - integer m,n,n2,krank,ifrescal,k,nulls,j - real*8 a(m,n),ra(n2,n),scal(n2+1),eps,residual, - 1 w(17*m+70),rat(n,n2+1),ss,ssmax -c -c -c Apply the random matrix to every column of a, obtaining ra. -c - do k = 1,n - call idd_frm(m,n2,w,a(1,k),ra(1,k)) - enddo ! k -c -c -c Compute the sum of squares of the entries in each column of ra -c and the maximum of all such sums. -c - ssmax = 0 -c - do k = 1,n -c - ss = 0 - do j = 1,m - ss = ss+a(j,k)**2 - enddo ! j -c - if(ss .gt. ssmax) ssmax = ss -c - enddo ! k -c -c -c Transpose ra to obtain rat. -c - call idd_atransposer(n2,n,ra,rat) -c -c - krank = 0 - nulls = 0 -c -c -c Loop until nulls = 7, krank+nulls = n2, or krank+nulls = n. -c - 1000 continue -c -c - if(krank .gt. 0) then -c -c Apply the previous Householder transformations -c to rat(:,krank+1). -c - ifrescal = 0 -c - do k = 1,krank - call idd_houseapp(n-k+1,rat(1,k),rat(k,krank+1), - 1 ifrescal,scal(k),rat(k,krank+1)) - enddo ! k -c - endif ! krank .gt. 0 -c -c -c Compute the Householder vector associated -c with rat(krank+1:*,krank+1). -c - call idd_house(n-krank,rat(krank+1,krank+1), - 1 residual,rat(1,krank+1),scal(krank+1)) - residual = abs(residual) -c -c - krank = krank+1 - if(residual .le. eps*sqrt(ssmax)) nulls = nulls+1 -c -c - if(nulls .lt. 7 .and. krank+nulls .lt. n2 - 1 .and. krank+nulls .lt. n) - 2 goto 1000 -c -c - if(nulls .lt. 7) krank = 0 -c -c - return - end -c -c -c -c - subroutine idd_atransposer(m,n,a,at) -c -c transposes a to obtain at. -c -c input: -c m -- first dimension of a, and second dimension of at -c n -- second dimension of a, and first dimension of at -c a -- matrix to be transposed -c -c output: -c at -- transpose of a -c - implicit none - integer m,n,j,k - real*8 a(m,n),at(n,m) -c -c - do k = 1,n - do j = 1,m -c - at(k,j) = a(j,k) -c - enddo ! j - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/iddp_asvd.f b/scipy/linalg/src/id_dist/src/iddp_asvd.f deleted file mode 100644 index a3dea461113b..000000000000 --- a/scipy/linalg/src/id_dist/src/iddp_asvd.f +++ /dev/null @@ -1,180 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine iddp_asvd computes the SVD, to a specified precision, -c of an arbitrary matrix. This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine iddp_asvd(lw,eps,m,n,a,winit,krank,iu,iv,is,w,ier) -c -c constructs a rank-krank SVD U Sigma V^T approximating a -c to precision eps, where U is an m x krank matrix whose -c columns are orthonormal, V is an n x krank matrix whose -c columns are orthonormal, and Sigma is a diagonal krank x krank -c matrix whose entries are all nonnegative. -c The entries of U are stored in w, starting at w(iu); -c the entries of V are stored in w, starting at w(iv). -c The diagonal entries of Sigma are stored in w, -c starting at w(is). This routine uses a randomized algorithm. -c -c input: -c lw -- maximum usable length (in real*8 elements) -c of the array w -c eps -- precision of the desired approximation -c m -- number of rows in a -c n -- number of columns in a -c a -- matrix to be approximated; the present routine does not -c alter a -c winit -- initialization array that has been constructed -c by routine idd_frmi -c -c output: -c krank -- rank of the SVD constructed -c iu -- index in w of the first entry of the matrix -c of orthonormal left singular vectors of a -c iv -- index in w of the first entry of the matrix -c of orthonormal right singular vectors of a -c is -- index in w of the first entry of the array -c of singular values of a -c w -- array containing the singular values and singular vectors -c of a; w doubles as a work array, and so must be at least -c max( (krank+1)*(3*m+5*n+1)+25*krank**2, (2*n+1)*(n2+1) ) -c real*8 elements long, where n2 is the greatest integer -c less than or equal to m, such that n2 is -c a positive integer power of two; krank is the rank output -c by this routine -c ier -- 0 when the routine terminates successfully; -c -1000 when lw is too small; -c other nonzero values when idd_id2svd bombs -c -c _N.B._: w must be at least -c max( (krank+1)*(3*m+5*n+1)+25*krank^2, (2*n+1)*(n2+1) ) -c real*8 elements long, where n2 is the greatest integer -c less than or equal to m, such that n2 is -c a positive integer power of two; -c krank is the rank output by this routine. -c Also, the algorithm used by this routine is randomized. -c - implicit none - integer m,n,krank,lw,ilist,llist,iproj,lproj,icol,lcol, - 1 iwork,lwork,k,ier,lw2,iu,iv,is,iui,ivi,isi,lu,lv,ls - real*8 eps,a(m,n),winit(17*m+70),w(*) -c -c -c Allocate memory in w. -c - lw2 = 0 -c - ilist = lw2+1 - llist = n - lw2 = lw2+llist -c - iproj = lw2+1 -c -c -c ID a. -c - call iddp_aid(eps,m,n,a,winit,krank,w(ilist),w(iproj)) -c -c - if(krank .gt. 0) then -c -c -c Allocate more memory in w. -c - lproj = krank*(n-krank) - lw2 = lw2+lproj -c - icol = lw2+1 - lcol = m*krank - lw2 = lw2+lcol -c - iui = lw2+1 - lu = m*krank - lw2 = lw2+lu -c - ivi = lw2+1 - lv = n*krank - lw2 = lw2+lv -c - isi = lw2+1 - ls = krank - lw2 = lw2+ls -c - iwork = lw2+1 - lwork = (krank+1)*(m+3*n)+26*krank**2 - lw2 = lw2+lwork -c -c - if(lw .lt. lw2) then - ier = -1000 - return - endif -c -c - call iddp_asvd0(m,n,a,krank,w(ilist),w(iproj), - 1 w(iui),w(ivi),w(isi),ier,w(icol),w(iwork)) - if(ier .ne. 0) return -c -c - iu = 1 - iv = iu+lu - is = iv+lv -c -c -c Copy the singular values and singular vectors -c into their proper locations. -c - do k = 1,lu - w(iu+k-1) = w(iui+k-1) - enddo ! k -c - do k = 1,lv - w(iv+k-1) = w(ivi+k-1) - enddo ! k -c - do k = 1,ls - w(is+k-1) = w(isi+k-1) - enddo ! k -c -c - endif ! krank .gt. 0 -c -c - return - end -c -c -c -c - subroutine iddp_asvd0(m,n,a,krank,list,proj,u,v,s,ier, - 1 col,work) -c -c routine iddp_asvd serves as a memory wrapper -c for the present routine (please see routine iddp_asvd -c for further documentation). -c - implicit none - integer m,n,krank,list(n),ier - real*8 a(m,n),u(m,krank),v(n,krank), - 1 s(krank),proj(krank,n-krank),col(m,krank), - 2 work((krank+1)*(m+3*n)+26*krank**2) -c -c -c Collect together the columns of a indexed by list into col. -c - call idd_copycols(m,n,a,krank,list,col) -c -c -c Convert the ID to an SVD. -c - call idd_id2svd(m,krank,col,n,list,proj,u,v,s,ier,work) -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/iddp_rid.f b/scipy/linalg/src/id_dist/src/iddp_rid.f deleted file mode 100644 index 93b255f15415..000000000000 --- a/scipy/linalg/src/id_dist/src/iddp_rid.f +++ /dev/null @@ -1,376 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine iddp_rid computes the ID, to a specified precision, -c of a matrix specified by a routine for applying its transpose -c to arbitrary vectors. This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine iddp_rid(lproj,eps,m,n,matvect,p1,p2,p3,p4, - 1 krank,list,proj,ier) -c -c computes the ID of a, i.e., lists in list the indices -c of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c krank -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank) (*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon dimensioned epsilon(m,n-krank) -c such that the greatest singular value of epsilon -c <= the greatest singular value of a * eps. -c -c input: -c lproj -- maximum usable length (in real*8 elements) -c of the array proj -c eps -- precision to which the ID is to be computed -c m -- first dimension of a -c n -- second dimension of a -c matvect -- routine which applies the transpose -c of the matrix to be ID'd to an arbitrary vector; -c this routine must have a calling sequence -c of the form -c -c matvect(m,x,n,y,p1,p2,p3,p4), -c -c where m is the length of x, -c x is the vector to which the transpose -c of the matrix is to be applied, -c n is the length of y, -c y is the product of the transposed matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvect -c p2 -- parameter to be passed to routine matvect -c p3 -- parameter to be passed to routine matvect -c p4 -- parameter to be passed to routine matvect -c -c output: -c krank -- numerical rank -c list -- indices of the columns in the ID -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns -c in the original matrix being ID'd; -c the present routine uses proj as a work array, too, so -c proj must be at least m+1 + 2*n*(krank+1) real*8 -c elements long, where krank is the rank output -c by the present routine -c ier -- 0 when the routine terminates successfully; -c -1000 when lproj is too small -c -c _N.B._: The algorithm used by this routine is randomized. -c proj must be at least m+1 + 2*n*(krank+1) real*8 -c elements long, where krank is the rank output -c by the present routine. -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,n,list(n),krank,lw,iwork,lwork,ira,kranki,lproj, - 1 lra,ier,k - real*8 eps,p1,p2,p3,p4,proj(*) - external matvect -c -c - ier = 0 -c -c -c Allocate memory in proj. -c - lw = 0 -c - iwork = lw+1 - lwork = m+2*n+1 - lw = lw+lwork -c - ira = lw+1 -c -c -c Find the rank of a. -c - lra = lproj-lwork - call idd_findrank(lra,eps,m,n,matvect,p1,p2,p3,p4, - 1 kranki,proj(ira),ier,proj(iwork)) - if(ier .ne. 0) return -c -c - if(lproj .lt. lwork+2*kranki*n) then - ier = -1000 - return - endif -c -c -c Transpose ra. -c - call idd_rtransposer(n,kranki,proj(ira),proj(ira+kranki*n)) -c -c -c Move the tranposed matrix to the beginning of proj. -c - do k = 1,kranki*n - proj(k) = proj(ira+kranki*n+k-1) - enddo ! k -c -c -c ID the transposed matrix. -c - call iddp_id(eps,kranki,n,proj,krank,list,proj(1+kranki*n)) -c -c - return - end -c -c -c -c - subroutine idd_findrank(lra,eps,m,n,matvect,p1,p2,p3,p4, - 1 krank,ra,ier,w) -c -c estimates the numerical rank krank of a matrix a to precision -c eps, where the routine matvect applies the transpose of a -c to an arbitrary vector. This routine applies the transpose of a -c to krank random vectors, and returns the resulting vectors -c as the columns of ra. -c -c input: -c lra -- maximum usable length (in real*8 elements) of array ra -c eps -- precision defining the numerical rank -c m -- first dimension of a -c n -- second dimension of a -c matvect -- routine which applies the transpose -c of the matrix whose rank is to be estimated -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvect(m,x,n,y,p1,p2,p3,p4), -c -c where m is the length of x, -c x is the vector to which the transpose -c of the matrix is to be applied, -c n is the length of y, -c y is the product of the transposed matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvect -c p2 -- parameter to be passed to routine matvect -c p3 -- parameter to be passed to routine matvect -c p4 -- parameter to be passed to routine matvect -c -c output: -c krank -- estimate of the numerical rank of a -c ra -- product of the transpose of a and a matrix whose entries -c are pseudorandom realizations of i.i.d. random numbers, -c uniformly distributed on [0,1]; -c ra must be at least 2*n*krank real*8 elements long -c ier -- 0 when the routine terminates successfully; -c -1000 when lra is too small -c -c work: -c w -- must be at least m+2*n+1 real*8 elements long -c -c _N.B._: ra must be at least 2*n*krank real*8 elements long. -c Also, the algorithm used by this routine is randomized. -c - implicit none - integer m,n,lw,krank,ix,lx,iy,ly,iscal,lscal,lra,ier - real*8 eps,p1,p2,p3,p4,ra(n,*),w(m+2*n+1) - external matvect -c -c - lw = 0 -c - ix = lw+1 - lx = m - lw = lw+lx -c - iy = lw+1 - ly = n - lw = lw+ly -c - iscal = lw+1 - lscal = n+1 - lw = lw+lscal -c -c - call idd_findrank0(lra,eps,m,n,matvect,p1,p2,p3,p4, - 1 krank,ra,ier,w(ix),w(iy),w(iscal)) -c -c - return - end -c -c -c -c - subroutine idd_findrank0(lra,eps,m,n,matvect,p1,p2,p3,p4, - 1 krank,ra,ier,x,y,scal) -c -c routine idd_findrank serves as a memory wrapper -c for the present routine. (Please see routine idd_findrank -c for further documentation.) -c - implicit none - integer m,n,krank,ifrescal,k,lra,ier - real*8 x(m),ra(n,2,*),p1,p2,p3,p4,scal(n+1),y(n),eps,residual, - 1 enorm - external matvect -c -c - ier = 0 -c -c - krank = 0 -c -c -c Loop until the relative residual is greater than eps, -c or krank = m or krank = n. -c - 1000 continue -c -c - if(lra .lt. n*2*(krank+1)) then - ier = -1000 - return - endif -c -c -c Apply the transpose of a to a random vector. -c - call id_srand(m,x) - call matvect(m,x,n,ra(1,1,krank+1),p1,p2,p3,p4) -c - do k = 1,n - y(k) = ra(k,1,krank+1) - enddo ! k -c -c - if(krank .eq. 0) then -c -c Compute the Euclidean norm of y. -c - enorm = 0 -c - do k = 1,n - enorm = enorm + y(k)**2 - enddo ! k -c - enorm = sqrt(enorm) -c - endif ! krank .eq. 0 -c -c - if(krank .gt. 0) then -c -c Apply the previous Householder transformations to y. -c - ifrescal = 0 -c - do k = 1,krank - call idd_houseapp(n-k+1,ra(1,2,k),y(k), - 1 ifrescal,scal(k),y(k)) - enddo ! k -c - endif ! krank .gt. 0 -c -c -c Compute the Householder vector associated with y. -c - call idd_house(n-krank,y(krank+1), - 1 residual,ra(1,2,krank+1),scal(krank+1)) - residual = abs(residual) -c -c - krank = krank+1 -c -c - if(residual .gt. eps*enorm - 1 .and. krank .lt. m .and. krank .lt. n) - 2 goto 1000 -c -c -c Delete the Householder vectors from the array ra. -c - call idd_crunch(n,krank,ra) -c -c - return - end -c -c -c -c - subroutine idd_crunch(n,l,a) -c -c removes every other block of n entries from a vector. -c -c input: -c n -- length of each block to remove -c l -- half of the total number of blocks -c a -- original array -c -c output: -c a -- array with every other block of n entries removed -c - implicit none - integer j,k,n,l - real*8 a(n,2*l) -c -c - do j = 2,l - do k = 1,n -c - a(k,j) = a(k,2*j-1) -c - enddo ! k - enddo ! j -c -c - return - end -c -c -c -c - subroutine idd_rtransposer(m,n,a,at) -c -c transposes a to obtain at. -c -c input: -c m -- first dimension of a, and second dimension of at -c n -- second dimension of a, and first dimension of at -c a -- matrix to be transposed -c -c output: -c at -- transpose of a -c - implicit none - integer m,n,j,k - real*8 a(m,n),at(n,m) -c -c - do k = 1,n - do j = 1,m -c - at(k,j) = a(j,k) -c - enddo ! j - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/iddp_rsvd.f b/scipy/linalg/src/id_dist/src/iddp_rsvd.f deleted file mode 100644 index 8af9ba04ccea..000000000000 --- a/scipy/linalg/src/id_dist/src/iddp_rsvd.f +++ /dev/null @@ -1,216 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine iddp_rsvd computes the SVD, to a specified precision, -c of a matrix specified by routines for applying the matrix -c and its transpose to arbitrary vectors. -c This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine iddp_rsvd(lw,eps,m,n,matvect,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,iu,iv,is,w,ier) -c -c constructs a rank-krank SVD U Sigma V^T approximating a -c to precision eps, where matvect is a routine which applies a^T -c to an arbitrary vector, and matvec is a routine -c which applies a to an arbitrary vector; U is an m x krank -c matrix whose columns are orthonormal, V is an n x krank -c matrix whose columns are orthonormal, and Sigma is a diagonal -c krank x krank matrix whose entries are all nonnegative. -c The entries of U are stored in w, starting at w(iu); -c the entries of V are stored in w, starting at w(iv). -c The diagonal entries of Sigma are stored in w, -c starting at w(is). This routine uses a randomized algorithm. -c -c input: -c lw -- maximum usable length (in real*8 elements) -c of the array w -c eps -- precision of the desired approximation -c m -- number of rows in a -c n -- number of columns in a -c matvect -- routine which applies the transpose -c of the matrix to be SVD'd -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvect(m,x,n,y,p1t,p2t,p3t,p4t), -c -c where m is the length of x, -c x is the vector to which the transpose -c of the matrix is to be applied, -c n is the length of y, -c y is the product of the transposed matrix and x, -c and p1t, p2t, p3t, and p4t are user-specified -c parameters -c p1t -- parameter to be passed to routine matvect -c p2t -- parameter to be passed to routine matvect -c p3t -- parameter to be passed to routine matvect -c p4t -- parameter to be passed to routine matvect -c matvec -- routine which applies the matrix to be SVD'd -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvec(n,x,m,y,p1,p2,p3,p4), -c -c where n is the length of x, -c x is the vector to which the matrix is to be applied, -c m is the length of y, -c y is the product of the matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvec -c p2 -- parameter to be passed to routine matvec -c p3 -- parameter to be passed to routine matvec -c p4 -- parameter to be passed to routine matvec -c -c output: -c krank -- rank of the SVD constructed -c iu -- index in w of the first entry of the matrix -c of orthonormal left singular vectors of a -c iv -- index in w of the first entry of the matrix -c of orthonormal right singular vectors of a -c is -- index in w of the first entry of the array -c of singular values of a -c w -- array containing the singular values and singular vectors -c of a; w doubles as a work array, and so must be at least -c (krank+1)*(3*m+5*n+1)+25*krank**2 real*8 elements long, -c where krank is the rank returned by the present routine -c ier -- 0 when the routine terminates successfully; -c -1000 when lw is too small; -c other nonzero values when idd_id2svd bombs -c -c _N.B._: w must be at least (krank+1)*(3*m+5*n+1)+25*krank**2 -c real*8 elements long, where krank is the rank -c returned by the present routine. Also, the algorithm -c used by the present routine is randomized. -c - implicit none - integer m,n,krank,lw,lw2,ilist,llist,iproj,icol,lcol,lp, - 1 iwork,lwork,ier,lproj,iu,iv,is,lu,lv,ls,iui,ivi,isi,k - real*8 eps,p1t,p2t,p3t,p4t,p1,p2,p3,p4,w(*) - external matvect,matvec -c -c -c Allocate some memory. -c - lw2 = 0 -c - ilist = lw2+1 - llist = n - lw2 = lw2+llist -c - iproj = lw2+1 -c -c -c ID a. -c - lp = lw-lw2 - call iddp_rid(lp,eps,m,n,matvect,p1t,p2t,p3t,p4t,krank, - 1 w(ilist),w(iproj),ier) - if(ier .ne. 0) return -c -c - if(krank .gt. 0) then -c -c -c Allocate more memory. -c - lproj = krank*(n-krank) - lw2 = lw2+lproj -c - icol = lw2+1 - lcol = m*krank - lw2 = lw2+lcol -c - iui = lw2+1 - lu = m*krank - lw2 = lw2+lu -c - ivi = lw2+1 - lv = n*krank - lw2 = lw2+lv -c - isi = lw2+1 - ls = krank - lw2 = lw2+ls -c - iwork = lw2+1 - lwork = (krank+1)*(m+3*n)+26*krank**2 - lw2 = lw2+lwork -c -c - if(lw .lt. lw2) then - ier = -1000 - return - endif -c -c - call iddp_rsvd0(m,n,matvect,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,w(iui),w(ivi), - 2 w(isi),ier,w(ilist),w(iproj),w(icol), - 3 w(iwork)) - if(ier .ne. 0) return -c -c - iu = 1 - iv = iu+lu - is = iv+lv -c -c -c Copy the singular values and singular vectors -c into their proper locations. -c - do k = 1,lu - w(iu+k-1) = w(iui+k-1) - enddo ! k -c - do k = 1,lv - w(iv+k-1) = w(ivi+k-1) - enddo ! k -c - do k = 1,ls - w(is+k-1) = w(isi+k-1) - enddo ! k -c -c - endif ! krank .gt. 0 -c -c - return - end -c -c -c -c - subroutine iddp_rsvd0(m,n,matvect,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,u,v,s,ier, - 2 list,proj,col,work) -c -c routine iddp_rsvd serves as a memory wrapper -c for the present routine (please see routine iddp_rsvd -c for further documentation). -c - implicit none - integer m,n,krank,list(n),ier - real*8 p1t,p2t,p3t,p4t,p1,p2,p3,p4,u(m,krank),v(n,krank), - 1 s(krank),proj(krank,n-krank),col(m*krank), - 2 work((krank+1)*(m+3*n)+26*krank**2) - external matvect,matvec -c -c -c Collect together the columns of a indexed by list into col. -c - call idd_getcols(m,n,matvec,p1,p2,p3,p4,krank,list,col,work) -c -c -c Convert the ID to an SVD. -c - call idd_id2svd(m,krank,col,n,list,proj,u,v,s,ier,work) -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/iddr_aid.f b/scipy/linalg/src/id_dist/src/iddr_aid.f deleted file mode 100644 index 2dc811148a7b..000000000000 --- a/scipy/linalg/src/id_dist/src/iddr_aid.f +++ /dev/null @@ -1,208 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine iddr_aid computes the ID, to a specified rank, -c of an arbitrary matrix. This routine is randomized. -c -c routine iddr_aidi initializes routine iddr_aid. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine iddr_aid(m,n,a,krank,w,list,proj) -c -c computes the ID of the matrix a, i.e., lists in list -c the indices of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c min(m,n,krank) -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank)(*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon, dimensioned epsilon(m,n-krank), -c whose norm is (hopefully) minimized by the pivoting procedure. -c -c input: -c m -- number of rows in a -c n -- number of columns in a -c a -- matrix to be ID'd; the present routine does not alter a -c krank -- rank of the ID to be constructed -c w -- initialization array that routine iddr_aidi -c has constructed -c -c output: -c list -- indices of the columns in the ID -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns -c in the original matrix being ID'd -c -c _N.B._: The algorithm used by this routine is randomized. -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,n,krank,list(n),lw,ir,lr,lw2,iw - real*8 a(m,n),proj(krank*(n-krank)),w((2*krank+17)*n+27*m+100) -c -c -c Allocate memory in w. -c - lw = 0 -c - iw = lw+1 - lw2 = 27*m+100+n - lw = lw+lw2 -c - ir = lw+1 - lr = (krank+8)*2*n - lw = lw+lr -c -c - call iddr_aid0(m,n,a,krank,w(iw),list,proj,w(ir)) -c -c - return - end -c -c -c -c - subroutine iddr_aid0(m,n,a,krank,w,list,proj,r) -c -c routine iddr_aid serves as a memory wrapper -c for the present routine -c (see iddr_aid for further documentation). -c - implicit none - integer k,l,m,n2,n,krank,list(n),mn,lproj - real*8 a(m,n),r(krank+8,2*n),proj(krank,n-krank), - 1 w(27*m+100+n) -c -c Please note that the second dimension of r is 2*n -c (instead of n) so that if krank+8 >= m/2, then -c we can copy the whole of a into r. -c -c -c Retrieve the number of random test vectors -c and the greatest integer less than m that is -c a positive integer power of two. -c - l = w(1) - n2 = w(2) -c -c - if(l .lt. n2 .and. l .le. m) then -c -c Apply the random matrix. -c - do k = 1,n - call idd_sfrm(l,m,n2,w(11),a(1,k),r(1,k)) - enddo ! k -c -c ID r. -c - call iddr_id(l,n,r,krank,list,w(26*m+101)) -c -c Retrieve proj from r. -c - lproj = krank*(n-krank) - call iddr_copydarr(lproj,r,proj) -c - endif -c -c - if(l .ge. n2 .or. l .gt. m) then -c -c ID a directly. -c - mn = m*n - call iddr_copydarr(mn,a,r) - call iddr_id(m,n,r,krank,list,w(26*m+101)) -c -c Retrieve proj from r. -c - lproj = krank*(n-krank) - call iddr_copydarr(lproj,r,proj) -c - endif -c -c - return - end -c -c -c -c - subroutine iddr_copydarr(n,a,b) -c -c copies a into b. -c -c input: -c n -- length of a and b -c a -- array to copy into b -c -c output: -c b -- copy of a -c - implicit none - integer n,k - real*8 a(n),b(n) -c -c - do k = 1,n - b(k) = a(k) - enddo ! k -c -c - return - end -c -c -c -c - subroutine iddr_aidi(m,n,krank,w) -c -c initializes the array w for using routine iddr_aid. -c -c input: -c m -- number of rows in the matrix to be ID'd -c n -- number of columns in the matrix to be ID'd -c krank -- rank of the ID to be constructed -c -c output: -c w -- initialization array for using routine iddr_aid -c - implicit none - integer m,n,krank,l,n2 - real*8 w((2*krank+17)*n+27*m+100) -c -c -c Set the number of random test vectors to 8 more than the rank. -c - l = krank+8 - w(1) = l -c -c -c Initialize the rest of the array w. -c - n2 = 0 - if(l .le. m) call idd_sfrmi(l,m,n2,w(11)) - w(2) = n2 -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/iddr_asvd.f b/scipy/linalg/src/id_dist/src/iddr_asvd.f deleted file mode 100644 index 9641f0cd63d5..000000000000 --- a/scipy/linalg/src/id_dist/src/iddr_asvd.f +++ /dev/null @@ -1,114 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine iddr_aid computes the SVD, to a specified rank, -c of an arbitrary matrix. This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine iddr_asvd(m,n,a,krank,w,u,v,s,ier) -c -c constructs a rank-krank SVD u diag(s) v^T approximating a, -c where u is an m x krank matrix whose columns are orthonormal, -c v is an n x krank matrix whose columns are orthonormal, -c and diag(s) is a diagonal krank x krank matrix whose entries -c are all nonnegative. This routine uses a randomized algorithm. -c -c input: -c m -- number of rows in a -c n -- number of columns in a -c a -- matrix to be decomposed; the present routine does not -c alter a -c krank -- rank of the SVD being constructed -c w -- initialization array that routine iddr_aidi -c has constructed (for use in the present routine, w must -c be at least (2*krank+28)*m+(6*krank+21)*n+25*krank**2+100 -c real*8 elements long) -c -c output: -c u -- matrix of orthonormal left singular vectors of a -c v -- matrix of orthonormal right singular vectors of a -c s -- array of singular values of a -c ier -- 0 when the routine terminates successfully; -c nonzero otherwise -c -c _N.B._: The algorithm used by this routine is randomized. -c - implicit none - integer m,n,krank,lw,ilist,llist,iproj,lproj,icol,lcol, - 1 iwork,lwork,iwinit,lwinit,ier - real*8 a(m,n),u(m,krank),v(n,krank),s(krank), - 1 w((2*krank+28)*m+(6*krank+21)*n+25*krank**2+100) -c -c -c Allocate memory in w. -c - lw = 0 -c - iwinit = lw+1 - lwinit = (2*krank+17)*n+27*m+100 - lw = lw+lwinit -c - ilist = lw+1 - llist = n - lw = lw+llist -c - iproj = lw+1 - lproj = krank*(n-krank) - lw = lw+lproj -c - icol = lw+1 - lcol = m*krank - lw = lw+lcol -c - iwork = lw+1 - lwork = (krank+1)*(m+3*n)+26*krank**2 - lw = lw+lwork -c -c - call iddr_asvd0(m,n,a,krank,w(iwinit),u,v,s,ier, - 1 w(ilist),w(iproj),w(icol),w(iwork)) -c -c - return - end -c -c -c -c - subroutine iddr_asvd0(m,n,a,krank,winit,u,v,s,ier, - 1 list,proj,col,work) -c -c routine iddr_asvd serves as a memory wrapper -c for the present routine (please see routine iddr_asvd -c for further documentation). -c - implicit none - integer m,n,krank,list(n),ier - real*8 a(m,n),u(m,krank),v(n,krank),s(krank), - 1 proj(krank,n-krank),col(m*krank), - 2 winit((2*krank+17)*n+27*m+100), - 3 work((krank+1)*(m+3*n)+26*krank**2) -c -c -c ID a. -c - call iddr_aid(m,n,a,krank,winit,list,proj) -c -c -c Collect together the columns of a indexed by list into col. -c - call idd_copycols(m,n,a,krank,list,col) -c -c -c Convert the ID to an SVD. -c - call idd_id2svd(m,krank,col,n,list,proj,u,v,s,ier,work) -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/iddr_rid.f b/scipy/linalg/src/id_dist/src/iddr_rid.f deleted file mode 100644 index eb96c145ab52..000000000000 --- a/scipy/linalg/src/id_dist/src/iddr_rid.f +++ /dev/null @@ -1,155 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine iddr_rid computes the ID, to a specified rank, -c of a matrix specified by a routine for applying its transpose -c to arbitrary vectors. This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine iddr_rid(m,n,matvect,p1,p2,p3,p4,krank,list,proj) -c -c computes the ID of a matrix "a" specified by -c the routine matvect -- matvect must apply the transpose -c of the matrix being ID'd to an arbitrary vector -- -c i.e., the present routine lists in list the indices -c of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c min(m,n,krank) -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank)(*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon, dimensioned epsilon(m,n-krank), -c whose norm is (hopefully) minimized by the pivoting procedure. -c -c input: -c m -- number of rows in the matrix to be ID'd -c n -- number of columns in the matrix to be ID'd -c matvect -- routine which applies the transpose -c of the matrix to be ID'd to an arbitrary vector; -c this routine must have a calling sequence -c of the form -c -c matvect(m,x,n,y,p1,p2,p3,p4), -c -c where m is the length of x, -c x is the vector to which the transpose -c of the matrix is to be applied, -c n is the length of y, -c y is the product of the transposed matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvect -c p2 -- parameter to be passed to routine matvect -c p3 -- parameter to be passed to routine matvect -c p4 -- parameter to be passed to routine matvect -c krank -- rank of the ID to be constructed -c -c output: -c list -- indices of the columns in the ID -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns -c in the original matrix being ID'd; -c proj doubles as a work array in the present routine, so -c proj must be at least m+(krank+3)*n real*8 elements -c long -c -c _N.B._: The algorithm used by this routine is randomized. -c proj must be at least m+(krank+3)*n real*8 elements -c long. -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,n,krank,list(n),lw,ix,lx,iy,ly,ir,lr - real*8 p1,p2,p3,p4,proj(m+(krank+3)*n) - external matvect -c -c -c Allocate memory in w. -c - lw = 0 -c - ir = lw+1 - lr = (krank+2)*n - lw = lw+lr -c - ix = lw+1 - lx = m - lw = lw+lx -c - iy = lw+1 - ly = n - lw = lw+ly -c -c - call iddr_ridall0(m,n,matvect,p1,p2,p3,p4,krank, - 1 list,proj(ir),proj(ix),proj(iy)) -c -c - return - end -c -c -c -c - subroutine iddr_ridall0(m,n,matvect,p1,p2,p3,p4,krank, - 1 list,r,x,y) -c -c routine iddr_ridall serves as a memory wrapper -c for the present routine -c (see iddr_ridall for further documentation). -c - implicit none - integer j,k,l,m,n,krank,list(n) - real*8 x(m),y(n),p1,p2,p3,p4,r(krank+2,n) - external matvect -c -c -c Set the number of random test vectors to 2 more than the rank. -c - l = krank+2 -c -c Apply the transpose of the original matrix to l random vectors. -c - do j = 1,l -c -c Generate a random vector. -c - call id_srand(m,x) -c -c Apply the transpose of the matrix to x, obtaining y. -c - call matvect(m,x,n,y,p1,p2,p3,p4) -c -c Copy y into row j of r. -c - do k = 1,n - r(j,k) = y(k) - enddo ! k -c - enddo ! j -c -c -c ID r. -c - call iddr_id(l,n,r,krank,list,y) -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/iddr_rsvd.f b/scipy/linalg/src/id_dist/src/iddr_rsvd.f deleted file mode 100644 index 000ce8693bb6..000000000000 --- a/scipy/linalg/src/id_dist/src/iddr_rsvd.f +++ /dev/null @@ -1,157 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine iddr_rsvd computes the SVD, to a specified rank, -c of a matrix specified by routines for applying the matrix -c and its transpose to arbitrary vectors. -c This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine iddr_rsvd(m,n,matvect,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,u,v,s,ier,w) -c -c constructs a rank-krank SVD u diag(s) v^T approximating a, -c where matvect is a routine which applies a^T -c to an arbitrary vector, and matvec is a routine -c which applies a to an arbitrary vector; -c u is an m x krank matrix whose columns are orthonormal, -c v is an n x krank matrix whose columns are orthonormal, -c and diag(s) is a diagonal krank x krank matrix whose entries -c are all nonnegative. This routine uses a randomized algorithm. -c -c input: -c m -- number of rows in a -c n -- number of columns in a -c matvect -- routine which applies the transpose -c of the matrix to be SVD'd -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvect(m,x,n,y,p1t,p2t,p3t,p4t), -c -c where m is the length of x, -c x is the vector to which the transpose -c of the matrix is to be applied, -c n is the length of y, -c y is the product of the transposed matrix and x, -c and p1t, p2t, p3t, and p4t are user-specified -c parameters -c p1t -- parameter to be passed to routine matvect -c p2t -- parameter to be passed to routine matvect -c p3t -- parameter to be passed to routine matvect -c p4t -- parameter to be passed to routine matvect -c matvec -- routine which applies the matrix to be SVD'd -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvec(n,x,m,y,p1,p2,p3,p4), -c -c where n is the length of x, -c x is the vector to which the matrix is to be applied, -c m is the length of y, -c y is the product of the matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvec -c p2 -- parameter to be passed to routine matvec -c p3 -- parameter to be passed to routine matvec -c p4 -- parameter to be passed to routine matvec -c krank -- rank of the SVD being constructed -c -c output: -c u -- matrix of orthonormal left singular vectors of a -c v -- matrix of orthonormal right singular vectors of a -c s -- array of singular values of a -c ier -- 0 when the routine terminates successfully; -c nonzero otherwise -c -c work: -c w -- must be at least (krank+1)*(2*m+4*n)+25*krank**2 -c real*8 elements long -c -c _N.B._: The algorithm used by this routine is randomized. -c - implicit none - integer m,n,krank,lw,ilist,llist,iproj,lproj,icol,lcol, - 1 iwork,lwork,ier - real*8 p1t,p2t,p3t,p4t,p1,p2,p3,p4,u(m,krank),v(n,krank), - 1 s(krank),w((krank+1)*(2*m+4*n)+25*krank**2) - external matvect,matvec -c -c -c Allocate memory in w. -c - lw = 0 -c - ilist = lw+1 - llist = n - lw = lw+llist -c - iproj = lw+1 - lproj = krank*(n-krank) - lw = lw+lproj -c - icol = lw+1 - lcol = m*krank - lw = lw+lcol -c - iwork = lw+1 - lwork = (krank+1)*(m+3*n)+26*krank**2 - lw = lw+lwork -c -c - call iddr_rsvd0(m,n,matvect,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,u,v,s,ier, - 2 w(ilist),w(iproj),w(icol),w(iwork)) -c -c - return - end -c -c -c -c - subroutine iddr_rsvd0(m,n,matvect,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,u,v,s,ier, - 2 list,proj,col,work) -c -c routine iddr_rsvd serves as a memory wrapper -c for the present routine (please see routine iddr_rsvd -c for further documentation). -c - implicit none - integer m,n,krank,list(n),ier,k - real*8 p1t,p2t,p3t,p4t,p1,p2,p3,p4,u(m,krank),v(n,krank), - 1 s(krank),proj(krank*(n-krank)),col(m*krank), - 2 work((krank+1)*(m+3*n)+26*krank**2) - external matvect,matvec -c -c -c ID a. -c - call iddr_rid(m,n,matvect,p1t,p2t,p3t,p4t,krank,list,work) -c -c -c Retrieve proj from work. -c - do k = 1,krank*(n-krank) - proj(k) = work(k) - enddo ! k -c -c -c Collect together the columns of a indexed by list into col. -c - call idd_getcols(m,n,matvec,p1,p2,p3,p4,krank,list,col,work) -c -c -c Convert the ID to an SVD. -c - call idd_id2svd(m,krank,col,n,list,proj,u,v,s,ier,work) -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idz_frm.f b/scipy/linalg/src/id_dist/src/idz_frm.f deleted file mode 100644 index 93c4d8ec71ce..000000000000 --- a/scipy/linalg/src/id_dist/src/idz_frm.f +++ /dev/null @@ -1,419 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idz_frm transforms a vector via a composition -c of Rokhlin's random transform, random subselection, and an FFT. -c -c routine idz_sfrm transforms a vector into a vector -c of specified length via a composition -c of Rokhlin's random transform, random subselection, and an FFT. -c -c routine idz_frmi initializes routine idz_frm. -c -c routine idz_sfrmi initializes routine idz_sfrm. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idz_frm(m,n,w,x,y) -c -c transforms x into y via a composition -c of Rokhlin's random transform, random subselection, and an FFT. -c In contrast to routine idz_sfrm, the present routine works best -c when the length of the transformed vector is the integer n -c output by routine idz_frmi, or when the length -c is not specified, but instead determined a posteriori -c using the output of the present routine. The transformed vector -c output by the present routine is randomly permuted. -c -c input: -c m -- length of x -c n -- greatest integer expressible as a positive integer power -c of 2 that is less than or equal to m, as obtained -c from the routine idz_frmi; n is the length of y -c w -- initialization array constructed by routine idz_frmi -c x -- vector to be transformed -c -c output: -c y -- transform of x -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,iw,n,k - complex*16 w(17*m+70),x(m),y(n) -c -c -c Apply Rokhlin's random transformation to x, obtaining -c w(16*m+71 : 17*m+70). -c - iw = w(3+m+n) - call idz_random_transf(x,w(16*m+70+1),w(iw)) -c -c -c Subselect from w(16*m+71 : 17*m+70) to obtain y. -c - call idz_subselect(n,w(3),m,w(16*m+70+1),y) -c -c -c Copy y into w(16*m+71 : 16*m+n+70). -c - do k = 1,n - w(16*m+70+k) = y(k) - enddo ! k -c -c -c Fourier transform w(16*m+71 : 16*m+n+70). -c - call zfftf(n,w(16*m+70+1),w(4+m+n)) -c -c -c Permute w(16*m+71 : 16*m+n+70) to obtain y. -c - call idz_permute(n,w(3+m),w(16*m+70+1),y) -c -c - return - end -c -c -c -c - subroutine idz_sfrm(l,m,n,w,x,y) -c -c transforms x into y via a composition -c of Rokhlin's random transform, random subselection, and an FFT. -c In contrast to routine idz_frm, the present routine works best -c when the length l of the transformed vector is known a priori. -c -c input: -c l -- length of y; l must be less than or equal to n -c m -- length of x -c n -- greatest integer expressible as a positive integer power -c of 2 that is less than or equal to m, as obtained -c from the routine idz_frmi -c w -- initialization array constructed by routine idz_sfrmi -c x -- vector to be transformed -c -c output: -c y -- transform of x -c -c _N.B._: l must be less than or equal to n. -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,iw,n,l - complex*16 w(21*m+70),x(m),y(l) -c -c -c Apply Rokhlin's random transformation to x, obtaining -c w(19*m+71 : 20*m+70). -c - iw = w(4+m+l) - call idz_random_transf(x,w(19*m+70+1),w(iw)) -c -c -c Subselect from w(19*m+71 : 20*m+70) to obtain -c w(20*m+71 : 20*m+n+70). -c - call idz_subselect(n,w(4),m,w(19*m+70+1),w(20*m+70+1)) -c -c -c Fourier transform w(20*m+71 : 20*m+n+70). -c - call idz_sfft(l,w(4+m),n,w(5+m+l),w(20*m+70+1)) -c -c -c Copy the desired entries from w(20*m+71 : 20*m+n+70) -c to y. -c - call idz_subselect(l,w(4+m),n,w(20*m+70+1),y) -c -c - return - end -c -c -c -c - subroutine idz_permute(n,ind,x,y) -c -c copy the entries of x into y, rearranged according -c to the permutation specified by ind. -c -c input: -c n -- length of ind, x, and y -c ind -- permutation of n objects -c x -- vector to be permuted -c -c output: -c y -- permutation of x -c - implicit none - integer n,ind(n),k - complex*16 x(n),y(n) -c -c - do k = 1,n - y(k) = x(ind(k)) - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_subselect(n,ind,m,x,y) -c -c copies into y the entries of x indicated by ind. -c -c input: -c n -- number of entries of x to copy into y -c ind -- indices of the entries in x to copy into y -c m -- length of x -c x -- vector whose entries are to be copied -c -c output: -c y -- collection of entries of x specified by ind -c - implicit none - integer n,ind(n),m,k - complex*16 x(m),y(n) -c -c - do k = 1,n - y(k) = x(ind(k)) - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_frmi(m,n,w) -c -c initializes data for the routine idz_frm. -c -c input: -c m -- length of the vector to be transformed -c -c output: -c n -- greatest integer expressible as a positive integer power -c of 2 that is less than or equal to m -c w -- initialization array to be used by routine idz_frm -c -c -c glossary for the fully initialized w: -c -c w(1) = m -c w(2) = n -c w(3:2+m) stores a permutation of m objects -c w(3+m:2+m+n) stores a permutation of n objects -c w(3+m+n) = address in w of the initialization array -c for idz_random_transf -c w(4+m+n:int(w(3+m+n))-1) stores the initialization array -c for zfft -c w(int(w(3+m+n)):16*m+70) stores the initialization array -c for idz_random_transf -c -c -c _N.B._: n is an output of the present routine; -c this routine changes n. -c -c - implicit none - integer m,n,l,nsteps,keep,lw,ia - complex*16 w(17*m+70) -c -c -c Find the greatest integer less than or equal to m -c which is a power of two. -c - call idz_poweroftwo(m,l,n) -c -c -c Store m and n in w. -c - w(1) = m - w(2) = n -c -c -c Store random permutations of m and n objects in w. -c - call id_randperm(m,w(3)) - call id_randperm(n,w(3+m)) -c -c -c Store the address within w of the idz_random_transf_init -c initialization data. -c - ia = 4+m+n+2*n+15 - w(3+m+n) = ia -c -c -c Store the initialization data for zfft in w. -c - call zffti(n,w(4+m+n)) -c -c -c Store the initialization data for idz_random_transf_init in w. -c - nsteps = 3 - call idz_random_transf_init(nsteps,m,w(ia),keep) -c -c -c Calculate the total number of elements used in w. -c - lw = 3+m+n+2*n+15 + 3*nsteps*m+2*m+m/4+50 -c - if(16*m+70 .lt. lw) then - call prinf('lw = *',lw,1) - call prinf('16m+70 = *',16*m+70,1) - stop - endif -c -c - return - end -c -c -c -c - subroutine idz_sfrmi(l,m,n,w) -c -c initializes data for the routine idz_sfrm. -c -c input: -c l -- length of the transformed (output) vector -c m -- length of the vector to be transformed -c -c output: -c n -- greatest integer expressible as a positive integer power -c of 2 that is less than or equal to m -c w -- initialization array to be used by routine idz_sfrm -c -c -c glossary for the fully initialized w: -c -c w(1) = m -c w(2) = n -c w(3) is unused -c w(4:3+m) stores a permutation of m objects -c w(4+m:3+m+l) stores the indices of the l outputs which idz_sfft -c calculates -c w(4+m+l) = address in w of the initialization array -c for idz_random_transf -c w(5+m+l:int(w(4+m+l))-1) stores the initialization array -c for idz_sfft -c w(int(w(4+m+l)):19*m+70) stores the initialization array -c for idz_random_transf -c -c -c _N.B._: n is an output of the present routine; -c this routine changes n. -c -c - implicit none - integer l,m,n,idummy,nsteps,keep,lw,ia - complex*16 w(21*m+70) -c -c -c Find the greatest integer less than or equal to m -c which is a power of two. -c - call idz_poweroftwo(m,idummy,n) -c -c -c Store m and n in w. -c - w(1) = m - w(2) = n - w(3) = 0 -c -c -c Store random permutations of m and n objects in w. -c - call id_randperm(m,w(4)) - call id_randperm(n,w(4+m)) -c -c -c Store the address within w of the idz_random_transf_init -c initialization data. -c - ia = 5+m+l+2*l+15+3*n - w(4+m+l) = ia -c -c -c Store the initialization data for idz_sfft in w. -c - call idz_sffti(l,w(4+m),n,w(5+m+l)) -c -c -c Store the initialization data for idz_random_transf_init in w. -c - nsteps = 3 - call idz_random_transf_init(nsteps,m,w(ia),keep) -c -c -c Calculate the total number of elements used in w. -c - lw = 4+m+l+2*l+15+3*n + 3*nsteps*m+2*m+m/4+50 -c - if(19*m+70 .lt. lw) then - call prinf('lw = *',lw,1) - call prinf('19m+70 = *',19*m+70,1) - stop - endif -c -c - return - end -c -c -c -c - subroutine idz_poweroftwo(m,l,n) -c -c computes l = floor(log_2(m)) and n = 2**l. -c -c input: -c m -- integer whose log_2 is to be taken -c -c output: -c l -- floor(log_2(m)) -c n -- 2**l -c - implicit none - integer l,m,n -c -c - l = 0 - n = 1 -c - 1000 continue - l = l+1 - n = n*2 - if(n .le. m) goto 1000 -c - l = l-1 - n = n/2 -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idz_house.f b/scipy/linalg/src/id_dist/src/idz_house.f deleted file mode 100644 index 93db06e6db9c..000000000000 --- a/scipy/linalg/src/id_dist/src/idz_house.f +++ /dev/null @@ -1,298 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idz_house calculates the vector and scalar -c needed to apply the Householder transformation reflecting -c a given vector into its first component. -c -c routine idz_houseapp applies a Householder matrix to a vector. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idz_houseapp(n,vn,u,ifrescal,scal,v) -c -c applies the Householder matrix -c identity_matrix - scal * vn * adjoint(vn) -c to the vector u, yielding the vector v; -c -c scal = 2/(1 + |vn(2)|^2 + ... + |vn(n)|^2) -c when vn(2), ..., vn(n) don't all vanish; -c -c scal = 0 -c when vn(2), ..., vn(n) do all vanish -c (including when n = 1). -c -c input: -c n -- size of vn, u, and v, though the indexing on vn goes -c from 2 to n -c vn -- components 2 to n of the Householder vector vn; -c vn(1) is assumed to be 1 -c u -- vector to be transformed -c ifrescal -- set to 1 to recompute scal from vn(2), ..., vn(n); -c set to 0 to use scal as input -c scal -- see the entry for ifrescal in the decription -c of the input -c -c output: -c scal -- see the entry for ifrescal in the decription -c of the input -c v -- result of applying the Householder matrix to u; -c it's O.K. to have v be the same as u -c in order to apply the matrix to the vector in place -c -c reference: -c Golub and Van Loan, "Matrix Computations," 3rd edition, -c Johns Hopkins University Press, 1996, Chapter 5. -c - implicit none - save - integer n,k,ifrescal - real*8 scal,sum - complex*16 vn(2:*),u(n),v(n),fact -c -c -c Get out of this routine if n = 1. -c - if(n .eq. 1) then - v(1) = u(1) - return - endif -c -c - if(ifrescal .eq. 1) then -c -c -c Calculate |vn(2)|^2 + ... + |vn(n)|^2. -c - sum = 0 - do k = 2,n - sum = sum+vn(k)*conjg(vn(k)) - enddo ! k -c -c -c Calculate scal. -c - if(sum .eq. 0) scal = 0 - if(sum .ne. 0) scal = 2/(1+sum) -c -c - endif -c -c -c Calculate fact = scal * adjoint(vn) * u. -c - fact = u(1) -c - do k = 2,n - fact = fact+conjg(vn(k))*u(k) - enddo ! k -c - fact = fact*scal -c -c -c Subtract fact*vn from u, yielding v. -c - v(1) = u(1) - fact -c - do k = 2,n - v(k) = u(k) - fact*vn(k) - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_house(n,x,css,vn,scal) -c -c constructs the vector vn with vn(1) = 1, -c and the scalar scal, such that the obviously self-adjoint -c H := identity_matrix - scal * vn * adjoint(vn) is unitary, -c the absolute value of the first entry of Hx -c is the root-sum-square of the entries of x, -c and all other entries of Hx are zero -c (H is the Householder matrix corresponding to x). -c -c input: -c n -- size of x and vn, though the indexing on vn goes -c from 2 to n -c x -- vector to reflect into its first component -c -c output: -c css -- root-sum-square of the entries of x * the phase of x(1) -c vn -- entries 2 to n of the Householder vector vn; -c vn(1) is assumed to be 1 -c scal -- scalar multiplying vn * adjoint(vn); -c -c scal = 2/(1 + |vn(2)|^2 + ... + |vn(n)|^2) -c when vn(2), ..., vn(n) don't all vanish; -c -c scal = 0 -c when vn(2), ..., vn(n) do all vanish -c (including when n = 1) -c -c reference: -c Golub and Van Loan, "Matrix Computations," 3rd edition, -c Johns Hopkins University Press, 1996, Chapter 5. -c - implicit none - save - integer n,k - real*8 scal,test,rss,sum - complex*16 x(n),v1,vn(2:*),x1,phase,css -c -c - x1 = x(1) -c -c -c Get out of this routine if n = 1. -c - if(n .eq. 1) then - css = x1 - scal = 0 - return - endif -c -c -c Calculate |x(2)|^2 + ... |x(n)|^2 -c and the root-sum-square value of the entries in x. -c -c - sum = 0 - do k = 2,n - sum = sum+x(k)*conjg(x(k)) - enddo ! k -c -c -c Get out of this routine if sum = 0; -c flag this case as such by setting v(2), ..., v(n) all to 0. -c - if(sum .eq. 0) then -c - css = x1 - do k = 2,n - vn(k) = 0 - enddo ! k - scal = 0 -c - return -c - endif -c -c - rss = x1*conjg(x1) + sum - rss = sqrt(rss) -c -c -c Determine the first component v1 -c of the unnormalized Householder vector -c v = x - phase(x1) * rss * (1 0 0 ... 0 0)^T. -c - if(x1 .eq. 0) phase = 1 - if(x1 .ne. 0) phase = x1/abs(x1) - test = conjg(phase) * x1 - css = phase*rss -c -c If test <= 0, then form x1-phase*rss directly, -c since that expression cannot involve any cancellation. -c - if(test .le. 0) v1 = x1-phase*rss -c -c If test > 0, then use the fact that -c x1-phase*rss = -phase*sum / ((phase)^* * x1 + rss), -c in order to avoid potential cancellation. -c - if(test .gt. 0) v1 = -phase*sum / (conjg(phase)*x1+rss) -c -c -c Compute the vector vn and the scalar scal such that vn(1) = 1 -c in the Householder transformation -c identity_matrix - scal * vn * adjoint(vn). -c - do k = 2,n - vn(k) = x(k)/v1 - enddo ! k -c -c scal = 2 -c / ( |vn(1)|^2 + |vn(2)|^2 + ... + |vn(n)|^2 ) -c -c = 2 -c / ( 1 + |vn(2)|^2 + ... + |vn(n)|^2 ) -c -c = 2*|v(1)|^2 -c / ( |v(1)|^2 + |v(1)*vn(2)|^2 + ... + |v(1)*vn(n)|^2 ) -c -c = 2*|v(1)|^2 -c / ( |v(1)|^2 + (|v(2)|^2 + ... + |v(n)|^2) ) -c - scal = 2*v1*conjg(v1) / (v1*conjg(v1)+sum) -c -c - rss = phase*rss -c -c - return - end -c -c -c -c - subroutine idz_housemat(n,vn,scal,h) -c -c fills h with the Householder matrix -c identity_matrix - scal * vn * adjoint(vn). -c -c input: -c n -- size of vn and h, though the indexing of vn goes -c from 2 to n -c vn -- entries 2 to n of the vector vn; -c vn(1) is assumed to be 1 -c scal -- scalar multiplying vn * adjoint(vn) -c -c output: -c h -- identity_matrix - scal * vn * adjoint(vn) -c - implicit none - save - integer n,j,k - real*8 scal - complex*16 vn(2:*),h(n,n),factor1,factor2 -c -c -c Fill h with the identity matrix. -c - do j = 1,n - do k = 1,n -c - if(j .eq. k) h(k,j) = 1 - if(j .ne. k) h(k,j) = 0 -c - enddo ! k - enddo ! j -c -c -c Subtract from h the matrix scal*vn*adjoint(vn). -c - do j = 1,n - do k = 1,n -c - if(j .eq. 1) factor1 = 1 - if(j .ne. 1) factor1 = vn(j) -c - if(k .eq. 1) factor2 = 1 - if(k .ne. 1) factor2 = conjg(vn(k)) -c - h(k,j) = h(k,j) - scal*factor1*factor2 -c - enddo ! k - enddo ! j -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idz_id.f b/scipy/linalg/src/id_dist/src/idz_id.f deleted file mode 100644 index 7a80243ff28d..000000000000 --- a/scipy/linalg/src/id_dist/src/idz_id.f +++ /dev/null @@ -1,566 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idzp_id computes the ID of a matrix, -c to a specified precision. -c -c routine idzr_id computes the ID of a matrix, -c to a specified rank. -c -c routine idz_reconid reconstructs a matrix from its ID. -c -c routine idz_copycols collects together selected columns -c of a matrix. -c -c routine idz_getcols collects together selected columns -c of a matrix specified by a routine for applying the matrix -c to arbitrary vectors. -c -c routine idz_reconint constructs p in the ID a = b p, -c where the columns of b are a subset of the columns of a, -c and p is the projection coefficient matrix, -c given list, krank, and proj output by routines idzr_id -c or idzp_id. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idzp_id(eps,m,n,a,krank,list,rnorms) -c -c computes the ID of a, i.e., lists in list the indices -c of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c krank -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank) (*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon dimensioned epsilon(m,n-krank) -c such that the greatest singular value of epsilon -c <= the greatest singular value of a * eps. -c The present routine stores the krank x (n-krank) matrix proj -c in the memory initially occupied by a. -c -c input: -c eps -- relative precision of the resulting ID -c m -- first dimension of a -c n -- second dimension of a, as well as the dimension required -c of list -c a -- matrix to be ID'd -c -c output: -c a -- the first krank*(n-krank) elements of a constitute -c the krank x (n-krank) interpolation matrix proj -c krank -- numerical rank -c list -- list of the indices of the krank columns of a -c through which the other columns of a are expressed; -c also, list describes the permutation of proj -c required to reconstruct a as indicated in (*) above -c rnorms -- absolute values of the entries on the diagonal -c of the triangular matrix used to compute the ID -c (these may be used to check the stability of the ID) -c -c _N.B._: This routine changes a. -c -c reference: -c Cheng, Gimbutas, Martinsson, Rokhlin, "On the compression of -c low-rank matrices," SIAM Journal on Scientific Computing, -c 26 (4): 1389-1404, 2005. -c - implicit none - integer m,n,krank,k,list(n),iswap - real*8 eps,rnorms(n) - complex*16 a(m,n) -c -c -c QR decompose a. -c - call idzp_qrpiv(eps,m,n,a,krank,list,rnorms) -c -c -c Build the list of columns chosen in a -c by multiplying together the permutations in list, -c with the permutation swapping 1 and list(1) taken rightmost -c in the product, that swapping 2 and list(2) taken next -c rightmost, ..., that swapping krank and list(krank) taken -c leftmost. -c - do k = 1,n - rnorms(k) = k - enddo ! k -c - if(krank .gt. 0) then - do k = 1,krank -c -c Swap rnorms(k) and rnorms(list(k)). -c - iswap = rnorms(k) - rnorms(k) = rnorms(list(k)) - rnorms(list(k)) = iswap -c - enddo ! k - endif -c - do k = 1,n - list(k) = rnorms(k) - enddo ! k -c -c -c Fill rnorms for the output. -c - if(krank .gt. 0) then -c - do k = 1,krank - rnorms(k) = a(k,k) - enddo ! k -c - endif -c -c -c Backsolve for proj, storing it at the beginning of a. -c - if(krank .gt. 0) then - call idz_lssolve(m,n,a,krank) - endif -c -c - return - end -c -c -c -c - subroutine idzr_id(m,n,a,krank,list,rnorms) -c -c computes the ID of a, i.e., lists in list the indices -c of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c krank -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank) (*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon, dimensioned epsilon(m,n-krank), -c whose norm is (hopefully) minimized by the pivoting procedure. -c The present routine stores the krank x (n-krank) matrix proj -c in the memory initially occupied by a. -c -c input: -c m -- first dimension of a -c n -- second dimension of a, as well as the dimension required -c of list -c a -- matrix to be ID'd -c krank -- desired rank of the output matrix -c (please note that if krank > m or krank > n, -c then the rank of the output matrix will be -c less than krank) -c -c output: -c a -- the first krank*(n-krank) elements of a constitute -c the krank x (n-krank) interpolation matrix proj -c list -- list of the indices of the krank columns of a -c through which the other columns of a are expressed; -c also, list describes the permutation of proj -c required to reconstruct a as indicated in (*) above -c rnorms -- absolute values of the entries on the diagonal -c of the triangular matrix used to compute the ID -c (these may be used to check the stability of the ID) -c -c _N.B._: This routine changes a. -c -c reference: -c Cheng, Gimbutas, Martinsson, Rokhlin, "On the compression of -c low-rank matrices," SIAM Journal on Scientific Computing, -c 26 (4): 1389-1404, 2005. -c - implicit none - integer m,n,krank,j,k,list(n),iswap - real*8 rnorms(n),ss - complex*16 a(m,n) -c -c -c QR decompose a. -c - call idzr_qrpiv(m,n,a,krank,list,rnorms) -c -c -c Build the list of columns chosen in a -c by multiplying together the permutations in list, -c with the permutation swapping 1 and list(1) taken rightmost -c in the product, that swapping 2 and list(2) taken next -c rightmost, ..., that swapping krank and list(krank) taken -c leftmost. -c - do k = 1,n - rnorms(k) = k - enddo ! k -c - if(krank .gt. 0) then - do k = 1,krank -c -c Swap rnorms(k) and rnorms(list(k)). -c - iswap = rnorms(k) - rnorms(k) = rnorms(list(k)) - rnorms(list(k)) = iswap -c - enddo ! k - endif -c - do k = 1,n - list(k) = rnorms(k) - enddo ! k -c -c -c Fill rnorms for the output. -c - ss = 0 -c - do k = 1,krank - rnorms(k) = a(k,k) - ss = ss + rnorms(k)**2 - enddo ! k -c -c -c Backsolve for proj, storing it at the beginning of a. -c - if(krank .gt. 0 .and. ss .gt. 0) then - call idz_lssolve(m,n,a,krank) - endif -c - if(ss .eq. 0) then -c - do k = 1,n - do j = 1,m -c - a(j,k) = 0 -c - enddo ! j - enddo ! k -c - endif -c -c - return - end -c -c -c -c - subroutine idz_reconid(m,krank,col,n,list,proj,approx) -c -c reconstructs the matrix that the routine idzp_id -c or idzr_id has decomposed, using the columns col -c of the reconstructed matrix whose indices are listed in list, -c in addition to the interpolation matrix proj. -c -c input: -c m -- first dimension of cols and approx -c krank -- first dimension of cols and proj; also, -c n-krank is the second dimension of proj -c col -- columns of the matrix to be reconstructed -c n -- second dimension of approx; also, -c n-krank is the second dimension of proj -c list(k) -- index of col(1:m,k) in the reconstructed matrix -c when k <= krank; in general, list describes -c the permutation required for reconstruction -c via cols and proj -c proj -- interpolation matrix -c -c output: -c approx -- reconstructed matrix -c - implicit none - integer m,n,krank,j,k,l,list(n) - complex*16 col(m,krank),proj(krank,n-krank),approx(m,n) -c -c - do j = 1,m - do k = 1,n -c - approx(j,list(k)) = 0 -c -c Add in the contributions due to the identity matrix. -c - if(k .le. krank) then - approx(j,list(k)) = approx(j,list(k)) + col(j,k) - endif -c -c Add in the contributions due to proj. -c - if(k .gt. krank) then - if(krank .gt. 0) then -c - do l = 1,krank - approx(j,list(k)) = approx(j,list(k)) - 1 + col(j,l)*proj(l,k-krank) - enddo ! l -c - endif - endif -c - enddo ! k - enddo ! j -c -c - return - end -c -c -c -c - subroutine idz_lssolve(m,n,a,krank) -c -c backsolves for proj satisfying R_11 proj ~ R_12, -c where R_11 = a(1:krank,1:krank) -c and R_12 = a(1:krank,krank+1:n). -c This routine overwrites the beginning of a with proj. -c -c input: -c m -- first dimension of a -c n -- second dimension of a; also, -c n-krank is the second dimension of proj -c a -- trapezoidal input matrix -c krank -- first dimension of proj; also, -c n-krank is the second dimension of proj -c -c output: -c a -- the first krank*(n-krank) elements of a constitute -c the krank x (n-krank) matrix proj -c - implicit none - integer m,n,krank,j,k,l - real*8 rnumer,rdenom - complex*16 a(m,n),sum -c -c -c Overwrite a(1:krank,krank+1:n) with proj. -c - do k = 1,n-krank - do j = krank,1,-1 -c - sum = 0 -c - do l = j+1,krank - sum = sum+a(j,l)*a(l,krank+k) - enddo ! l -c - a(j,krank+k) = a(j,krank+k)-sum -c -c Make sure that the entry in proj won't be too big; -c set the entry to 0 when roundoff would make it too big -c (in which case a(j,j) is so small that the contribution -c from this entry in proj to the overall matrix approximation -c is supposed to be negligible). -c - rnumer = a(j,krank+k)*conjg(a(j,krank+k)) - rdenom = a(j,j)*conjg(a(j,j)) -c - if(rnumer .lt. 2**30*rdenom) then - a(j,krank+k) = a(j,krank+k)/a(j,j) - else - a(j,krank+k) = 0 - endif -c - enddo ! j - enddo ! k -c -c -c Move proj from a(1:krank,krank+1:n) to the beginning of a. -c - call idz_moverup(m,n,krank,a) -c -c - return - end -c -c -c -c - subroutine idz_moverup(m,n,krank,a) -c -c moves the krank x (n-krank) matrix in a(1:krank,krank+1:n), -c where a is initially dimensioned m x n, to the beginning of a. -c (This is not the most natural way to code the move, -c but one of my usually well-behaved compilers chokes -c on more natural ways.) -c -c input: -c m -- initial first dimension of a -c n -- initial second dimension of a -c krank -- number of rows to move -c a -- m x n matrix whose krank x (n-krank) block -c a(1:krank,krank+1:n) is to be moved -c -c output: -c a -- array starting with the moved krank x (n-krank) block -c - implicit none - integer m,n,krank,j,k - complex*16 a(m*n) -c -c - do k = 1,n-krank - do j = 1,krank - a(j+krank*(k-1)) = a(j+m*(krank+k-1)) - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_getcols(m,n,matvec,p1,p2,p3,p4,krank,list, - 1 col,x) -c -c collects together the columns of the matrix a indexed by list -c into the matrix col, where routine matvec applies a -c to an arbitrary vector. -c -c input: -c m -- first dimension of a -c n -- second dimension of a -c matvec -- routine which applies a to an arbitrary vector; -c this routine must have a calling sequence of the form -c -c matvec(m,x,n,y,p1,p2,p3,p4) -c -c where m is the length of x, -c x is the vector to which the matrix is to be applied, -c n is the length of y, -c y is the product of the matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvec -c p2 -- parameter to be passed to routine matvec -c p3 -- parameter to be passed to routine matvec -c p4 -- parameter to be passed to routine matvec -c krank -- number of columns to be extracted -c list -- indices of the columns to be extracted -c -c output: -c col -- columns of a indexed by list -c -c work: -c x -- must be at least n complex*16 elements long -c - implicit none - integer m,n,krank,list(krank),j,k - complex*16 col(m,krank),x(n),p1,p2,p3,p4 - external matvec -c -c - do j = 1,krank -c - do k = 1,n - x(k) = 0 - enddo ! k -c - x(list(j)) = 1 -c - call matvec(n,x,m,col(1,j),p1,p2,p3,p4) -c - enddo ! j -c -c - return - end -c -c -c -c - subroutine idz_reconint(n,list,krank,proj,p) -c -c constructs p in the ID a = b p, -c where the columns of b are a subset of the columns of a, -c and p is the projection coefficient matrix, -c given list, krank, and proj output -c by routines idzp_id or idzr_id. -c -c input: -c n -- part of the second dimension of proj and p -c list -- list of columns retained from the original matrix -c in the ID -c krank -- rank of the ID -c proj -- matrix of projection coefficients in the ID -c -c output: -c p -- projection matrix in the ID -c - implicit none - integer n,krank,list(n),j,k - complex*16 proj(krank,n-krank),p(krank,n) -c -c - do k = 1,krank - do j = 1,n -c - if(j .le. krank) then - if(j .eq. k) p(k,list(j)) = 1 - if(j .ne. k) p(k,list(j)) = 0 - endif -c - if(j .gt. krank) then - p(k,list(j)) = proj(k,j-krank) - endif -c - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_copycols(m,n,a,krank,list,col) -c -c collects together the columns of the matrix a indexed by list -c into the matrix col. -c -c input: -c m -- first dimension of a -c n -- second dimension of a -c a -- matrix whose columns are to be extracted -c krank -- number of columns to be extracted -c list -- indices of the columns to be extracted -c -c output: -c col -- columns of a indexed by list -c - implicit none - integer m,n,krank,list(krank),j,k - complex*16 a(m,n),col(m,krank) -c -c - do k = 1,krank - do j = 1,m -c - col(j,k) = a(j,list(k)) -c - enddo ! j - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idz_id2svd.f b/scipy/linalg/src/id_dist/src/idz_id2svd.f deleted file mode 100644 index 55832e5d1c28..000000000000 --- a/scipy/linalg/src/id_dist/src/idz_id2svd.f +++ /dev/null @@ -1,389 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idz_id2svd converts an approximation to a matrix -c in the form of an ID to an approximation in the form of an SVD. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idz_id2svd(m,krank,b,n,list,proj,u,v,s,ier,w) -c -c converts an approximation to a matrix in the form of an ID -c to an approximation in the form of an SVD. -c -c input: -c m -- first dimension of b -c krank -- rank of the ID -c b -- columns of the original matrix in the ID -c list -- list of columns chosen from the original matrix -c in the ID -c n -- length of list and part of the second dimension of proj -c proj -- projection coefficients in the ID -c -c output: -c u -- left singular vectors -c v -- right singular vectors -c s -- singular values -c ier -- 0 when the routine terminates successfully; -c nonzero otherwise -c -c work: -c w -- must be at least (krank+1)*(m+3*n+10)+9*krank**2 -c complex*16 elements long -c -c _N.B._: This routine destroys b. -c - implicit none - integer m,krank,n,list(n),iwork,lwork,ip,lp,it,lt,ir,lr, - 1 ir2,lr2,ir3,lr3,iind,lind,iindt,lindt,lw,ier - real*8 s(krank) - complex*16 b(m,krank),proj(krank,n-krank),u(m,krank), - 1 v(n,krank),w((krank+1)*(m+3*n+10)+9*krank**2) -c -c -c Allocate memory for idz_id2svd0. -c - lw = 0 -c - iwork = lw+1 - lwork = 8*krank**2+10*krank - lw = lw+lwork -c - ip = lw+1 - lp = krank*n - lw = lw+lp -c - it = lw+1 - lt = n*krank - lw = lw+lt -c - ir = lw+1 - lr = krank*n - lw = lw+lr -c - ir2 = lw+1 - lr2 = krank*m - lw = lw+lr2 -c - ir3 = lw+1 - lr3 = krank*krank - lw = lw+lr3 -c - iind = lw+1 - lind = n/4+1 - lw = lw+1 -c - iindt = lw+1 - lindt = m/4+1 - lw = lw+1 -c -c - call idz_id2svd0(m,krank,b,n,list,proj,u,v,s,ier, - 1 w(iwork),w(ip),w(it),w(ir),w(ir2),w(ir3), - 2 w(iind),w(iindt)) -c -c - return - end -c -c -c -c - subroutine idz_id2svd0(m,krank,b,n,list,proj,u,v,s,ier, - 1 work,p,t,r,r2,r3,ind,indt) -c -c routine idz_id2svd serves as a memory wrapper -c for the present routine (please see routine idz_id2svd -c for further documentation). -c - implicit none -c - character*1 jobz - integer m,n,krank,list(n),ind(n),indt(m),ifadjoint, - 1 lwork,ldu,ldvt,ldr,info,j,k,ier - real*8 s(krank) - complex*16 b(m,krank),proj(krank,n-krank),p(krank,n), - 1 r(krank,n),r2(krank,m),t(n,krank),r3(krank,krank), - 2 u(m,krank),v(n,krank),work(8*krank**2+10*krank) -c -c -c - ier = 0 -c -c -c -c Construct the projection matrix p from the ID. -c - call idz_reconint(n,list,krank,proj,p) -c -c -c -c Compute a pivoted QR decomposition of b. -c - call idzr_qrpiv(m,krank,b,krank,ind,r) -c -c -c Extract r from the QR decomposition. -c - call idz_rinqr(m,krank,b,krank,r) -c -c -c Rearrange r according to ind. -c - call idz_rearr(krank,ind,krank,krank,r) -c -c -c -c Take the adjoint of p to obtain t. -c - call idz_matadj(krank,n,p,t) -c -c -c Compute a pivoted QR decomposition of t. -c - call idzr_qrpiv(n,krank,t,krank,indt,r2) -c -c -c Extract r2 from the QR decomposition. -c - call idz_rinqr(n,krank,t,krank,r2) -c -c -c Rearrange r2 according to indt. -c - call idz_rearr(krank,indt,krank,krank,r2) -c -c -c -c Multiply r and r2^* to obtain r3. -c - call idz_matmulta(krank,krank,r,krank,r2,r3) -c -c -c -c Use LAPACK to SVD r3. -c - jobz = 'S' - ldr = krank - lwork = 8*krank**2+10*krank - 1 - (krank**2+2*krank+3*krank**2+4*krank) - ldu = krank - ldvt = krank -c - call zgesdd(jobz,krank,krank,r3,ldr,s,work,ldu,r,ldvt, - 1 work(krank**2+2*krank+3*krank**2+4*krank+1),lwork, - 2 work(krank**2+2*krank+1),work(krank**2+1),info) -c - if(info .ne. 0) then - ier = info - return - endif -c -c -c -c Multiply the u from r3 from the left by the q from b -c to obtain the u for a. -c - do k = 1,krank -c - do j = 1,krank - u(j,k) = work(j+krank*(k-1)) - enddo ! j -c - do j = krank+1,m - u(j,k) = 0 - enddo ! j -c - enddo ! k -c - ifadjoint = 0 - call idz_qmatmat(ifadjoint,m,krank,b,krank,krank,u,r2) -c -c -c -c Take the adjoint of r to obtain r2. -c - call idz_matadj(krank,krank,r,r2) -c -c -c Multiply the v from r3 from the left by the q from p^* -c to obtain the v for a. -c - do k = 1,krank -c - do j = 1,krank - v(j,k) = r2(j,k) - enddo ! j -c - do j = krank+1,n - v(j,k) = 0 - enddo ! j -c - enddo ! k -c - ifadjoint = 0 - call idz_qmatmat(ifadjoint,n,krank,t,krank,krank,v,r2) -c -c - return - end -c -c -c -c - subroutine idz_matadj(m,n,a,aa) -c -c Takes the adjoint of a to obtain aa. -c -c input: -c m -- first dimension of a, and second dimension of aa -c n -- second dimension of a, and first dimension of aa -c a -- matrix whose adjoint is to be taken -c -c output: -c aa -- adjoint of a -c - implicit none - integer m,n,j,k - complex*16 a(m,n),aa(n,m) -c -c - do k = 1,n - do j = 1,m - aa(k,j) = conjg(a(j,k)) - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_matmulta(l,m,a,n,b,c) -c -c multiplies a and b^* to obtain c. -c -c input: -c l -- first dimension of a and c -c m -- second dimension of a and b -c a -- leftmost matrix in the product c = a b^* -c n -- first dimension of b and second dimension of c -c b -- rightmost matrix in the product c = a b^* -c -c output: -c c -- product of a and b^* -c - implicit none - integer l,m,n,i,j,k - complex*16 a(l,m),b(n,m),c(l,n),sum -c -c - do i = 1,l - do k = 1,n -c - sum = 0 -c - do j = 1,m - sum = sum+a(i,j)*conjg(b(k,j)) - enddo ! j -c - c(i,k) = sum -c - enddo ! k - enddo ! i -c -c - return - end -c -c -c -c - subroutine idz_rearr(krank,ind,m,n,a) -c -c rearranges a according to ind obtained -c from routines idzr_qrpiv or idzp_qrpiv, -c assuming that a = q r, where q and r are from idzr_qrpiv -c or idzp_qrpiv. -c -c input: -c krank -- rank obtained from routine idzp_qrpiv, -c or provided to routine idzr_qrpiv -c ind -- indexing array obtained from routine idzr_qrpiv -c or idzp_qrpiv -c m -- first dimension of a -c n -- second dimension of a -c a -- matrix to be rearranged -c -c output: -c a -- rearranged matrix -c - implicit none - integer k,krank,m,n,j,ind(krank) - complex*16 cswap,a(m,n) -c -c - do k = krank,1,-1 - do j = 1,m -c - cswap = a(j,k) - a(j,k) = a(j,ind(k)) - a(j,ind(k)) = cswap -c - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_rinqr(m,n,a,krank,r) -c -c extracts R in the QR decomposition specified by the output a -c of the routine idzr_qrpiv or idzp_qrpiv. -c -c input: -c m -- first dimension of a -c n -- second dimension of a and r -c a -- output of routine idzr_qrpiv or idzp_qrpiv -c krank -- rank output by routine idzp_qrpiv (or specified -c to routine idzr_qrpiv) -c -c output: -c r -- triangular factor in the QR decomposition specified -c by the output a of the routine idzr_qrpiv or idzp_qrpiv -c - implicit none - integer m,n,j,k,krank - complex*16 a(m,n),r(krank,n) -c -c -c Copy a into r and zero out the appropriate -c Householder vectors that are stored in one triangle of a. -c - do k = 1,n - do j = 1,krank - r(j,k) = a(j,k) - enddo ! j - enddo ! k -c - do k = 1,n - if(k .lt. krank) then - do j = k+1,krank - r(j,k) = 0 - enddo ! j - endif - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idz_qrpiv.f b/scipy/linalg/src/id_dist/src/idz_qrpiv.f deleted file mode 100644 index 3e7bcaf99855..000000000000 --- a/scipy/linalg/src/id_dist/src/idz_qrpiv.f +++ /dev/null @@ -1,898 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idzp_qrpiv computes the pivoted QR decomposition -c of a matrix via Householder transformations, -c stopping at a specified precision of the decomposition. -c -c routine idzr_qrpiv computes the pivoted QR decomposition -c of a matrix via Householder transformations, -c stopping at a specified rank of the decomposition. -c -c routine idz_qmatvec applies to a single vector -c the Q matrix (or its adjoint) in the QR decomposition -c of a matrix, as described by the output of idzp_qrpiv or -c idzr_qrpiv. If you're concerned about efficiency and want -c to apply Q (or its adjoint) to multiple vectors, -c use idz_qmatmat instead. -c -c routine idz_qmatmat applies -c to multiple vectors collected together -c as a matrix the Q matrix (or its adjoint) -c in the QR decomposition of a matrix, as described -c by the output of idzp_qrpiv. If you don't want to provide -c a work array and want to apply Q (or its adjoint) -c to a single vector, use idz_qmatvec instead. -c -c routine idz_qinqr reconstructs the Q matrix -c in a QR decomposition from the data generated by idzp_qrpiv -c or idzr_qrpiv. -c -c routine idz_permmult multiplies together a bunch -c of permutations. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idz_permmult(m,ind,n,indprod) -c -c multiplies together the series of permutations in ind. -c -c input: -c m -- length of ind -c ind(k) -- number of the slot with which to swap -c the k^th slot -c n -- length of indprod and indprodinv -c -c output: -c indprod -- product of the permutations in ind, -c with the permutation swapping 1 and ind(1) -c taken leftmost in the product, -c that swapping 2 and ind(2) taken next leftmost, -c ..., that swapping krank and ind(krank) -c taken rightmost; indprod(k) is the number -c of the slot with which to swap the k^th slot -c in the product permutation -c - implicit none - integer m,n,ind(m),indprod(n),k,iswap -c -c - do k = 1,n - indprod(k) = k - enddo ! k -c - do k = m,1,-1 -c -c Swap indprod(k) and indprod(ind(k)). -c - iswap = indprod(k) - indprod(k) = indprod(ind(k)) - indprod(ind(k)) = iswap -c - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_qinqr(m,n,a,krank,q) -c -c constructs the matrix q from idzp_qrpiv or idzr_qrpiv -c (see the routine idzp_qrpiv or idzr_qrpiv -c for more information). -c -c input: -c m -- first dimension of a; also, right now, q is m x m -c n -- second dimension of a -c a -- matrix output by idzp_qrpiv or idzr_qrpiv -c (and denoted the same there) -c krank -- numerical rank output by idzp_qrpiv or idzr_qrpiv -c (and denoted the same there) -c -c output: -c q -- unitary matrix implicitly specified by the data in a -c from idzp_qrpiv or idzr_qrpiv -c -c Note: -c Right now, this routine simply multiplies -c one after another the krank Householder matrices -c in the full QR decomposition of a, -c in order to obtain the complete m x m Q factor in the QR. -c This routine should instead use the following -c (more elaborate but more efficient) scheme -c to construct a q dimensioned q(krank,m); this scheme -c was introduced by Robert Schreiber and Charles Van Loan -c in "A Storage-Efficient _WY_ Representation -c for Products of Householder Transformations," -c _SIAM Journal on Scientific and Statistical Computing_, -c Vol. 10, No. 1, pp. 53-57, January, 1989: -c -c Theorem 1. Suppose that Q = _1_ + YTY^* is -c an m x m unitary matrix, -c where Y is an m x k matrix -c and T is a k x k upper triangular matrix. -c Suppose also that P = _1_ - 2 v v^* is -c a Householder matrix and Q_+ = QP, -c where v is an m x 1 real vector, -c normalized so that v^* v = 1. -c Then, Q_+ = _1_ + Y_+ T_+ Y_+^*, -c where Y_+ = (Y v) is the m x (k+1) matrix -c formed by adjoining v to the right of Y, -c ( T z ) -c and T_+ = ( ) is -c ( 0 -2 ) -c the (k+1) x (k+1) upper triangular matrix -c formed by adjoining z to the right of T -c and the vector (0 ... 0 -2) with k zeroes below (T z), -c where z = -2 T Y^* v. -c -c Now, suppose that A is a (rank-deficient) matrix -c whose complete QR decomposition has -c the blockwise partioned form -c ( Q_11 Q_12 ) ( R_11 R_12 ) ( Q_11 ) -c A = ( ) ( ) = ( ) (R_11 R_12). -c ( Q_21 Q_22 ) ( 0 0 ) ( Q_21 ) -c Then, the only blocks of the orthogonal factor -c in the above QR decomposition of A that matter are -c ( Q_11 ) -c Q_11 and Q_21, _i.e._, only the block of columns ( ) -c ( Q_21 ) -c interests us. -c Suppose in addition that Q_11 is a k x k matrix, -c Q_21 is an (m-k) x k matrix, and that -c ( Q_11 Q_12 ) -c ( ) = _1_ + YTY^*, as in Theorem 1 above. -c ( Q_21 Q_22 ) -c Then, Q_11 = _1_ + Y_1 T Y_1^* -c and Q_21 = Y_2 T Y_1^*, -c where Y_1 is the k x k matrix and Y_2 is the (m-k) x k matrix -c ( Y_1 ) -c so that Y = ( ). -c ( Y_2 ) -c -c So, you can calculate T and Y via the above recursions, -c and then use these to compute the desired Q_11 and Q_21. -c -c - implicit none - integer m,n,krank,j,k,mm,ifrescal - real*8 scal - complex*16 a(m,n),q(m,m) -c -c -c Zero all of the entries of q. -c - do k = 1,m - do j = 1,m - q(j,k) = 0 - enddo ! j - enddo ! k -c -c -c Place 1's along the diagonal of q. -c - do k = 1,m - q(k,k) = 1 - enddo ! k -c -c -c Apply the krank Householder transformations stored in a. -c - do k = krank,1,-1 - do j = k,m - mm = m-k+1 - ifrescal = 1 - if(k .lt. m) call idz_houseapp(mm,a(k+1,k),q(k,j), - 1 ifrescal,scal,q(k,j)) - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_qmatvec(ifadjoint,m,n,a,krank,v) -c -c applies to a single vector the Q matrix (or its adjoint) -c which the routine idzp_qrpiv or idzr_qrpiv has stored -c in a triangle of the matrix it produces (stored, incidentally, -c as data for applying a bunch of Householder reflections). -c Use the routine idz_qmatmat to apply the Q matrix -c (or its adjoint) -c to a bunch of vectors collected together as a matrix, -c if you're concerned about efficiency. -c -c input: -c ifadjoint -- set to 0 for applying Q; -c set to 1 for applying the adjoint of Q -c m -- first dimension of a and length of v -c n -- second dimension of a -c a -- data describing the qr decomposition of a matrix, -c as produced by idzp_qrpiv or idzr_qrpiv -c krank -- numerical rank -c v -- vector to which Q (or its adjoint) is to be applied -c -c output: -c v -- vector to which Q (or its adjoint) has been applied -c - implicit none - save - integer m,n,krank,k,ifrescal,mm,ifadjoint - real*8 scal - complex*16 a(m,n),v(m) -c -c - ifrescal = 1 -c -c - if(ifadjoint .eq. 0) then -c - do k = krank,1,-1 - mm = m-k+1 - if(k .lt. m) call idz_houseapp(mm,a(k+1,k),v(k), - 1 ifrescal,scal,v(k)) - enddo ! k -c - endif -c -c - if(ifadjoint .eq. 1) then -c - do k = 1,krank - mm = m-k+1 - if(k .lt. m) call idz_houseapp(mm,a(k+1,k),v(k), - 1 ifrescal,scal,v(k)) - enddo ! k -c - endif -c -c - return - end -c -c -c -c - subroutine idz_qmatmat(ifadjoint,m,n,a,krank,l,b,work) -c -c applies to a bunch of vectors collected together as a matrix -c the Q matrix (or its adjoint) which the routine idzp_qrpiv -c or idzr_qrpiv has stored in a triangle of the matrix -c it produces (stored, incidentally, as data -c for applying a bunch of Householder reflections). -c Use the routine idz_qmatvec to apply the Q matrix -c (or its adjoint) -c to a single vector, if you'd rather not provide a work array. -c -c input: -c ifadjoint -- set to 0 for applying Q; -c set to 1 for applying the adjoint of Q -c m -- first dimension of both a and b -c n -- second dimension of a -c a -- data describing the qr decomposition of a matrix, -c as produced by idzp_qrpiv or idzr_qrpiv -c krank -- numerical rank -c l -- second dimension of b -c b -- matrix to which Q (or its adjoint) is to be applied -c -c output: -c b -- matrix to which Q (or its adjoint) has been applied -c -c work: -c work -- must be at least krank real*8 elements long -c - implicit none - save - integer l,m,n,krank,j,k,ifrescal,mm,ifadjoint - real*8 work(krank) - complex*16 a(m,n),b(m,l) -c -c - if(ifadjoint .eq. 0) then -c -c -c Handle the first iteration, j = 1, -c calculating all scals (ifrescal = 1). -c - ifrescal = 1 -c - j = 1 -c - do k = krank,1,-1 - if(k .lt. m) then - mm = m-k+1 - call idz_houseapp(mm,a(k+1,k),b(k,j),ifrescal, - 1 work(k),b(k,j)) - endif - enddo ! k -c -c - if(l .gt. 1) then -c -c Handle the other iterations, j > 1, -c using the scals just computed (ifrescal = 0). -c - ifrescal = 0 -c - do j = 2,l -c - do k = krank,1,-1 - if(k .lt. m) then - mm = m-k+1 - call idz_houseapp(mm,a(k+1,k),b(k,j),ifrescal, - 1 work(k),b(k,j)) - endif - enddo ! k -c - enddo ! j -c - endif ! j .gt. 1 -c -c - endif ! ifadjoint .eq. 0 -c -c - if(ifadjoint .eq. 1) then -c -c -c Handle the first iteration, j = 1, -c calculating all scals (ifrescal = 1). -c - ifrescal = 1 -c - j = 1 -c - do k = 1,krank - if(k .lt. m) then - mm = m-k+1 - call idz_houseapp(mm,a(k+1,k),b(k,j),ifrescal, - 1 work(k),b(k,j)) - endif - enddo ! k -c -c - if(l .gt. 1) then -c -c Handle the other iterations, j > 1, -c using the scals just computed (ifrescal = 0). -c - ifrescal = 0 -c - do j = 2,l -c - do k = 1,krank - if(k .lt. m) then - mm = m-k+1 - call idz_houseapp(mm,a(k+1,k),b(k,j),ifrescal, - 1 work(k),b(k,j)) - endif - enddo ! k -c - enddo ! j -c - endif ! j .gt. 1 -c -c - endif ! ifadjoint .eq. 1 -c -c - return - end -c -c -c -c - subroutine idzp_qrpiv(eps,m,n,a,krank,ind,ss) -c -c computes the pivoted QR decomposition -c of the matrix input into a, using Householder transformations, -c _i.e._, transforms the matrix a from its input value in -c to the matrix out with entry -c -c m -c out(j,indprod(k)) = Sigma q(l,j) * in(l,k), -c l=1 -c -c for all j = 1, ..., krank, and k = 1, ..., n, -c -c where in = the a from before the routine runs, -c out = the a from after the routine runs, -c out(j,k) = 0 when j > k (so that out is triangular), -c q(1:m,1), ..., q(1:m,krank) are orthonormal, -c indprod is the product of the permutations given by ind, -c (as computable via the routine permmult, -c with the permutation swapping 1 and ind(1) taken leftmost -c in the product, that swapping 2 and ind(2) taken next leftmost, -c ..., that swapping krank and ind(krank) taken rightmost), -c and with the matrix out satisfying -c -c krank -c in(j,k) = Sigma q(j,l) * out(l,indprod(k)) + epsilon(j,k), -c l=1 -c -c for all j = 1, ..., m, and k = 1, ..., n, -c -c for some matrix epsilon such that -c the root-sum-square of the entries of epsilon -c <= the root-sum-square of the entries of in * eps. -c Well, technically, this routine outputs the Householder vectors -c (or, rather, their second through last entries) -c in the part of a that is supposed to get zeroed, that is, -c in a(j,k) with m >= j > k >= 1. -c -c input: -c eps -- relative precision of the resulting QR decomposition -c m -- first dimension of a and q -c n -- second dimension of a -c a -- matrix whose QR decomposition gets computed -c -c output: -c a -- triangular (R) factor in the QR decompositon -c of the matrix input into the same storage locations, -c with the Householder vectors stored in the part of a -c that would otherwise consist entirely of zeroes, that is, -c in a(j,k) with m >= j > k >= 1 -c krank -- numerical rank -c ind(k) -- index of the k^th pivot vector; -c the following code segment will correctly rearrange -c the product b of q and the upper triangle of out -c so that b matches the input matrix in -c to relative precision eps: -c -c copy the non-rearranged product of q and out into b -c set k to krank -c [start of loop] -c swap b(1:m,k) and b(1:m,ind(k)) -c decrement k by 1 -c if k > 0, then go to [start of loop] -c -c work: -c ss -- must be at least n real*8 words long -c -c _N.B._: This routine outputs the Householder vectors -c (or, rather, their second through last entries) -c in the part of a that is supposed to get zeroed, that is, -c in a(j,k) with m >= j > k >= 1. -c -c reference: -c Golub and Van Loan, "Matrix Computations," 3rd edition, -c Johns Hopkins University Press, 1996, Chapter 5. -c - implicit none - integer n,m,ind(n),krank,k,j,kpiv,mm,nupdate,ifrescal - real*8 ss(n),eps,ssmax,scal,ssmaxin,rswap,feps - complex*16 a(m,n),cswap -c -c - feps = .1d-16 -c -c -c Compute the sum of squares of the entries in each column of a, -c the maximum of all such sums, and find the first pivot -c (column with the greatest such sum). -c - ssmax = 0 - kpiv = 1 -c - do k = 1,n -c - ss(k) = 0 - do j = 1,m - ss(k) = ss(k)+a(j,k)*conjg(a(j,k)) - enddo ! j -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - ssmaxin = ssmax -c - nupdate = 0 -c -c -c While ssmax > eps**2*ssmaxin, krank < m, and krank < n, -c do the following block of code, -c which ends at the statement labeled 2000. -c - krank = 0 - 1000 continue -c - if(ssmax .le. eps**2*ssmaxin - 1 .or. krank .ge. m .or. krank .ge. n) goto 2000 - krank = krank+1 -c -c - mm = m-krank+1 -c -c -c Perform the pivoting. -c - ind(krank) = kpiv -c -c Swap a(1:m,krank) and a(1:m,kpiv). -c - do j = 1,m - cswap = a(j,krank) - a(j,krank) = a(j,kpiv) - a(j,kpiv) = cswap - enddo ! j -c -c Swap ss(krank) and ss(kpiv). -c - rswap = ss(krank) - ss(krank) = ss(kpiv) - ss(kpiv) = rswap -c -c - if(krank .lt. m) then -c -c -c Compute the data for the Householder transformation -c which will zero a(krank+1,krank), ..., a(m,krank) -c when applied to a, replacing a(krank,krank) -c with the first entry of the result of the application -c of the Householder matrix to a(krank:m,krank), -c and storing entries 2 to mm of the Householder vector -c in a(krank+1,krank), ..., a(m,krank) -c (which otherwise would get zeroed upon application -c of the Householder transformation). -c - call idz_house(mm,a(krank,krank),a(krank,krank), - 1 a(krank+1,krank),scal) - ifrescal = 0 -c -c -c Apply the Householder transformation -c to the lower right submatrix of a -c with upper leftmost entry at position (krank,krank+1). -c - if(krank .lt. n) then - do k = krank+1,n - call idz_houseapp(mm,a(krank+1,krank),a(krank,k), - 1 ifrescal,scal,a(krank,k)) - enddo ! k - endif -c -c -c Update the sums-of-squares array ss. -c - do k = krank,n - ss(k) = ss(k)-a(krank,k)*conjg(a(krank,k)) - enddo ! k -c -c -c Find the pivot (column with the greatest sum of squares -c of its entries). -c - ssmax = 0 - kpiv = krank+1 -c - if(krank .lt. n) then -c - do k = krank+1,n -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - endif ! krank .lt. n -c -c -c Recompute the sums-of-squares and the pivot -c when ssmax first falls below -c sqrt((1000*feps)^2) * ssmaxin -c and when ssmax first falls below -c ((1000*feps)^2) * ssmaxin. -c - if( - 1 (ssmax .lt. sqrt((1000*feps)**2) * ssmaxin - 2 .and. nupdate .eq. 0) .or. - 3 (ssmax .lt. ((1000*feps)**2) * ssmaxin - 4 .and. nupdate .eq. 1) - 5 ) then -c - nupdate = nupdate+1 -c - ssmax = 0 - kpiv = krank+1 -c - if(krank .lt. n) then -c - do k = krank+1,n -c - ss(k) = 0 - do j = krank+1,m - ss(k) = ss(k)+a(j,k)*conjg(a(j,k)) - enddo ! j -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - endif ! krank .lt. n -c - endif -c -c - endif ! krank .lt. m -c -c - goto 1000 - 2000 continue -c -c - return - end -c -c -c -c - subroutine idzr_qrpiv(m,n,a,krank,ind,ss) -c -c computes the pivoted QR decomposition -c of the matrix input into a, using Householder transformations, -c _i.e._, transforms the matrix a from its input value in -c to the matrix out with entry -c -c m -c out(j,indprod(k)) = Sigma q(l,j) * in(l,k), -c l=1 -c -c for all j = 1, ..., krank, and k = 1, ..., n, -c -c where in = the a from before the routine runs, -c out = the a from after the routine runs, -c out(j,k) = 0 when j > k (so that out is triangular), -c q(1:m,1), ..., q(1:m,krank) are orthonormal, -c indprod is the product of the permutations given by ind, -c (as computable via the routine permmult, -c with the permutation swapping 1 and ind(1) taken leftmost -c in the product, that swapping 2 and ind(2) taken next leftmost, -c ..., that swapping krank and ind(krank) taken rightmost), -c and with the matrix out satisfying -c -c min(m,n,krank) -c in(j,k) = Sigma q(j,l) * out(l,indprod(k)) -c l=1 -c -c + epsilon(j,k), -c -c for all j = 1, ..., m, and k = 1, ..., n, -c -c for some matrix epsilon whose norm is (hopefully) minimized -c by the pivoting procedure. -c Well, technically, this routine outputs the Householder vectors -c (or, rather, their second through last entries) -c in the part of a that is supposed to get zeroed, that is, -c in a(j,k) with m >= j > k >= 1. -c -c input: -c m -- first dimension of a and q -c n -- second dimension of a -c a -- matrix whose QR decomposition gets computed -c krank -- desired rank of the output matrix -c (please note that if krank > m or krank > n, -c then the rank of the output matrix will be -c less than krank) -c -c output: -c a -- triangular (R) factor in the QR decompositon -c of the matrix input into the same storage locations, -c with the Householder vectors stored in the part of a -c that would otherwise consist entirely of zeroes, that is, -c in a(j,k) with m >= j > k >= 1 -c ind(k) -- index of the k^th pivot vector; -c the following code segment will correctly rearrange -c the product b of q and the upper triangle of out -c so that b matches the input matrix in -c to relative precision eps: -c -c copy the non-rearranged product of q and out into b -c set k to krank -c [start of loop] -c swap b(1:m,k) and b(1:m,ind(k)) -c decrement k by 1 -c if k > 0, then go to [start of loop] -c -c work: -c ss -- must be at least n real*8 words long -c -c _N.B._: This routine outputs the Householder vectors -c (or, rather, their second through last entries) -c in the part of a that is supposed to get zeroed, that is, -c in a(j,k) with m >= j > k >= 1. -c -c reference: -c Golub and Van Loan, "Matrix Computations," 3rd edition, -c Johns Hopkins University Press, 1996, Chapter 5. -c - implicit none - integer n,m,ind(n),krank,k,j,kpiv,mm,nupdate,ifrescal, - 1 loops,loop - real*8 ss(n),ssmax,scal,ssmaxin,rswap,feps - complex*16 a(m,n),cswap -c -c - feps = .1d-16 -c -c -c Compute the sum of squares of the entries in each column of a, -c the maximum of all such sums, and find the first pivot -c (column with the greatest such sum). -c - ssmax = 0 - kpiv = 1 -c - do k = 1,n -c - ss(k) = 0 - do j = 1,m - ss(k) = ss(k)+a(j,k)*conjg(a(j,k)) - enddo ! j -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - ssmaxin = ssmax -c - nupdate = 0 -c -c -c Set loops = min(krank,m,n). -c - loops = krank - if(m .lt. loops) loops = m - if(n .lt. loops) loops = n -c - do loop = 1,loops -c -c - mm = m-loop+1 -c -c -c Perform the pivoting. -c - ind(loop) = kpiv -c -c Swap a(1:m,loop) and a(1:m,kpiv). -c - do j = 1,m - cswap = a(j,loop) - a(j,loop) = a(j,kpiv) - a(j,kpiv) = cswap - enddo ! j -c -c Swap ss(loop) and ss(kpiv). -c - rswap = ss(loop) - ss(loop) = ss(kpiv) - ss(kpiv) = rswap -c -c - if(loop .lt. m) then -c -c -c Compute the data for the Householder transformation -c which will zero a(loop+1,loop), ..., a(m,loop) -c when applied to a, replacing a(loop,loop) -c with the first entry of the result of the application -c of the Householder matrix to a(loop:m,loop), -c and storing entries 2 to mm of the Householder vector -c in a(loop+1,loop), ..., a(m,loop) -c (which otherwise would get zeroed upon application -c of the Householder transformation). -c - call idz_house(mm,a(loop,loop),a(loop,loop), - 1 a(loop+1,loop),scal) - ifrescal = 0 -c -c -c Apply the Householder transformation -c to the lower right submatrix of a -c with upper leftmost entry at position (loop,loop+1). -c - if(loop .lt. n) then - do k = loop+1,n - call idz_houseapp(mm,a(loop+1,loop),a(loop,k), - 1 ifrescal,scal,a(loop,k)) - enddo ! k - endif -c -c -c Update the sums-of-squares array ss. -c - do k = loop,n - ss(k) = ss(k)-a(loop,k)*conjg(a(loop,k)) - enddo ! k -c -c -c Find the pivot (column with the greatest sum of squares -c of its entries). -c - ssmax = 0 - kpiv = loop+1 -c - if(loop .lt. n) then -c - do k = loop+1,n -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - endif ! loop .lt. n -c -c -c Recompute the sums-of-squares and the pivot -c when ssmax first falls below -c sqrt((1000*feps)^2) * ssmaxin -c and when ssmax first falls below -c ((1000*feps)^2) * ssmaxin. -c - if( - 1 (ssmax .lt. sqrt((1000*feps)**2) * ssmaxin - 2 .and. nupdate .eq. 0) .or. - 3 (ssmax .lt. ((1000*feps)**2) * ssmaxin - 4 .and. nupdate .eq. 1) - 5 ) then -c - nupdate = nupdate+1 -c - ssmax = 0 - kpiv = loop+1 -c - if(loop .lt. n) then -c - do k = loop+1,n -c - ss(k) = 0 - do j = loop+1,m - ss(k) = ss(k)+a(j,k)*conjg(a(j,k)) - enddo ! j -c - if(ss(k) .gt. ssmax) then - ssmax = ss(k) - kpiv = k - endif -c - enddo ! k -c - endif ! loop .lt. n -c - endif -c -c - endif ! loop .lt. m -c -c - enddo ! loop -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idz_sfft.f b/scipy/linalg/src/id_dist/src/idz_sfft.f deleted file mode 100644 index c8dd9ab18bc0..000000000000 --- a/scipy/linalg/src/id_dist/src/idz_sfft.f +++ /dev/null @@ -1,210 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idz_sffti initializes routine idz_sfft. -c -c routine idz_sfft rapidly computes a subset of the entries -c of the DFT of a vector, composed with permutation matrices -c both on input and on output. -c -c routine idz_ldiv finds the greatest integer less than or equal -c to a specified integer, that is divisible by another (larger) -c specified integer. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idz_ldiv(l,n,m) -c -c finds the greatest integer less than or equal to l -c that divides n. -c -c input: -c l -- integer at least as great as m -c n -- integer divisible by m -c -c output: -c m -- greatest integer less than or equal to l that divides n -c - implicit none - integer n,l,m -c -c - m = l -c - 1000 continue - if(m*(n/m) .eq. n) goto 2000 -c - m = m-1 - goto 1000 -c - 2000 continue -c -c - return - end -c -c -c -c - subroutine idz_sffti(l,ind,n,wsave) -c -c initializes wsave for use with routine idz_sfft. -c -c input: -c l -- number of entries in the output of idz_sfft to compute -c ind -- indices of the entries in the output of idz_sfft -c to compute -c n -- length of the vector to be transformed -c -c output: -c wsave -- array needed by routine idz_sfft for processing -c - implicit none - integer l,ind(l),n,nblock,ii,m,idivm,imodm,i,j,k - real*8 r1,twopi,fact - complex*16 wsave(2*l+15+3*n),ci,twopii -c - ci = (0,1) - r1 = 1 - twopi = 2*4*atan(r1) - twopii = twopi*ci -c -c -c Determine the block lengths for the FFTs. -c - call idz_ldiv(l,n,nblock) - m = n/nblock -c -c -c Initialize wsave for use with routine zfftf. -c - call zffti(nblock,wsave) -c -c -c Calculate the coefficients in the linear combinations -c needed for the direct portion of the calculation. -c - fact = 1/sqrt(r1*n) -c - ii = 2*l+15 -c - do j = 1,l -c - i = ind(j) -c - idivm = (i-1)/m - imodm = (i-1)-m*idivm -c - do k = 1,m - wsave(ii+m*(j-1)+k) = exp(-twopii*imodm*(k-1)/(r1*m)) - 1 * exp(-twopii*(k-1)*idivm/(r1*n)) * fact - enddo ! k -c - enddo ! j -c -c - return - end -c -c -c -c - subroutine idz_sfft(l,ind,n,wsave,v) -c -c computes a subset of the entries of the DFT of v, -c composed with permutation matrices both on input and on output, -c via a two-stage procedure (routine zfftf2 is supposed -c to calculate the full vector from which idz_sfft returns -c a subset of the entries, when zfftf2 has the same parameter -c nblock as in the present routine). -c -c input: -c l -- number of entries in the output to compute -c ind -- indices of the entries of the output to compute -c n -- length of v -c v -- vector to be transformed -c wsave -- processing array initialized by routine idz_sffti -c -c output: -c v -- entries indexed by ind are given their appropriate -c transformed values -c -c _N.B._: The user has to boost the memory allocations -c for wsave (and change iii accordingly) if s/he wishes -c to use strange sizes of n; it's best to stick to powers -c of 2. -c -c references: -c Sorensen and Burrus, "Efficient computation of the DFT with -c only a subset of input or output points," -c IEEE Transactions on Signal Processing, 41 (3): 1184-1200, -c 1993. -c Woolfe, Liberty, Rokhlin, Tygert, "A fast randomized algorithm -c for the approximation of matrices," Applied and -c Computational Harmonic Analysis, 25 (3): 335-366, 2008; -c Section 3.3. -c - implicit none - integer n,m,l,k,j,ind(l),i,idivm,nblock,ii,iii - real*8 r1,twopi - complex*16 v(n),wsave(2*l+15+3*n),ci,sum -c - ci = (0,1) - r1 = 1 - twopi = 2*4*atan(r1) -c -c -c Determine the block lengths for the FFTs. -c - call idz_ldiv(l,n,nblock) -c -c - m = n/nblock -c -c -c FFT each block of length nblock of v. -c - do k = 1,m - call zfftf(nblock,v(nblock*(k-1)+1),wsave) - enddo ! k -c -c -c Transpose v to obtain wsave(2*l+15+2*n+1 : 2*l+15+3*n). -c - iii = 2*l+15+2*n -c - do k = 1,m - do j = 1,nblock - wsave(iii+m*(j-1)+k) = v(nblock*(k-1)+j) - enddo ! j - enddo ! k -c -c -c Directly calculate the desired entries of v. -c - ii = 2*l+15 - iii = 2*l+15+2*n -c - do j = 1,l -c - i = ind(j) -c - idivm = (i-1)/m -c - sum = 0 -c - do k = 1,m - sum = sum + wsave(ii+m*(j-1)+k) * wsave(iii+m*idivm+k) - enddo ! k -c - v(i) = sum -c - enddo ! j -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idz_snorm.f b/scipy/linalg/src/id_dist/src/idz_snorm.f deleted file mode 100644 index 9fe713d47661..000000000000 --- a/scipy/linalg/src/id_dist/src/idz_snorm.f +++ /dev/null @@ -1,407 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idz_snorm estimates the spectral norm -c of a matrix specified by routines for applying the matrix -c and its adjoint to arbitrary vectors. This routine uses -c the power method with a random starting vector. -c -c routine idz_diffsnorm estimates the spectral norm -c of the difference between two matrices specified by routines -c for applying the matrices and their adjoints -c to arbitrary vectors. This routine uses -c the power method with a random starting vector. -c -c routine idz_enorm calculates the Euclidean norm of a vector. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idz_snorm(m,n,matveca,p1a,p2a,p3a,p4a, - 1 matvec,p1,p2,p3,p4,its,snorm,v,u) -c -c estimates the spectral norm of a matrix a specified -c by a routine matvec for applying a to an arbitrary vector, -c and by a routine matveca for applying a^* -c to an arbitrary vector. This routine uses the power method -c with a random starting vector. -c -c input: -c m -- number of rows in a -c n -- number of columns in a -c matveca -- routine which applies the adjoint of a -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matveca(m,x,n,y,p1a,p2a,p3a,p4a), -c -c where m is the length of x, -c x is the vector to which the adjoint of a -c is to be applied, -c n is the length of y, -c y is the product of the adjoint of a and x, -c and p1a, p2a, p3a, and p4a are user-specified -c parameters -c p1a -- parameter to be passed to routine matveca -c p2a -- parameter to be passed to routine matveca -c p3a -- parameter to be passed to routine matveca -c p4a -- parameter to be passed to routine matveca -c matvec -- routine which applies the matrix a -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvec(n,x,m,y,p1,p2,p3,p4), -c -c where n is the length of x, -c x is the vector to which a is to be applied, -c m is the length of y, -c y is the product of a and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvec -c p2 -- parameter to be passed to routine matvec -c p3 -- parameter to be passed to routine matvec -c p4 -- parameter to be passed to routine matvec -c its -- number of iterations of the power method to conduct -c -c output: -c snorm -- estimate of the spectral norm of a -c v -- estimate of a normalized right singular vector -c corresponding to the greatest singular value of a -c -c work: -c u -- must be at least m complex*16 elements long -c -c reference: -c Kuczynski and Wozniakowski, "Estimating the largest eigenvalue -c by the power and Lanczos algorithms with a random start," -c SIAM Journal on Matrix Analysis and Applications, -c 13 (4): 1992, 1094-1122. -c - implicit none - integer m,n,its,it,n2,k - real*8 snorm,enorm - complex*16 p1a,p2a,p3a,p4a,p1,p2,p3,p4,u(m),v(n) - external matveca,matvec -c -c -c Fill the real and imaginary parts of each entry -c of the initial vector v with i.i.d. random variables -c drawn uniformly from [-1,1]. -c - n2 = 2*n - call id_srand(n2,v) -c - do k = 1,n - v(k) = 2*v(k)-1 - enddo ! k -c -c -c Normalize v. -c - call idz_enorm(n,v,enorm) -c - do k = 1,n - v(k) = v(k)/enorm - enddo ! k -c -c - do it = 1,its -c -c Apply a to v, obtaining u. -c - call matvec(n,v,m,u,p1,p2,p3,p4) -c -c Apply a^* to u, obtaining v. -c - call matveca(m,u,n,v,p1a,p2a,p3a,p4a) -c -c Normalize v. -c - call idz_enorm(n,v,snorm) -c - if(snorm .ne. 0) then -c - do k = 1,n - v(k) = v(k)/snorm - enddo ! k -c - endif -c - snorm = sqrt(snorm) -c - enddo ! it -c -c - return - end -c -c -c -c - subroutine idz_enorm(n,v,enorm) -c -c computes the Euclidean norm of v, the square root -c of the sum of the squares of the absolute values -c of the entries of v. -c -c input: -c n -- length of v -c v -- vector whose Euclidean norm is to be calculated -c -c output: -c enorm -- Euclidean norm of v -c - implicit none - integer n,k - real*8 enorm - complex*16 v(n) -c -c - enorm = 0 -c - do k = 1,n - enorm = enorm+v(k)*conjg(v(k)) - enddo ! k -c - enorm = sqrt(enorm) -c -c - return - end -c -c -c -c - subroutine idz_diffsnorm(m,n,matveca,p1a,p2a,p3a,p4a, - 1 matveca2,p1a2,p2a2,p3a2,p4a2, - 2 matvec,p1,p2,p3,p4, - 3 matvec2,p12,p22,p32,p42,its,snorm,w) -c -c estimates the spectral norm of the difference between matrices -c a and a2, where a is specified by routines matvec and matveca -c for applying a and a^* to arbitrary vectors, -c and a2 is specified by routines matvec2 and matveca2 -c for applying a2 and (a2)^* to arbitrary vectors. -c This routine uses the power method -c with a random starting vector. -c -c input: -c m -- number of rows in a, as well as the number of rows in a2 -c n -- number of columns in a, as well as the number of columns -c in a2 -c matveca -- routine which applies the adjoint of a -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matveca(m,x,n,y,p1a,p2a,p3a,p4a), -c -c where m is the length of x, -c x is the vector to which the adjoint of a -c is to be applied, -c n is the length of y, -c y is the product of the adjoint of a and x, -c and p1a, p2a, p3a, and p4a are user-specified -c parameters -c p1a -- parameter to be passed to routine matveca -c p2a -- parameter to be passed to routine matveca -c p3a -- parameter to be passed to routine matveca -c p4a -- parameter to be passed to routine matveca -c matveca2 -- routine which applies the adjoint of a2 -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matveca2(m,x,n,y,p1a2,p2a2,p3a2,p4a2), -c -c where m is the length of x, -c x is the vector to which the adjoint of a2 -c is to be applied, -c n is the length of y, -c y is the product of the adjoint of a2 and x, -c and p1a2, p2a2, p3a2, and p4a2 are user-specified -c parameters -c p1a2 -- parameter to be passed to routine matveca2 -c p2a2 -- parameter to be passed to routine matveca2 -c p3a2 -- parameter to be passed to routine matveca2 -c p4a2 -- parameter to be passed to routine matveca2 -c matvec -- routine which applies the matrix a -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvec(n,x,m,y,p1,p2,p3,p4), -c -c where n is the length of x, -c x is the vector to which a is to be applied, -c m is the length of y, -c y is the product of a and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvec -c p2 -- parameter to be passed to routine matvec -c p3 -- parameter to be passed to routine matvec -c p4 -- parameter to be passed to routine matvec -c matvec2 -- routine which applies the matrix a2 -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvec2(n,x,m,y,p12,p22,p32,p42), -c -c where n is the length of x, -c x is the vector to which a2 is to be applied, -c m is the length of y, -c y is the product of a2 and x, and -c p12, p22, p32, and p42 are user-specified parameters -c p12 -- parameter to be passed to routine matvec2 -c p22 -- parameter to be passed to routine matvec2 -c p32 -- parameter to be passed to routine matvec2 -c p42 -- parameter to be passed to routine matvec2 -c its -- number of iterations of the power method to conduct -c -c output: -c snorm -- estimate of the spectral norm of a-a2 -c -c work: -c w -- must be at least 3*m+3*n complex*16 elements long -c -c reference: -c Kuczynski and Wozniakowski, "Estimating the largest eigenvalue -c by the power and Lanczos algorithms with a random start," -c SIAM Journal on Matrix Analysis and Applications, -c 13 (4): 1992, 1094-1122. -c - implicit none - integer m,n,its,lw,iu,lu,iu1,lu1,iu2,lu2, - 1 iv,lv,iv1,lv1,iv2,lv2 - real*8 snorm - complex*16 p1a,p2a,p3a,p4a,p1a2,p2a2,p3a2,p4a2, - 1 p1,p2,p3,p4,p12,p22,p32,p42,w(3*m+3*n) - external matveca,matvec,matveca2,matvec2 -c -c -c Allocate memory in w. -c - lw = 0 -c - iu = lw+1 - lu = m - lw = lw+lu -c - iu1 = lw+1 - lu1 = m - lw = lw+lu1 -c - iu2 = lw+1 - lu2 = m - lw = lw+lu2 -c - iv = lw+1 - lv = n - lw = lw+1 -c - iv1 = lw+1 - lv1 = n - lw = lw+lv1 -c - iv2 = lw+1 - lv2 = n - lw = lw+lv2 -c -c - call idz_diffsnorm0(m,n,matveca,p1a,p2a,p3a,p4a, - 1 matveca2,p1a2,p2a2,p3a2,p4a2, - 2 matvec,p1,p2,p3,p4, - 3 matvec2,p12,p22,p32,p42, - 4 its,snorm,w(iu),w(iu1),w(iu2), - 5 w(iv),w(iv1),w(iv2)) -c -c - return - end -c -c -c -c - subroutine idz_diffsnorm0(m,n,matveca,p1a,p2a,p3a,p4a, - 1 matveca2,p1a2,p2a2,p3a2,p4a2, - 2 matvec,p1,p2,p3,p4, - 3 matvec2,p12,p22,p32,p42, - 4 its,snorm,u,u1,u2,v,v1,v2) -c -c routine idz_diffsnorm serves as a memory wrapper -c for the present routine. (Please see routine idz_diffsnorm -c for further documentation.) -c - implicit none - integer m,n,its,it,n2,k - real*8 snorm,enorm - complex*16 p1a,p2a,p3a,p4a,p1a2,p2a2,p3a2,p4a2, - 1 p1,p2,p3,p4,p12,p22,p32,p42,u(m),u1(m),u2(m), - 2 v(n),v1(n),v2(n) - external matveca,matvec,matveca2,matvec2 -c -c -c Fill the real and imaginary parts of each entry -c of the initial vector v with i.i.d. random variables -c drawn uniformly from [-1,1]. -c - n2 = 2*n - call id_srand(n2,v) -c - do k = 1,n - v(k) = 2*v(k)-1 - enddo ! k -c -c -c Normalize v. -c - call idz_enorm(n,v,enorm) -c - do k = 1,n - v(k) = v(k)/enorm - enddo ! k -c -c - do it = 1,its -c -c Apply a and a2 to v, obtaining u1 and u2. -c - call matvec(n,v,m,u1,p1,p2,p3,p4) - call matvec2(n,v,m,u2,p12,p22,p32,p42) -c -c Form u = u1-u2. -c - do k = 1,m - u(k) = u1(k)-u2(k) - enddo ! k -c -c Apply a^* and (a2)^* to u, obtaining v1 and v2. -c - call matveca(m,u,n,v1,p1a,p2a,p3a,p4a) - call matveca2(m,u,n,v2,p1a2,p2a2,p3a2,p4a2) -c -c Form v = v1-v2. -c - do k = 1,n - v(k) = v1(k)-v2(k) - enddo ! k -c -c Normalize v. -c - call idz_enorm(n,v,snorm) -c - if(snorm .gt. 0) then -c - do k = 1,n - v(k) = v(k)/snorm - enddo ! k -c - endif -c - snorm = sqrt(snorm) -c - enddo ! it -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idz_svd.f b/scipy/linalg/src/id_dist/src/idz_svd.f deleted file mode 100644 index e14cf66a007b..000000000000 --- a/scipy/linalg/src/id_dist/src/idz_svd.f +++ /dev/null @@ -1,438 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idzr_svd computes an approximation of specified rank -c to a given matrix, in the usual SVD form U S V^*, -c where U has orthonormal columns, V has orthonormal columns, -c and S is diagonal. -c -c routine idzp_svd computes an approximation of specified -c precision to a given matrix, in the usual SVD form U S V^*, -c where U has orthonormal columns, V has orthonormal columns, -c and S is diagonal. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idzr_svd(m,n,a,krank,u,v,s,ier,r) -c -c constructs a rank-krank SVD u diag(s) v^* approximating a, -c where u is an m x krank matrix whose columns are orthonormal, -c v is an n x krank matrix whose columns are orthonormal, -c and diag(s) is a diagonal krank x krank matrix whose entries -c are all nonnegative. This routine combines a QR code -c (which is based on plane/Householder reflections) -c with the LAPACK routine zgesdd. -c -c input: -c m -- first dimension of a and u -c n -- second dimension of a, and first dimension of v -c a -- matrix to be SVD'd -c krank -- desired rank of the approximation to a -c -c output: -c u -- left singular vectors of a corresponding -c to the k greatest singular values of a -c v -- right singular vectors of a corresponding -c to the k greatest singular values of a -c s -- k greatest singular values of a -c ier -- 0 when the routine terminates successfully; -c nonzero when the routine encounters an error -c -c work: -c r -- must be at least -c (krank+2)*n+8*min(m,n)+6*krank**2+8*krank -c complex*16 elements long -c -c _N.B._: This routine destroys a. Also, please beware that -c the source code for this routine could be clearer. -c - implicit none - character*1 jobz - integer m,n,k,krank,ifadjoint,ldr,ldu,ldvadj,lwork, - 1 info,j,ier,io - real*8 s(krank) - complex*16 a(m,n),u(m,krank),v(n*krank),r(*) -c -c - io = 8*min(m,n) -c -c - ier = 0 -c -c -c Compute a pivoted QR decomposition of a. -c - call idzr_qrpiv(m,n,a,krank,r,r(io+1)) -c -c -c Extract R from the QR decomposition. -c - call idz_retriever(m,n,a,krank,r(io+1)) -c -c -c Rearrange R according to ind. -c - call idz_permuter(krank,r,krank,n,r(io+1)) -c -c -c Use LAPACK to SVD r, -c storing the krank (krank x 1) left singular vectors -c in r(io+krank*n+1 : io+krank*n+krank*krank). -c - jobz = 'S' - ldr = krank - lwork = 2*(krank**2+2*krank+n) - ldu = krank - ldvadj = krank -c - call zgesdd(jobz,krank,n,r(io+1),ldr,s,r(io+krank*n+1),ldu, - 1 v,ldvadj,r(io+krank*n+krank*krank+1),lwork, - 2 r(io+krank*n+krank*krank+lwork+1),r,info) -c - if(info .ne. 0) then - ier = info - return - endif -c -c -c Multiply the U from R from the left by Q to obtain the U -c for A. -c - do k = 1,krank -c - do j = 1,krank - u(j,k) = r(io+krank*n+j+krank*(k-1)) - enddo ! j -c - do j = krank+1,m - u(j,k) = 0 - enddo ! j -c - enddo ! k -c - ifadjoint = 0 - call idz_qmatmat(ifadjoint,m,n,a,krank,krank,u,r) -c -c -c Take the adjoint of v to obtain r. -c - call idz_adjer(krank,n,v,r) -c -c -c Copy r into v. -c - do k = 1,n*krank - v(k) = r(k) - enddo ! k -c -c - return - end -c -c -c -c - subroutine idzp_svd(lw,eps,m,n,a,krank,iu,iv,is,w,ier) -c -c constructs a rank-krank SVD U Sigma V^* approximating a -c to precision eps, where U is an m x krank matrix whose -c columns are orthonormal, V is an n x krank matrix whose -c columns are orthonormal, and Sigma is a diagonal krank x krank -c matrix whose entries are all nonnegative. -c The entries of U are stored in w, starting at w(iu); -c the entries of V are stored in w, starting at w(iv). -c The diagonal entries of Sigma are stored in w, -c starting at w(is). This routine combines a QR code -c (which is based on plane/Householder reflections) -c with the LAPACK routine zgesdd. -c -c input: -c lw -- maximum usable length of w (in complex*16 elements) -c eps -- precision to which the SVD approximates a -c m -- first dimension of a and u -c n -- second dimension of a, and first dimension of v -c a -- matrix to be SVD'd -c -c output: -c krank -- rank of the approximation to a -c iu -- index in w of the first entry of the matrix -c of orthonormal left singular vectors of a -c iv -- index in w of the first entry of the matrix -c of orthonormal right singular vectors of a -c is -- index in w of the first entry of the array -c of singular values of a; the singular values are stored -c as complex*16 numbers whose imaginary parts are zeros -c w -- array containing the singular values and singular vectors -c of a; w doubles as a work array, and so must be at least -c (krank+1)*(m+2*n+9)+8*min(m,n)+6*krank**2 -c complex*16 elements long, where krank is the rank -c output by the present routine -c ier -- 0 when the routine terminates successfully; -c -1000 when lw is too small; -c other nonzero values when zgesdd bombs -c -c _N.B._: This routine destroys a. Also, please beware that -c the source code for this routine could be clearer. -c w must be at least -c (krank+1)*(m+2*n+9)+8*min(m,n)+6*krank**2 -c complex*16 elements long, where krank is the rank -c output by the present routine. -c - implicit none - character*1 jobz - integer m,n,k,krank,ifadjoint,ldr,ldu,ldvadj,lwork, - 1 info,j,ier,io,iu,iv,is,ivi,isi,lu,lv,ls,lw - real*8 eps - complex*16 a(m,n),w(*) -c -c - io = 8*min(m,n) -c -c - ier = 0 -c -c -c Compute a pivoted QR decomposition of a. -c - call idzp_qrpiv(eps,m,n,a,krank,w,w(io+1)) -c -c - if(krank .gt. 0) then -c -c -c Extract R from the QR decomposition. -c - call idz_retriever(m,n,a,krank,w(io+1)) -c -c -c Rearrange R according to ind. -c - call idz_permuter(krank,w,krank,n,w(io+1)) -c -c -c Use LAPACK to SVD R, -c storing the krank (krank x 1) left singular vectors -c in w(io+krank*n+1 : io+krank*n+krank*krank). -c - jobz = 'S' - ldr = krank - lwork = 2*(krank**2+2*krank+n) - ldu = krank - ldvadj = krank -c - ivi = io+krank*n+krank*krank+lwork+3*krank**2+4*krank+1 - lv = n*krank -c - isi = ivi+lv - ls = krank -c - if(lw .lt. isi+ls+m*krank-1) then - ier = -1000 - return - endif -c - call zgesdd(jobz,krank,n,w(io+1),ldr,w(isi),w(io+krank*n+1), - 1 ldu,w(ivi),ldvadj,w(io+krank*n+krank*krank+1), - 2 lwork,w(io+krank*n+krank*krank+lwork+1),w,info) -c - if(info .ne. 0) then - ier = info - return - endif -c -c -c Take the adjoint of w(ivi:ivi+lv-1) to obtain V. -c - iv = 1 - call idz_adjer(krank,n,w(ivi),w(iv)) -c -c -c Copy w(isi:isi+ls/2) into w(is:is+ls-1). -c - is = iv+lv -c - call idz_realcomp(ls,w(isi),w(is)) -c -c -c Multiply the U from R from the left by Q to obtain the U -c for A. -c - iu = is+ls - lu = m*krank -c - do k = 1,krank -c - do j = 1,krank - w(iu-1+j+krank*(k-1)) = w(io+krank*n+j+krank*(k-1)) - enddo ! j -c - enddo ! k -c - do k = krank,1,-1 -c - do j = m,krank+1,-1 - w(iu-1+j+m*(k-1)) = 0 - enddo ! j -c - do j = krank,1,-1 - w(iu-1+j+m*(k-1)) = w(iu-1+j+krank*(k-1)) - enddo ! j -c - enddo ! k -c - ifadjoint = 0 - call idz_qmatmat(ifadjoint,m,n,a,krank,krank,w(iu), - 1 w(iu+lu+1)) -c -c - endif ! krank .gt. 0 -c -c - return - end -c -c -c -c - subroutine idz_realcomp(n,a,b) -c -c copies the real*8 array a into the complex*16 array b. -c -c input: -c n -- length of a and b -c a -- real*8 array to be copied into b -c -c output: -c b -- complex*16 copy of a -c - integer n,k - real*8 a(n) - complex*16 b(n) -c -c - do k = 1,n - b(k) = a(k) - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_permuter(krank,ind,m,n,a) -c -c permutes the columns of a according to ind obtained -c from routine idzr_qrpiv or idzp_qrpiv, assuming that -c a = q r from idzr_qrpiv or idzp_qrpiv. -c -c input: -c krank -- rank specified to routine idzr_qrpiv -c or obtained from routine idzp_qrpiv -c ind -- indexing array obtained from routine idzr_qrpiv -c or idzp_qrpiv -c m -- first dimension of a -c n -- second dimension of a -c a -- matrix to be rearranged -c -c output: -c a -- rearranged matrix -c - implicit none - integer k,krank,m,n,j,ind(krank) - complex*16 cswap,a(m,n) -c -c - do k = krank,1,-1 - do j = 1,m -c - cswap = a(j,k) - a(j,k) = a(j,ind(k)) - a(j,ind(k)) = cswap -c - enddo ! j - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_retriever(m,n,a,krank,r) -c -c extracts R in the QR decomposition specified by the output a -c of the routine idzr_qrpiv or idzp_qrpiv -c -c input: -c m -- first dimension of a -c n -- second dimension of a and r -c a -- output of routine idzr_qrpiv or idzp_qrpiv -c krank -- rank specified to routine idzr_qrpiv, -c or output by routine idzp_qrpiv -c -c output: -c r -- triangular factor in the QR decomposition specified -c by the output a of the routine idzr_qrpiv or idzp_qrpiv -c - implicit none - integer m,n,j,k,krank - complex*16 a(m,n),r(krank,n) -c -c -c Copy a into r and zero out the appropriate -c Householder vectors that are stored in one triangle of a. -c - do k = 1,n - do j = 1,krank - r(j,k) = a(j,k) - enddo ! j - enddo ! k -c - do k = 1,n - if(k .lt. krank) then - do j = k+1,krank - r(j,k) = 0 - enddo ! j - endif - enddo ! k -c -c - return - end -c -c -c -c - subroutine idz_adjer(m,n,a,aa) -c -c forms the adjoint aa of a. -c -c input: -c m -- first dimension of a and second dimension of aa -c n -- second dimension of a and first dimension of aa -c a -- matrix whose adjoint is to be taken -c -c output: -c aa -- adjoint of a -c - implicit none - integer m,n,j,k - complex*16 a(m,n),aa(n,m) -c -c - do k = 1,n - do j = 1,m - aa(k,j) = conjg(a(j,k)) - enddo ! j - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idzp_aid.f b/scipy/linalg/src/id_dist/src/idzp_aid.f deleted file mode 100644 index 784b40cde43b..000000000000 --- a/scipy/linalg/src/id_dist/src/idzp_aid.f +++ /dev/null @@ -1,390 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idzp_aid computes the ID, to a specified precision, -c of an arbitrary matrix. This routine is randomized. -c -c routine idz_estrank estimates the numerical rank, -c to a specified precision, of an arbitrary matrix. -c This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idzp_aid(eps,m,n,a,work,krank,list,proj) -c -c computes the ID of the matrix a, i.e., lists in list -c the indices of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c krank -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank) (*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon dimensioned epsilon(m,n-krank) -c such that the greatest singular value of epsilon -c <= the greatest singular value of a * eps. -c -c input: -c eps -- precision to which the ID is to be computed -c m -- first dimension of a -c n -- second dimension of a -c a -- matrix to be decomposed; the present routine does not -c alter a -c work -- initialization array that has been constructed -c by routine idz_frmi -c -c output: -c krank -- numerical rank of a to precision eps -c list -- indices of the columns in the ID -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns -c in the original matrix being ID'd; -c proj doubles as a work array in the present routine, so -c proj must be at least n*(2*n2+1)+n2+1 complex*16 -c elements long, where n2 is the greatest integer -c less than or equal to m, such that n2 is -c a positive integer power of two. -c -c _N.B._: The algorithm used by this routine is randomized. -c proj must be at least n*(2*n2+1)+n2+1 complex*16 -c elements long, where n2 is the greatest integer -c less than or equal to m, such that n2 is -c a positive integer power of two. -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,n,list(n),krank,kranki,n2 - real*8 eps - complex*16 a(m,n),proj(*),work(17*m+70) -c -c -c Allocate memory in proj. -c - n2 = work(2) -c -c -c Find the rank of a. -c - call idz_estrank(eps,m,n,a,work,kranki,proj) -c -c - if(kranki .eq. 0) call idzp_aid0(eps,m,n,a,krank,list,proj, - 1 proj(m*n+1)) -c - if(kranki .ne. 0) call idzp_aid1(eps,n2,n,kranki,proj, - 1 krank,list,proj(n2*n+1)) -c -c - return - end -c -c -c -c - subroutine idzp_aid0(eps,m,n,a,krank,list,proj,rnorms) -c -c uses routine idzp_id to ID a without modifying its entries -c (in contrast to the usual behavior of idzp_id). -c -c input: -c eps -- precision of the decomposition to be constructed -c m -- first dimension of a -c n -- second dimension of a -c -c output: -c krank -- numerical rank of the ID -c list -- indices of the columns in the ID -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns in a; -c proj doubles as a work array in the present routine, so -c must be at least m*n complex*16 elements long -c -c work: -c rnorms -- must be at least n real*8 elements long -c -c _N.B._: proj must be at least m*n complex*16 elements long -c - implicit none - integer m,n,krank,list(n),j,k - real*8 eps,rnorms(n) - complex*16 a(m,n),proj(m,n) -c -c -c Copy a into proj. -c - do k = 1,n - do j = 1,m - proj(j,k) = a(j,k) - enddo ! j - enddo ! k -c -c -c ID proj. -c - call idzp_id(eps,m,n,proj,krank,list,rnorms) -c -c - return - end -c -c -c -c - subroutine idzp_aid1(eps,n2,n,kranki,proj,krank,list,rnorms) -c -c IDs the uppermost kranki x n block of the n2 x n matrix -c input as proj. -c -c input: -c eps -- precision of the decomposition to be constructed -c n2 -- first dimension of proj as input -c n -- second dimension of proj as input -c kranki -- number of rows to extract from proj -c proj -- matrix containing the kranki x n block to be ID'd -c -c output: -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns -c in the original matrix being ID'd -c krank -- numerical rank of the ID -c list -- indices of the columns in the ID -c -c work: -c rnorms -- must be at least n real*8 elements long -c - implicit none - integer n,n2,kranki,krank,list(n),j,k - real*8 eps,rnorms(n) - complex*16 proj(n2*n) -c -c -c Move the uppermost kranki x n block of the n2 x n matrix proj -c to the beginning of proj. -c - do k = 1,n - do j = 1,kranki - proj(j+kranki*(k-1)) = proj(j+n2*(k-1)) - enddo ! j - enddo ! k -c -c -c ID proj. -c - call idzp_id(eps,kranki,n,proj,krank,list,rnorms) -c -c - return - end -c -c -c -c - subroutine idz_estrank(eps,m,n,a,w,krank,ra) -c -c estimates the numerical rank krank of an m x n matrix a -c to precision eps. This routine applies n2 random vectors -c to a, obtaining ra, where n2 is the greatest integer -c less than or equal to m such that n2 is a positive integer -c power of two. krank is typically about 8 higher than -c the actual numerical rank. -c -c input: -c eps -- precision defining the numerical rank -c m -- first dimension of a -c n -- second dimension of a -c a -- matrix whose rank is to be estimated -c w -- initialization array that has been constructed -c by routine idz_frmi -c -c output: -c krank -- estimate of the numerical rank of a; -c this routine returns krank = 0 when the actual -c numerical rank is nearly full (that is, -c greater than n - 8 or n2 - 8) -c ra -- product of an n2 x m random matrix and the m x n matrix -c a, where n2 is the greatest integer less than or equal -c to m such that n2 is a positive integer power of two; -c ra doubles as a work array in the present routine, and so -c must be at least n*n2+(n+1)*(n2+1) complex*16 elements -c long -c -c _N.B._: ra must be at least n*n2+(n2+1)*(n+1) complex*16 -c elements long for use in the present routine -c (here, n2 is the greatest integer less than or equal -c to m, such that n2 is a positive integer power of two). -c This routine returns krank = 0 when the actual -c numerical rank is nearly full. -c - implicit none - integer m,n,krank,n2,irat,lrat,iscal,lscal,ira,lra,lra2 - real*8 eps - complex*16 a(m,n),ra(*),w(17*m+70) -c -c -c Extract from the array w initialized by routine idz_frmi -c the greatest integer less than or equal to m that is -c a positive integer power of two. -c - n2 = w(2) -c -c -c Allocate memory in ra. -c - lra = 0 -c - ira = lra+1 - lra2 = n2*n - lra = lra+lra2 -c - irat = lra+1 - lrat = n*(n2+1) - lra = lra+lrat -c - iscal = lra+1 - lscal = n2+1 - lra = lra+lscal -c - call idz_estrank0(eps,m,n,a,w,n2,krank,ra(ira),ra(irat), - 1 ra(iscal)) -c -c - return - end -c -c -c -c - subroutine idz_estrank0(eps,m,n,a,w,n2,krank,ra,rat,scal) -c -c routine idz_estrank serves as a memory wrapper -c for the present routine. (Please see routine idz_estrank -c for further documentation.) -c - implicit none - integer m,n,n2,krank,ifrescal,k,nulls,j - real*8 eps,scal(n2+1),ss,ssmax - complex*16 a(m,n),ra(n2,n),residual,w(17*m+70),rat(n,n2+1) -c -c -c Apply the random matrix to every column of a, obtaining ra. -c - do k = 1,n - call idz_frm(m,n2,w,a(1,k),ra(1,k)) - enddo ! k -c -c -c Compute the sum of squares of the entries in each column of ra -c and the maximum of all such sums. -c - ssmax = 0 -c - do k = 1,n -c - ss = 0 - do j = 1,m - ss = ss+a(j,k)*conjg(a(j,k)) - enddo ! j -c - if(ss .gt. ssmax) ssmax = ss -c - enddo ! k -c -c -c Transpose ra to obtain rat. -c - call idz_transposer(n2,n,ra,rat) -c -c - krank = 0 - nulls = 0 -c -c -c Loop until nulls = 7, krank+nulls = n2, or krank+nulls = n. -c - 1000 continue -c -c - if(krank .gt. 0) then -c -c Apply the previous Householder transformations -c to rat(:,krank+1). -c - ifrescal = 0 -c - do k = 1,krank - call idz_houseapp(n-k+1,rat(1,k),rat(k,krank+1), - 1 ifrescal,scal(k),rat(k,krank+1)) - enddo ! k -c - endif ! krank .gt. 0 -c -c -c Compute the Householder vector associated -c with rat(krank+1:*,krank+1). -c - call idz_house(n-krank,rat(krank+1,krank+1), - 1 residual,rat(1,krank+1),scal(krank+1)) -c -c - krank = krank+1 - if(abs(residual) .le. eps*sqrt(ssmax)) nulls = nulls+1 -c -c - if(nulls .lt. 7 .and. krank+nulls .lt. n2 - 1 .and. krank+nulls .lt. n) - 2 goto 1000 -c -c - if(nulls .lt. 7) krank = 0 -c -c - return - end -c -c -c -c - subroutine idz_transposer(m,n,a,at) -c -c transposes a to obtain at. -c -c input: -c m -- first dimension of a, and second dimension of at -c n -- second dimension of a, and first dimension of at -c a -- matrix to be transposed -c -c output: -c at -- transpose of a -c - implicit none - integer m,n,j,k - complex*16 a(m,n),at(n,m) -c -c - do k = 1,n - do j = 1,m -c - at(k,j) = a(j,k) -c - enddo ! j - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idzp_asvd.f b/scipy/linalg/src/id_dist/src/idzp_asvd.f deleted file mode 100644 index 4704f5bbd583..000000000000 --- a/scipy/linalg/src/id_dist/src/idzp_asvd.f +++ /dev/null @@ -1,207 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idzp_asvd computes the SVD, to a specified precision, -c of an arbitrary matrix. This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idzp_asvd(lw,eps,m,n,a,winit,krank,iu,iv,is,w,ier) -c -c constructs a rank-krank SVD U Sigma V^* approximating a -c to precision eps, where U is an m x krank matrix whose -c columns are orthonormal, V is an n x krank matrix whose -c columns are orthonormal, and Sigma is a diagonal krank x krank -c matrix whose entries are all nonnegative. -c The entries of U are stored in w, starting at w(iu); -c the entries of V are stored in w, starting at w(iv). -c The diagonal entries of Sigma are stored in w, -c starting at w(is). This routine uses a randomized algorithm. -c -c input: -c lw -- maximum usable length (in complex*16 elements) -c of the array w -c eps -- precision of the desired approximation -c m -- number of rows in a -c n -- number of columns in a -c a -- matrix to be approximated; the present routine does not -c alter a -c winit -- initialization array that has been constructed -c by routine idz_frmi -c -c output: -c krank -- rank of the SVD constructed -c iu -- index in w of the first entry of the matrix -c of orthonormal left singular vectors of a -c iv -- index in w of the first entry of the matrix -c of orthonormal right singular vectors of a -c is -- index in w of the first entry of the array -c of singular values of a -c w -- array containing the singular values and singular vectors -c of a; w doubles as a work array, and so must be at least -c max( (krank+1)*(3*m+5*n+11)+8*krank**2, (2*n+1)*(n2+1) ) -c complex*16 elements long, where n2 is the greatest integer -c less than or equal to m, such that n2 is -c a positive integer power of two; krank is the rank output -c by this routine -c ier -- 0 when the routine terminates successfully; -c -1000 when lw is too small; -c other nonzero values when idz_id2svd bombs -c -c _N.B._: w must be at least -c max( (krank+1)*(3*m+5*n+11)+8*krank^2, (2*n+1)*(n2+1) ) -c complex*16 elements long, where n2 is -c the greatest integer less than or equal to m, -c such that n2 is a positive integer power of two; -c krank is the rank output by this routine. -c Also, the algorithm used by this routine is randomized. -c - implicit none - integer m,n,krank,lw,ilist,llist,iproj,lproj,icol,lcol, - 1 iwork,lwork,k,ier,lw2,iu,iv,is,iui,ivi,isi,lu,lv,ls - real*8 eps - complex*16 a(m,n),winit(17*m+70),w(*) -c -c -c Allocate memory in w. -c - lw2 = 0 -c - ilist = lw2+1 - llist = n - lw2 = lw2+llist -c - iproj = lw2+1 -c -c -c ID a. -c - call idzp_aid(eps,m,n,a,winit,krank,w(ilist),w(iproj)) -c -c - if(krank .gt. 0) then -c -c -c Allocate more memory in w. -c - lproj = krank*(n-krank) - lw2 = lw2+lproj -c - icol = lw2+1 - lcol = m*krank - lw2 = lw2+lcol -c - iui = lw2+1 - lu = m*krank - lw2 = lw2+lu -c - ivi = lw2+1 - lv = n*krank - lw2 = lw2+lv -c - isi = lw2+1 - ls = krank - lw2 = lw2+ls -c - iwork = lw2+1 - lwork = (krank+1)*(m+3*n+10)+9*krank**2 - lw2 = lw2+lwork -c -c - if(lw .lt. lw2) then - ier = -1000 - return - endif -c -c - call idzp_asvd0(m,n,a,krank,w(ilist),w(iproj), - 1 w(iui),w(ivi),w(isi),ier,w(icol),w(iwork)) - if(ier .ne. 0) return -c -c - iu = 1 - iv = iu+lu - is = iv+lv -c -c -c Copy the singular values and singular vectors -c into their proper locations. -c - do k = 1,lu - w(iu+k-1) = w(iui+k-1) - enddo ! k -c - do k = 1,lv - w(iv+k-1) = w(ivi+k-1) - enddo ! k -c - call idz_realcomplex(ls,w(isi),w(is)) -c -c - endif ! krank .gt. 0 -c -c - return - end -c -c -c -c - subroutine idzp_asvd0(m,n,a,krank,list,proj,u,v,s,ier, - 1 col,work) -c -c routine idzp_asvd serves as a memory wrapper -c for the present routine (please see routine idzp_asvd -c for further documentation). -c - implicit none - integer m,n,krank,list(n),ier - real*8 s(krank) - complex*16 a(m,n),u(m,krank),v(n,krank), - 1 proj(krank,n-krank),col(m,krank), - 2 work((krank+1)*(m+3*n+10)+9*krank**2) -c -c -c Collect together the columns of a indexed by list into col. -c - call idz_copycols(m,n,a,krank,list,col) -c -c -c Convert the ID to an SVD. -c - call idz_id2svd(m,krank,col,n,list,proj,u,v,s,ier,work) -c -c - return - end -c -c -c -c - subroutine idz_realcomplex(n,a,b) -c -c copies the real*8 array a into the complex*16 array b. -c -c input: -c n -- length of a and b -c a -- real*8 array to be copied into b -c -c output: -c b -- complex*16 copy of a -c - integer n,k - real*8 a(n) - complex*16 b(n) -c -c - do k = 1,n - b(k) = a(k) - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idzp_rid.f b/scipy/linalg/src/id_dist/src/idzp_rid.f deleted file mode 100644 index f12623aed640..000000000000 --- a/scipy/linalg/src/id_dist/src/idzp_rid.f +++ /dev/null @@ -1,379 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idzp_rid computes the ID, to a specified precision, -c of a matrix specified by a routine for applying its adjoint -c to arbitrary vectors. This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idzp_rid(lproj,eps,m,n,matveca,p1,p2,p3,p4, - 1 krank,list,proj,ier) -c -c computes the ID of a, i.e., lists in list the indices -c of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c krank -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank) (*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon dimensioned epsilon(m,n-krank) -c such that the greatest singular value of epsilon -c <= the greatest singular value of a * eps. -c -c input: -c lproj -- maximum usable length (in complex*16 elements) -c of the array proj -c eps -- precision to which the ID is to be computed -c m -- first dimension of a -c n -- second dimension of a -c matveca -- routine which applies the adjoint -c of the matrix to be ID'd to an arbitrary vector; -c this routine must have a calling sequence -c of the form -c -c matveca(m,x,n,y,p1,p2,p3,p4), -c -c where m is the length of x, -c x is the vector to which the adjoint -c of the matrix is to be applied, -c n is the length of y, -c y is the product of the adjoint of the matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matveca -c p2 -- parameter to be passed to routine matveca -c p3 -- parameter to be passed to routine matveca -c p4 -- parameter to be passed to routine matveca -c -c output: -c krank -- numerical rank -c list -- indices of the columns in the ID -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns -c in the original matrix being ID'd; -c the present routine uses proj as a work array, too, so -c proj must be at least m+1 + 2*n*(krank+1) complex*16 -c elements long, where krank is the rank output -c by the present routine -c ier -- 0 when the routine terminates successfully; -c -1000 when lproj is too small -c -c _N.B._: The algorithm used by this routine is randomized. -c proj must be at least m+1 + 2*n*(krank+1) complex*16 -c elements long, where krank is the rank output -c by the present routine. -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,n,list(n),krank,lw,iwork,lwork,ira,kranki,lproj, - 1 lra,ier,k - real*8 eps - complex*16 p1,p2,p3,p4,proj(*) - external matveca -c -c - ier = 0 -c -c -c Allocate memory in proj. -c - lw = 0 -c - iwork = lw+1 - lwork = m+2*n+1 - lw = lw+lwork -c - ira = lw+1 -c -c -c Find the rank of a. -c - lra = lproj-lwork - call idz_findrank(lra,eps,m,n,matveca,p1,p2,p3,p4, - 1 kranki,proj(ira),ier,proj(iwork)) - if(ier .ne. 0) return -c -c - if(lproj .lt. lwork+2*kranki*n) then - ier = -1000 - return - endif -c -c -c Take the adjoint of ra. -c - call idz_adjointer(n,kranki,proj(ira),proj(ira+kranki*n)) -c -c -c Move the adjoint thus obtained to the beginning of proj. -c - do k = 1,kranki*n - proj(k) = proj(ira+kranki*n+k-1) - enddo ! k -c -c -c ID the adjoint. -c - call idzp_id(eps,kranki,n,proj,krank,list,proj(1+kranki*n)) -c -c - return - end -c -c -c -c - subroutine idz_findrank(lra,eps,m,n,matveca,p1,p2,p3,p4, - 1 krank,ra,ier,w) -c -c estimates the numerical rank krank of a matrix a to precision -c eps, where the routine matveca applies the adjoint of a -c to an arbitrary vector. This routine applies the adjoint of a -c to krank random vectors, and returns the resulting vectors -c as the columns of ra. -c -c input: -c lra -- maximum usable length (in complex*16 elements) -c of array ra -c eps -- precision defining the numerical rank -c m -- first dimension of a -c n -- second dimension of a -c matveca -- routine which applies the adjoint -c of the matrix whose rank is to be estimated -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matveca(m,x,n,y,p1,p2,p3,p4), -c -c where m is the length of x, -c x is the vector to which the adjoint -c of the matrix is to be applied, -c n is the length of y, -c y is the product of the adjoint of the matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matveca -c p2 -- parameter to be passed to routine matveca -c p3 -- parameter to be passed to routine matveca -c p4 -- parameter to be passed to routine matveca -c -c output: -c krank -- estimate of the numerical rank of a -c ra -- product of the adjoint of a and a matrix whose entries -c are pseudorandom realizations of i.i.d. random numbers, -c uniformly distributed on [0,1]; -c ra must be at least 2*n*krank complex*16 elements long -c ier -- 0 when the routine terminates successfully; -c -1000 when lra is too small -c -c work: -c w -- must be at least m+2*n+1 complex*16 elements long -c -c _N.B._: ra must be at least 2*n*krank complex*16 elements long. -c Also, the algorithm used by this routine is randomized. -c - implicit none - integer m,n,lw,krank,ix,lx,iy,ly,iscal,lscal,lra,ier - real*8 eps - complex*16 p1,p2,p3,p4,ra(n,*),w(m+2*n+1) - external matveca -c -c - lw = 0 -c - ix = lw+1 - lx = m - lw = lw+lx -c - iy = lw+1 - ly = n - lw = lw+ly -c - iscal = lw+1 - lscal = n+1 - lw = lw+lscal -c -c - call idz_findrank0(lra,eps,m,n,matveca,p1,p2,p3,p4, - 1 krank,ra,ier,w(ix),w(iy),w(iscal)) -c -c - return - end -c -c -c -c - subroutine idz_findrank0(lra,eps,m,n,matveca,p1,p2,p3,p4, - 1 krank,ra,ier,x,y,scal) -c -c routine idz_findrank serves as a memory wrapper -c for the present routine. (Please see routine idz_findrank -c for further documentation.) -c - implicit none - integer m,n,krank,ifrescal,k,lra,ier,m2 - real*8 eps,enorm - complex*16 x(m),ra(n,2,*),p1,p2,p3,p4,scal(n+1),y(n),residual - external matveca -c -c - ier = 0 -c -c - krank = 0 -c -c -c Loop until the relative residual is greater than eps, -c or krank = m or krank = n. -c - 1000 continue -c -c - if(lra .lt. n*2*(krank+1)) then - ier = -1000 - return - endif -c -c -c Apply the adjoint of a to a random vector. -c - m2 = m*2 - call id_srand(m2,x) - call matveca(m,x,n,ra(1,1,krank+1),p1,p2,p3,p4) -c - do k = 1,n - y(k) = ra(k,1,krank+1) - enddo ! k -c -c - if(krank .eq. 0) then -c -c Compute the Euclidean norm of y. -c - enorm = 0 -c - do k = 1,n - enorm = enorm + y(k)*conjg(y(k)) - enddo ! k -c - enorm = sqrt(enorm) -c - endif ! krank .eq. 0 -c -c - if(krank .gt. 0) then -c -c Apply the previous Householder transformations to y. -c - ifrescal = 0 -c - do k = 1,krank - call idz_houseapp(n-k+1,ra(1,2,k),y(k), - 1 ifrescal,scal(k),y(k)) - enddo ! k -c - endif ! krank .gt. 0 -c -c -c Compute the Householder vector associated with y. -c - call idz_house(n-krank,y(krank+1), - 1 residual,ra(1,2,krank+1),scal(krank+1)) -c -c - krank = krank+1 -c -c - if(abs(residual) .gt. eps*enorm - 1 .and. krank .lt. m .and. krank .lt. n) - 2 goto 1000 -c -c -c Delete the Householder vectors from the array ra. -c - call idz_crunch(n,krank,ra) -c -c - return - end -c -c -c -c - subroutine idz_crunch(n,l,a) -c -c removes every other block of n entries from a vector. -c -c input: -c n -- length of each block to remove -c l -- half of the total number of blocks -c a -- original array -c -c output: -c a -- array with every other block of n entries removed -c - implicit none - integer j,k,n,l - complex*16 a(n,2*l) -c -c - do j = 2,l - do k = 1,n -c - a(k,j) = a(k,2*j-1) -c - enddo ! k - enddo ! j -c -c - return - end -c -c -c -c - subroutine idz_adjointer(m,n,a,aa) -c -c forms the adjoint aa of a. -c -c input: -c m -- first dimension of a, and second dimension of aa -c n -- second dimension of a, and first dimension of aa -c a -- matrix whose adjoint is to be taken -c -c output: -c aa -- adjoint of a -c - implicit none - integer m,n,j,k - complex*16 a(m,n),aa(n,m) -c -c - do k = 1,n - do j = 1,m -c - aa(k,j) = conjg(a(j,k)) -c - enddo ! j - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idzp_rsvd.f b/scipy/linalg/src/id_dist/src/idzp_rsvd.f deleted file mode 100644 index e34b3e3740fc..000000000000 --- a/scipy/linalg/src/id_dist/src/idzp_rsvd.f +++ /dev/null @@ -1,244 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idzp_rsvd computes the SVD, to a specified precision, -c of a matrix specified by routines for applying the matrix -c and its adjoint to arbitrary vectors. -c This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idzp_rsvd(lw,eps,m,n,matveca,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,iu,iv,is,w,ier) -c -c constructs a rank-krank SVD U Sigma V^* approximating a -c to precision eps, where matveca is a routine which applies a^* -c to an arbitrary vector, and matvec is a routine -c which applies a to an arbitrary vector; U is an m x krank -c matrix whose columns are orthonormal, V is an n x krank -c matrix whose columns are orthonormal, and Sigma is a diagonal -c krank x krank matrix whose entries are all nonnegative. -c The entries of U are stored in w, starting at w(iu); -c the entries of V are stored in w, starting at w(iv). -c The diagonal entries of Sigma are stored in w, -c starting at w(is). This routine uses a randomized algorithm. -c -c input: -c lw -- maximum usable length (in complex*16 elements) -c of the array w -c eps -- precision of the desired approximation -c m -- number of rows in a -c n -- number of columns in a -c matveca -- routine which applies the adjoint -c of the matrix to be SVD'd -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matveca(m,x,n,y,p1t,p2t,p3t,p4t), -c -c where m is the length of x, -c x is the vector to which the adjoint -c of the matrix is to be applied, -c n is the length of y, -c y is the product of the adjoint of the matrix and x, -c and p1t, p2t, p3t, and p4t are user-specified -c parameters -c p1t -- parameter to be passed to routine matveca -c p2t -- parameter to be passed to routine matveca -c p3t -- parameter to be passed to routine matveca -c p4t -- parameter to be passed to routine matveca -c matvec -- routine which applies the matrix to be SVD'd -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvec(n,x,m,y,p1,p2,p3,p4), -c -c where n is the length of x, -c x is the vector to which the matrix is to be applied, -c m is the length of y, -c y is the product of the matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvec -c p2 -- parameter to be passed to routine matvec -c p3 -- parameter to be passed to routine matvec -c p4 -- parameter to be passed to routine matvec -c -c output: -c krank -- rank of the SVD constructed -c iu -- index in w of the first entry of the matrix -c of orthonormal left singular vectors of a -c iv -- index in w of the first entry of the matrix -c of orthonormal right singular vectors of a -c is -- index in w of the first entry of the array -c of singular values of a; the singular values are stored -c as complex*16 numbers whose imaginary parts are zeros -c w -- array containing the singular values and singular vectors -c of a; w doubles as a work array, and so must be at least -c (krank+1)*(3*m+5*n+11)+8*krank^2 complex*16 elements long, -c where krank is the rank returned by the present routine -c ier -- 0 when the routine terminates successfully; -c -1000 when lw is too small; -c other nonzero values when idz_id2svd bombs -c -c _N.B._: w must be at least (krank+1)*(3*m+5*n+11)+8*krank**2 -c complex*16 elements long, where krank is the rank -c returned by the present routine. Also, the algorithm -c used by the present routine is randomized. -c - implicit none - integer m,n,krank,lw,lw2,ilist,llist,iproj,icol,lcol,lp, - 1 iwork,lwork,ier,lproj,iu,iv,is,lu,lv,ls,iui,ivi,isi,k - real*8 eps - complex*16 p1t,p2t,p3t,p4t,p1,p2,p3,p4,w(*) - external matveca,matvec -c -c -c Allocate some memory. -c - lw2 = 0 -c - ilist = lw2+1 - llist = n - lw2 = lw2+llist -c - iproj = lw2+1 -c -c -c ID a. -c - lp = lw-lw2 - call idzp_rid(lp,eps,m,n,matveca,p1t,p2t,p3t,p4t,krank, - 1 w(ilist),w(iproj),ier) - if(ier .ne. 0) return -c -c - if(krank .gt. 0) then -c -c -c Allocate more memory. -c - lproj = krank*(n-krank) - lw2 = lw2+lproj -c - icol = lw2+1 - lcol = m*krank - lw2 = lw2+lcol -c - iui = lw2+1 - lu = m*krank - lw2 = lw2+lu -c - ivi = lw2+1 - lv = n*krank - lw2 = lw2+lv -c - isi = lw2+1 - ls = krank - lw2 = lw2+ls -c - iwork = lw2+1 - lwork = (krank+1)*(m+3*n+10)+9*krank**2 - lw2 = lw2+lwork -c -c - if(lw .lt. lw2) then - ier = -1000 - return - endif -c -c - call idzp_rsvd0(m,n,matveca,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,w(iui),w(ivi), - 2 w(isi),ier,w(ilist),w(iproj),w(icol), - 3 w(iwork)) - if(ier .ne. 0) return -c -c - iu = 1 - iv = iu+lu - is = iv+lv -c -c -c Copy the singular values and singular vectors -c into their proper locations. -c - do k = 1,lu - w(iu+k-1) = w(iui+k-1) - enddo ! k -c - do k = 1,lv - w(iv+k-1) = w(ivi+k-1) - enddo ! k -c - call idz_reco(ls,w(isi),w(is)) -c -c - endif ! krank .gt. 0 -c -c - return - end -c -c -c -c - subroutine idzp_rsvd0(m,n,matveca,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,u,v,s,ier, - 2 list,proj,col,work) -c -c routine idzp_rsvd serves as a memory wrapper -c for the present routine (please see routine idzp_rsvd -c for further documentation). -c - implicit none - integer m,n,krank,list(n),ier - real*8 s(krank) - complex*16 p1t,p2t,p3t,p4t,p1,p2,p3,p4,u(m,krank),v(n,krank), - 1 proj(krank,n-krank),col(m*krank), - 2 work((krank+1)*(m+3*n+10)+9*krank**2) - external matveca,matvec -c -c -c Collect together the columns of a indexed by list into col. -c - call idz_getcols(m,n,matvec,p1,p2,p3,p4,krank,list,col,work) -c -c -c Convert the ID to an SVD. -c - call idz_id2svd(m,krank,col,n,list,proj,u,v,s,ier,work) -c -c - return - end -c -c -c -c - subroutine idz_reco(n,a,b) -c -c copies the real*8 array a into the complex*16 array b. -c -c input: -c n -- length of a and b -c a -- real*8 array to be copied into b -c -c output: -c b -- complex*16 copy of a -c - integer n,k - real*8 a(n) - complex*16 b(n) -c -c - do k = 1,n - b(k) = a(k) - enddo ! k -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idzr_aid.f b/scipy/linalg/src/id_dist/src/idzr_aid.f deleted file mode 100644 index e8380ecd30fc..000000000000 --- a/scipy/linalg/src/id_dist/src/idzr_aid.f +++ /dev/null @@ -1,209 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idzr_aid computes the ID, to a specified rank, -c of an arbitrary matrix. This routine is randomized. -c -c routine idzr_aidi initializes routine idzr_aid. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idzr_aid(m,n,a,krank,w,list,proj) -c -c computes the ID of the matrix a, i.e., lists in list -c the indices of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c min(m,n,krank) -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank)(*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon, dimensioned epsilon(m,n-krank), -c whose norm is (hopefully) minimized by the pivoting procedure. -c -c input: -c m -- number of rows in a -c n -- number of columns in a -c a -- matrix to be ID'd; the present routine does not alter a -c krank -- rank of the ID to be constructed -c w -- initialization array that routine idzr_aidi -c has constructed -c -c output: -c list -- indices of the columns in the ID -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns -c in the original matrix being ID'd -c -c _N.B._: The algorithm used by this routine is randomized. -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,n,krank,list(n),lw,ir,lr,lw2,iw - complex*16 a(m,n),proj(krank*(n-krank)), - 1 w((2*krank+17)*n+21*m+80) -c -c -c Allocate memory in w. -c - lw = 0 -c - iw = lw+1 - lw2 = 21*m+80+n - lw = lw+lw2 -c - ir = lw+1 - lr = (krank+8)*2*n - lw = lw+lr -c -c - call idzr_aid0(m,n,a,krank,w(iw),list,proj,w(ir)) -c -c - return - end -c -c -c -c - subroutine idzr_aid0(m,n,a,krank,w,list,proj,r) -c -c routine idzr_aid serves as a memory wrapper -c for the present routine -c (see idzr_aid for further documentation). -c - implicit none - integer k,l,m,n2,n,krank,list(n),mn,lproj - complex*16 a(m,n),r(krank+8,2*n),proj(krank,n-krank), - 1 w(21*m+80+n) -c -c Please note that the second dimension of r is 2*n -c (instead of n) so that if krank+8 >= m/2, then -c we can copy the whole of a into r. -c -c -c Retrieve the number of random test vectors -c and the greatest integer less than m that is -c a positive integer power of two. -c - l = w(1) - n2 = w(2) -c -c - if(l .lt. n2 .and. l .le. m) then -c -c Apply the random matrix. -c - do k = 1,n - call idz_sfrm(l,m,n2,w(11),a(1,k),r(1,k)) - enddo ! k -c -c ID r. -c - call idzr_id(l,n,r,krank,list,w(20*m+81)) -c -c Retrieve proj from r. -c - lproj = krank*(n-krank) - call idzr_copyzarr(lproj,r,proj) -c - endif -c -c - if(l .ge. n2 .or. l .gt. m) then -c -c ID a directly. -c - mn = m*n - call idzr_copyzarr(mn,a,r) - call idzr_id(m,n,r,krank,list,w(20*m+81)) -c -c Retrieve proj from r. -c - lproj = krank*(n-krank) - call idzr_copyzarr(lproj,r,proj) -c - endif -c -c - return - end -c -c -c -c - subroutine idzr_copyzarr(n,a,b) -c -c copies a into b. -c -c input: -c n -- length of a and b -c a -- array to copy into b -c -c output: -c b -- copy of a -c - implicit none - integer n,k - complex*16 a(n),b(n) -c -c - do k = 1,n - b(k) = a(k) - enddo ! k -c -c - return - end -c -c -c -c - subroutine idzr_aidi(m,n,krank,w) -c -c initializes the array w for using routine idzr_aid. -c -c input: -c m -- number of rows in the matrix to be ID'd -c n -- number of columns in the matrix to be ID'd -c krank -- rank of the ID to be constructed -c -c output: -c w -- initialization array for using routine idzr_aid -c - implicit none - integer m,n,krank,l,n2 - complex*16 w((2*krank+17)*n+21*m+80) -c -c -c Set the number of random test vectors to 8 more than the rank. -c - l = krank+8 - w(1) = l -c -c -c Initialize the rest of the array w. -c - n2 = 0 - if(l .le. m) call idz_sfrmi(l,m,n2,w(11)) - w(2) = n2 -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idzr_asvd.f b/scipy/linalg/src/id_dist/src/idzr_asvd.f deleted file mode 100644 index 55ad61203d77..000000000000 --- a/scipy/linalg/src/id_dist/src/idzr_asvd.f +++ /dev/null @@ -1,118 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idzr_aid computes the SVD, to a specified rank, -c of an arbitrary matrix. This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idzr_asvd(m,n,a,krank,w,u,v,s,ier) -c -c constructs a rank-krank SVD u diag(s) v^* approximating a, -c where u is an m x krank matrix whose columns are orthonormal, -c v is an n x krank matrix whose columns are orthonormal, -c and diag(s) is a diagonal krank x krank matrix whose entries -c are all nonnegative. This routine uses a randomized algorithm. -c -c input: -c m -- number of rows in a -c n -- number of columns in a -c a -- matrix to be decomposed; the present routine does not -c alter a -c krank -- rank of the SVD being constructed -c w -- initialization array that routine idzr_aidi -c has constructed (for use in the present routine, -c w must be at least -c (2*krank+22)*m+(6*krank+21)*n+8*krank**2+10*krank+90 -c complex*16 elements long) -c -c output: -c u -- matrix of orthonormal left singular vectors of a -c v -- matrix of orthonormal right singular vectors of a -c s -- array of singular values of a -c ier -- 0 when the routine terminates successfully; -c nonzero otherwise -c -c _N.B._: The algorithm used by this routine is randomized. -c - implicit none - integer m,n,krank,lw,ilist,llist,iproj,lproj,icol,lcol, - 1 iwork,lwork,iwinit,lwinit,ier - real*8 s(krank) - complex*16 a(m,n),u(m,krank),v(n,krank), - 1 w((2*krank+22)*m+(6*krank+21)*n+8*krank**2 - 2 +10*krank+90) -c -c -c Allocate memory in w. -c - lw = 0 -c - iwinit = lw+1 - lwinit = (2*krank+17)*n+21*m+80 - lw = lw+lwinit -c - ilist = lw+1 - llist = n - lw = lw+llist -c - iproj = lw+1 - lproj = krank*(n-krank) - lw = lw+lproj -c - icol = lw+1 - lcol = m*krank - lw = lw+lcol -c - iwork = lw+1 - lwork = (krank+1)*(m+3*n+10)+9*krank**2 - lw = lw+lwork -c -c - call idzr_asvd0(m,n,a,krank,w(iwinit),u,v,s,ier, - 1 w(ilist),w(iproj),w(icol),w(iwork)) -c -c - return - end -c -c -c -c - subroutine idzr_asvd0(m,n,a,krank,winit,u,v,s,ier, - 1 list,proj,col,work) -c -c routine idzr_asvd serves as a memory wrapper -c for the present routine (please see routine idzr_asvd -c for further documentation). -c - implicit none - integer m,n,krank,list(n),ier - real*8 s(krank) - complex*16 a(m,n),u(m,krank),v(n,krank), - 1 proj(krank,n-krank),col(m*krank), - 2 winit((2*krank+17)*n+21*m+80), - 3 work((krank+1)*(m+3*n+10)+9*krank**2) -c -c -c ID a. -c - call idzr_aid(m,n,a,krank,winit,list,proj) -c -c -c Collect together the columns of a indexed by list into col. -c - call idz_copycols(m,n,a,krank,list,col) -c -c -c Convert the ID to an SVD. -c - call idz_id2svd(m,krank,col,n,list,proj,u,v,s,ier,work) -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idzr_rid.f b/scipy/linalg/src/id_dist/src/idzr_rid.f deleted file mode 100644 index cf8fcaacfa2a..000000000000 --- a/scipy/linalg/src/id_dist/src/idzr_rid.f +++ /dev/null @@ -1,156 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idzr_rid computes the ID, to a specified rank, -c of a matrix specified by a routine for applying its adjoint -c to arbitrary vectors. This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idzr_rid(m,n,matveca,p1,p2,p3,p4,krank,list,proj) -c -c computes the ID of a matrix "a" specified by -c the routine matveca -- matveca must apply the adjoint -c of the matrix being ID'd to an arbitrary vector -- -c i.e., the present routine lists in list the indices -c of krank columns of a such that -c -c a(j,list(k)) = a(j,list(k)) -c -c for all j = 1, ..., m; k = 1, ..., krank, and -c -c min(m,n,krank) -c a(j,list(k)) = Sigma a(j,list(l)) * proj(l,k-krank)(*) -c l=1 -c -c + epsilon(j,k-krank) -c -c for all j = 1, ..., m; k = krank+1, ..., n, -c -c for some matrix epsilon, dimensioned epsilon(m,n-krank), -c whose norm is (hopefully) minimized by the pivoting procedure. -c -c input: -c m -- number of rows in the matrix to be ID'd -c n -- number of columns in the matrix to be ID'd -c matveca -- routine which applies the adjoint -c of the matrix to be ID'd to an arbitrary vector; -c this routine must have a calling sequence -c of the form -c -c matveca(m,x,n,y,p1,p2,p3,p4), -c -c where m is the length of x, -c x is the vector to which the adjoint -c of the matrix is to be applied, -c n is the length of y, -c y is the product of the adjoint of the matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matveca -c p2 -- parameter to be passed to routine matveca -c p3 -- parameter to be passed to routine matveca -c p4 -- parameter to be passed to routine matveca -c krank -- rank of the ID to be constructed -c -c output: -c list -- indices of the columns in the ID -c proj -- matrix of coefficients needed to interpolate -c from the selected columns to the other columns -c in the original matrix being ID'd; -c proj doubles as a work array in the present routine, so -c proj must be at least m+(krank+3)*n complex*16 elements -c long -c -c _N.B._: The algorithm used by this routine is randomized. -c proj must be at least m+(krank+3)*n complex*16 elements -c long. -c -c reference: -c Halko, Martinsson, Tropp, "Finding structure with randomness: -c probabilistic algorithms for constructing approximate -c matrix decompositions," SIAM Review, 53 (2): 217-288, -c 2011. -c - implicit none - integer m,n,krank,list(n),lw,ix,lx,iy,ly,ir,lr - complex*16 p1,p2,p3,p4,proj(m+(krank+3)*n) - external matveca -c -c -c Allocate memory in w. -c - lw = 0 -c - ir = lw+1 - lr = (krank+2)*n - lw = lw+lr -c - ix = lw+1 - lx = m - lw = lw+lx -c - iy = lw+1 - ly = n - lw = lw+ly -c -c - call idzr_ridall0(m,n,matveca,p1,p2,p3,p4,krank, - 1 list,proj(ir),proj(ix),proj(iy)) -c -c - return - end -c -c -c -c - subroutine idzr_ridall0(m,n,matveca,p1,p2,p3,p4,krank, - 1 list,r,x,y) -c -c routine idzr_ridall serves as a memory wrapper -c for the present routine -c (see idzr_ridall for further documentation). -c - implicit none - integer j,k,l,m,n,krank,list(n),m2 - complex*16 x(m),y(n),p1,p2,p3,p4,r(krank+2,n) - external matveca -c -c -c Set the number of random test vectors to 2 more than the rank. -c - l = krank+2 -c -c Apply the adjoint of the original matrix to l random vectors. -c - do j = 1,l -c -c Generate a random vector. -c - m2 = m*2 - call id_srand(m2,x) -c -c Apply the adjoint of the matrix to x, obtaining y. -c - call matveca(m,x,n,y,p1,p2,p3,p4) -c -c Copy the conjugate of y into row j of r. -c - do k = 1,n - r(j,k) = conjg(y(k)) - enddo ! k -c - enddo ! j -c -c -c ID r. -c - call idzr_id(l,n,r,krank,list,y) -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/idzr_rsvd.f b/scipy/linalg/src/id_dist/src/idzr_rsvd.f deleted file mode 100644 index d788e219b137..000000000000 --- a/scipy/linalg/src/id_dist/src/idzr_rsvd.f +++ /dev/null @@ -1,159 +0,0 @@ -c this file contains the following user-callable routines: -c -c -c routine idzr_rsvd computes the SVD, to a specified rank, -c of a matrix specified by routines for applying the matrix -c and its adjoint to arbitrary vectors. -c This routine is randomized. -c -c -ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -c -c -c -c - subroutine idzr_rsvd(m,n,matveca,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,u,v,s,ier,w) -c -c constructs a rank-krank SVD u diag(s) v^* approximating a, -c where matveca is a routine which applies a^* -c to an arbitrary vector, and matvec is a routine -c which applies a to an arbitrary vector; -c u is an m x krank matrix whose columns are orthonormal, -c v is an n x krank matrix whose columns are orthonormal, -c and diag(s) is a diagonal krank x krank matrix whose entries -c are all nonnegative. This routine uses a randomized algorithm. -c -c input: -c m -- number of rows in a -c n -- number of columns in a -c matveca -- routine which applies the adjoint -c of the matrix to be SVD'd -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matveca(m,x,n,y,p1t,p2t,p3t,p4t), -c -c where m is the length of x, -c x is the vector to which the adjoint -c of the matrix is to be applied, -c n is the length of y, -c y is the product of the adjoint of the matrix and x, -c and p1t, p2t, p3t, and p4t are user-specified -c parameters -c p1t -- parameter to be passed to routine matveca -c p2t -- parameter to be passed to routine matveca -c p3t -- parameter to be passed to routine matveca -c p4t -- parameter to be passed to routine matveca -c matvec -- routine which applies the matrix to be SVD'd -c to an arbitrary vector; this routine must have -c a calling sequence of the form -c -c matvec(n,x,m,y,p1,p2,p3,p4), -c -c where n is the length of x, -c x is the vector to which the matrix is to be applied, -c m is the length of y, -c y is the product of the matrix and x, -c and p1, p2, p3, and p4 are user-specified parameters -c p1 -- parameter to be passed to routine matvec -c p2 -- parameter to be passed to routine matvec -c p3 -- parameter to be passed to routine matvec -c p4 -- parameter to be passed to routine matvec -c krank -- rank of the SVD being constructed -c -c output: -c u -- matrix of orthonormal left singular vectors of a -c v -- matrix of orthonormal right singular vectors of a -c s -- array of singular values of a -c ier -- 0 when the routine terminates successfully; -c nonzero otherwise -c -c work: -c w -- must be at least (krank+1)*(2*m+4*n+10)+8*krank**2 -c complex*16 elements long -c -c _N.B._: The algorithm used by this routine is randomized. -c - implicit none - integer m,n,krank,lw,ilist,llist,iproj,lproj,icol,lcol, - 1 iwork,lwork,ier - real*8 s(krank) - complex*16 p1t,p2t,p3t,p4t,p1,p2,p3,p4,u(m,krank),v(n,krank), - 1 w((krank+1)*(2*m+4*n+10)+8*krank**2) - external matveca,matvec -c -c -c Allocate memory in w. -c - lw = 0 -c - ilist = lw+1 - llist = n - lw = lw+llist -c - iproj = lw+1 - lproj = krank*(n-krank) - lw = lw+lproj -c - icol = lw+1 - lcol = m*krank - lw = lw+lcol -c - iwork = lw+1 - lwork = (krank+1)*(m+3*n+10)+9*krank**2 - lw = lw+lwork -c -c - call idzr_rsvd0(m,n,matveca,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,u,v,s,ier, - 2 w(ilist),w(iproj),w(icol),w(iwork)) -c -c - return - end -c -c -c -c - subroutine idzr_rsvd0(m,n,matveca,p1t,p2t,p3t,p4t, - 1 matvec,p1,p2,p3,p4,krank,u,v,s,ier, - 2 list,proj,col,work) -c -c routine idzr_rsvd serves as a memory wrapper -c for the present routine (please see routine idzr_rsvd -c for further documentation). -c - implicit none - integer m,n,krank,list(n),ier,k - real*8 s(krank) - complex*16 p1t,p2t,p3t,p4t,p1,p2,p3,p4,u(m,krank),v(n,krank), - 1 proj(krank*(n-krank)),col(m*krank), - 2 work((krank+1)*(m+3*n+10)+9*krank**2) - external matveca,matvec -c -c -c ID a. -c - call idzr_rid(m,n,matveca,p1t,p2t,p3t,p4t,krank,list,work) -c -c -c Retrieve proj from work. -c - do k = 1,krank*(n-krank) - proj(k) = work(k) - enddo ! k -c -c -c Collect together the columns of a indexed by list into col. -c - call idz_getcols(m,n,matvec,p1,p2,p3,p4,krank,list,col,work) -c -c -c Convert the ID to an SVD. -c - call idz_id2svd(m,krank,col,n,list,proj,u,v,s,ier,work) -c -c - return - end diff --git a/scipy/linalg/src/id_dist/src/prini.f b/scipy/linalg/src/id_dist/src/prini.f deleted file mode 100644 index 679590d84259..000000000000 --- a/scipy/linalg/src/id_dist/src/prini.f +++ /dev/null @@ -1,113 +0,0 @@ -C -C -C -C - SUBROUTINE PRINI(IP1,IQ1) - save - CHARACTER *1 MES(1), AA(1) - REAL *4 A(1) - REAL *8 A2(1) - REAL *8 A4(1) - INTEGER *4 IA(1) - INTEGER *2 IA2(1) - IP=IP1 - IQ=IQ1 - - RETURN - -C -C -C -C -C - ENTRY PRIN(MES,A,N) - CALL MESSPR(MES,IP,IQ) - IF(IP.NE.0 .AND. N.NE.0) WRITE(IP,1200)(A(J),J=1,N) - IF(IQ.NE.0 .AND. N.NE.0) WRITE(IQ,1200)(A(J),J=1,N) - 1200 FORMAT(6(2X,E11.5)) - RETURN -C -C -C -C - ENTRY PRIN2(MES,A2,N) - CALL MESSPR(MES,IP,IQ) - IF(IP.NE.0 .AND. N.NE.0) WRITE(IP,1400)(A2(J),J=1,N) - IF(IQ.NE.0 .AND. N.NE.0) WRITE(IQ,1400)(A2(J),J=1,N) - 1400 FORMAT(6(2X,E11.5)) - RETURN -C -C -C -C - ENTRY PRIN2_long(MES,A2,N) - CALL MESSPR(MES,IP,IQ) - IF(IP.NE.0 .AND. N.NE.0) WRITE(IP,1450)(A2(J),J=1,N) - IF(IQ.NE.0 .AND. N.NE.0) WRITE(IQ,1450)(A2(J),J=1,N) - 1450 FORMAT(2(2X,E22.16)) - RETURN -C -C -C -C - ENTRY PRINQ(MES,A4,N) - CALL MESSPR(MES,IP,IQ) - IF(IP.NE.0 .AND. N.NE.0) WRITE(IP,1500)(A4(J),J=1,N) - IF(IQ.NE.0 .AND. N.NE.0) WRITE(IQ,1500)(A4(J),J=1,N) - 1500 FORMAT(6(2X,e11.5)) - RETURN -C -C -C -C - ENTRY PRINF(MES,IA,N) - CALL MESSPR(MES,IP,IQ) - IF(IP.NE.0 .AND. N.NE.0) WRITE(IP,1600)(IA(J),J=1,N) - IF(IQ.NE.0 .AND. N.NE.0) WRITE(IQ,1600)(IA(J),J=1,N) - 1600 FORMAT(10(1X,I7)) - RETURN -C -C -C -C - ENTRY PRINF2(MES,IA2,N) - CALL MESSPR(MES,IP,IQ) - IF(IP.NE.0 .AND. N.NE.0) WRITE(IP,1600)(IA2(J),J=1,N) - IF(IQ.NE.0 .AND. N.NE.0) WRITE(IQ,1600)(IA2(J),J=1,N) - RETURN -C -C -C -C - ENTRY PRINA(MES,AA,N) - CALL MESSPR(MES,IP,IQ) - 2000 FORMAT(1X,80A1) - IF(IP.NE.0 .AND. N.NE.0) WRITE(IP,2000)(AA(J),J=1,N) - IF(IQ.NE.0 .AND. N.NE.0) WRITE(IQ,2000)(AA(J),J=1,N) - RETURN - END -c -c -c -c -c - SUBROUTINE MESSPR(MES,IP,IQ) - save - CHARACTER *1 MES(1),AST - DATA AST/'*'/ -C -C DETERMINE THE LENGTH OF THE MESSAGE -C - I1=0 - DO 1400 I=1,10000 - IF(MES(I).EQ.AST) GOTO 1600 - I1=I - 1400 CONTINUE - 1600 CONTINUE - IF ( (I1.NE.0) .AND. (IP.NE.0) ) - 1 WRITE(IP,1800) (MES(I),I=1,I1) - IF ( (I1.NE.0) .AND. (IQ.NE.0) ) - 1 WRITE(IQ,1800) (MES(I),I=1,I1) - 1800 FORMAT(1X,80A1) - RETURN - END From cb6d2af3905cd6939c33e3f195997b7adf19e11e Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Tue, 23 Apr 2024 09:27:13 +0200 Subject: [PATCH 405/500] ENH:linalg:Translate id_dist F77 code to Cython MAINT:linalg: Convert double to numpy types MAINT:linalg: Fix linting and a typo in interpolative code DOC:linalg: Remove non-compliant dash character --- scipy/linalg/_decomp_interpolative.pyx | 1992 ++++++++++++++++++++++++ scipy/linalg/interpolative.py | 2 +- 2 files changed, 1993 insertions(+), 1 deletion(-) create mode 100644 scipy/linalg/_decomp_interpolative.pyx diff --git a/scipy/linalg/_decomp_interpolative.pyx b/scipy/linalg/_decomp_interpolative.pyx new file mode 100644 index 000000000000..e1a5b2a62302 --- /dev/null +++ b/scipy/linalg/_decomp_interpolative.pyx @@ -0,0 +1,1992 @@ +# cython: boundscheck=False +# cython: initializedcheck=False +# cython: wraparound=False +# cython: cdivision=True +# cython: cpow=True + +""" +This file is a Cython rewrite of the original Fortran code of "ID: A software package +for low-rank approximation of matrices via interpolative decompositions, Version 0.4", +written by Per-Gunnar Martinsson, Vladimir Rokhlin, Yoel Shkolnisky, and Mark Tygert. + +The original Fortran code can be found at the last author's current website +http://tygert.com/software.html + + +References +---------- + +N. Halko, P.G. Martinsson, and J. A. Tropp, "Finding structure with randomness: +probabilistic algorithms for constructing approximate matrix decompositions", +SIAM Review, 53 (2011), pp. 217-288. DOI:10.1137/090771806 + +H. Cheng, Z. Gimbutas, P.G. Martinsson, V.Rokhlin, "On the Compression of Low +Rank Matrices", SIAM Journal of Scientific Computing, 2005, Vol.26(4), +DOI:10.1137/030602678 + + + +Copyright (C) 2024 SciPy developers + +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. Names of the SciPy Developers 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 HOLDERS 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. + + +Notes +----- + +The translated functions from the original Fortran77 code are as follows (with various +internal functions subsumed into respective functions): + + idd_diffsnorm + idd_estrank + idd_findrank + idd_id2svd + idd_ldiv + idd_poweroftwo + idd_reconid + idd_snorm + iddp_aid + iddp_asvd + iddp_id + iddp_qrpiv + iddp_rid + iddp_rsvd + iddp_svd + iddr_aid + iddr_asvd + iddr_id + iddr_qrpiv + iddr_rid + iddr_rsvd + iddr_svd + idz_diffsnorm + idz_estrank + idz_findrank + idz_id2svd + idz_reconid + idz_snorm + idzp_aid + idzp_asvd + idzp_id + idzp_qrpiv + idzp_rid + idzp_rsvd + idzp_svd + idzr_aid + idzr_asvd + idzr_id + idzr_rid + idzr_rsvd + idzr_qrpiv + idzr_svd + +""" + +import numpy as np +from numpy.typing import NDArray +cimport numpy as cnp +cnp.import_array() + +from cpython.mem cimport PyMem_Free, PyMem_Malloc, PyMem_Realloc +from libc.math cimport hypot + +import scipy.linalg as la +from scipy.fft import rfft, fft +from scipy.sparse.linalg import LinearOperator + +from scipy.linalg.cython_lapack cimport dlarfgp, dorm2r, zunm2r, zlarfgp +from scipy.linalg.cython_blas cimport dnrm2, dtrsm, dznrm2, ztrsm + + +__all__ = ['idd_estrank', 'idd_ldiv', 'idd_poweroftwo', 'idd_reconid', 'iddp_aid', + 'iddp_asvd', 'iddp_id', 'iddp_qrpiv', 'iddp_svd', 'iddr_aid', 'iddr_asvd', + 'iddr_id', 'iddr_qrpiv', 'iddr_svd', 'idz_estrank', 'idz_reconid', + 'idzp_aid', 'idzp_asvd', 'idzp_id', 'idzp_qrpiv', 'idzp_svd', 'idzr_aid', + 'idzr_asvd', 'idzr_id', 'idzr_qrpiv', 'idzr_svd', 'idd_id2svd', 'idz_id2svd' + # LinearOperator funcs + 'idd_findrank', 'iddp_rid', 'iddp_rsvd', 'iddr_rid', 'iddr_rsvd', + 'idz_findrank', 'idzp_rid', 'idzp_rsvd', 'idzr_rid', 'idzr_rsvd', + 'idd_snorm', 'idz_snorm', 'idd_diffsnorm', 'idz_diffsnorm' + ] + + +def idd_diffsnorm(A: LinearOperator, B: LinearOperator, int its=20, rng=None): + cdef int n = A.shape[1], j = 0, intone = 1 + cdef cnp.float64_t snorm = 0.0 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] v1 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] v2 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] u1 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] u2 + + if not rng: + rng = np.random.default_rng() + v1 = rng.uniform(low=-1., high=1., size=n) + v1 /= dnrm2(&n, &v1[0], &intone) + + for j in range(its): + u1 = A.matvec(v1) + u2 = B.matvec(v1) + u1 -= u2 + v1 = A.rmatvec(u1) + v2 = B.rmatvec(u1) + v1 -= v2 + + snorm = dnrm2(&n, &v1[0], &intone) + if snorm > 0.0: + v1 /= snorm + + snorm = np.sqrt(snorm) + + return snorm + + +def idd_estrank(cnp.ndarray[cnp.float64_t, mode="c", ndim=2] a: NDArray, eps: float, + rng=None): + cdef int m = a.shape[0], n = a.shape[1] + cdef int intone = 1, n2, nsteps = 3, row, r, nstep, cols, k, nulls + cdef cnp.float64_t h, alpha, beta + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=3] albetas + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] tau_arr + cdef cnp.ndarray[cnp.int64_t, mode='c', ndim=1] subselect + cdef cnp.float64_t *aa + cdef cnp.float64_t *ff + cdef cnp.float64_t[:, ::1] Fmemview + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] giv2x2 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] rta + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] Fc + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] F + + if not rng: + rng = np.random.default_rng() + + n2 = idd_poweroftwo(m) + + # This part is the initialization that is done via idd_frmi + # for a Subsampled Randomized Fourier Transfmrom (SRFT). + + # Draw (nsteps x m x 2) arrays from [-1, 1) uniformly and scale + # each 2-element row to unity norm + albetas = rng.uniform(low=-1.0, high=1.0, size=[nsteps, m, 2]) + aa = cnp.PyArray_DATA(albetas) + # Walk over every 2D row and normalize + for r in range(0, 2*nsteps*m, 2): + h = 1/hypot(aa[r], aa[r+1]) + aa[r] *= h + aa[r+1] *= h + + # idd_random_transf + rta = a.copy() + + # Rotate and shuffle "a" nsteps-many times + giv2x2 = cnp.PyArray_ZEROS(2, [2, 2], cnp.NPY_FLOAT64, 0) + for nstep in range(nsteps): + for row in range(m-1): + alpha, beta = albetas[nstep, row, 0], albetas[nstep, row, 1] + giv2x2[0, 0] = alpha + giv2x2[0, 1] = beta + giv2x2[1, 0] = -beta + giv2x2[1, 1] = alpha + np.matmul(giv2x2, rta[row:row+2, :], out=rta[row:row+2, :]) + + rta = rta[rng.permutation(m), :] + + # idd_subselect pick randomly n2-many rows + subselect = rng.choice(m, n2, replace=False) + rta = rta[subselect, :] + + # Perform rfft on each column. Note that the first and the last + # element of the result is real valued (n2 is power of 2). + # + # We view the complex valued entries as two consecutive doubles + # (by also removing the 2nd and last all-0 rows -- see idd_frm). + # Then after transpose we do a final row shuffle after transpose. + Fc = rfft(rta.T, axis=1) + # Move the first col to second col + Fc[:, 0] *= 1.j + # Perform the final permutation + F = Fc.view(np.float64)[:, 1:-1].T[rng.permutation(n2), :] + + Fcopy = F.copy() + cols = F.shape[1] + row = F.shape[0] + sssmax = 0. + ff = cnp.PyArray_DATA(F) + for r in range(cols): + h = dnrm2(&row, &ff[r], &cols) + if h > sssmax: + sssmax = h + + tau_arr = cnp.PyArray_ZEROS(1, [cols], cnp.NPY_FLOAT64, 0) + k, nulls = 0, 0 + + # In Fortran id_dist, F is transposed and works on the columns + # Since we have a C-array we work directly on rows + # The reflectors are overwritten on rows of F directly + # Hence at any k'th step, we have + # + # [ B r r r r r r r ] + # [ .... ] + # [ .... ] + # [ x x x B r r r r ] + # [ x x x x B r r r ] + # [ x x x x x B r r ] + # [ x x x x x x x x ] + # [ x x x x x x x x ] + # + + # Loop until nulls = 7, or krank+nulls = n2, or krank+nulls = n. + Fmemview = F + while (nulls < 7) and (k+nulls < min(n, n2)): + # Apply previous Householder reflectors + if k > 0: + for kk in range(k): + F[k, kk:] -= tau_arr[kk]*(F[kk, kk:] @ F[k, kk:])*F[kk, kk:] + + # Get the next Householder reflector and store in F + r = cols-k + # n, alpha, x, incx, tau + dlarfgp(&r, &Fmemview[k, k], &Fmemview[k, k+1], &intone, &tau_arr[k]) + beta = F[k, k] + F[k, k] = 1 + + if (beta <= eps*sssmax): + nulls += 1 + k += 1 + + if nulls < 7: + k = 0 + + return k, Fcopy + + +def idd_findrank(A: LinearOperator, cnp.float64_t eps, rng=None): + # Estimate the rank of A by repeatedly using A.rmatvec(random vec) + + cdef int m = A.shape[0], n = A.shape[1], k = 0, kk = 0,r = n, krank + cdef int no_of_cols = 4, intone = 1, info = 0 + cdef cnp.float64_t[::1] tau = cnp.PyArray_ZEROS(1, [min(m, n)], cnp.NPY_FLOAT64, 0) + cdef cnp.float64_t[::1] y = cnp.PyArray_ZEROS(1, [n], cnp.NPY_FLOAT64, 0) + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] retarr + + # The size of the QR decomposition is rank dependent which is unknown + # at runtime. Hence we don't want to allocate a dense version of the + # linear operator which can be too big. Instead, a typical "realloc double + # if run out of space" strategy is used here. Starts with 4*n + # Also, we hold the A.T @ x results in a separate array to return + # and do the same for that too. + cdef cnp.float64_t *ra = PyMem_Malloc( + sizeof(cnp.float64_t)*no_of_cols*n + ) + cdef cnp.float64_t *reallocated_ra + cdef cnp.float64_t *ret = PyMem_Malloc( + sizeof(cnp.float64_t)*no_of_cols*n + ) + cdef cnp.float64_t *reallocated_ret + cdef cnp.float64_t enorm = 0.0 + + if (not ra) or (not ret): + raise MemoryError("Failed to allocate at least required memory " + f"{no_of_cols*n*8} bytes for" + "'scipy.linalg.interpolative.idd_findrank()' " + "function.") + + if not rng: + rng = np.random.default_rng() + + krank = 0 + try: + while True: + + # Generate random vector and rmatvec then save the result + x = rng.uniform(size=m) + y = A.rmatvec(x) + for kk in range(n): + ret[krank*n + kk] = y[kk] + + if krank == 0: + enorm = dnrm2(&n, &y[0], &intone) + else: # krank > 0 + # Transpose-Apply previous Householder reflectors, if any + # SIDE, TRANS, M, N, K, A, LDA, TAU, C, LDC, WORK, INFO + dorm2r('L','T', &n, &intone, &krank, &ra[0], &n, + &tau[0], &y[0], &n, &ra[(no_of_cols-1)*n], &info) + + # Get the next Householder reflector + r = n-krank + # N, ALPHA, X, INCX, TAU + dlarfgp(&r, &y[krank], &y[krank+1], &intone, &tau[krank]) + + for kk in range(n): + ra[krank*n + kk] = y[kk] + + # Running out of space; try to double the size of ra + if krank == (no_of_cols-2): + reallocated_ra = PyMem_Realloc( + ra, sizeof(cnp.float64_t)*no_of_cols*n*2) + reallocated_ret = PyMem_Realloc( + ret, sizeof(cnp.float64_t)*no_of_cols*n*2) + + if reallocated_ra and reallocated_ret: + ra = reallocated_ra + ret = reallocated_ret + no_of_cols *= 2 + else: + raise MemoryError( + "'scipy.linalg.interpolative.idd_findrank()' failed to " + f"allocate the required memory,{no_of_cols*n*16} bytes " + "while trying to determine the rank (currently " + f"{krank}) of a LinearOperator with precision {eps}." + ) + krank += 1 + if (y[krank-1] < eps*enorm) or (krank >= min(m, n)): + break + finally: + # Crashed or successfully ended up here + # Discard Householder vectors + PyMem_Free(ra) + retarr = cnp.PyArray_EMPTY(2, [krank, n], cnp.NPY_FLOAT64, 0) + for k in range(krank): + for kk in range(n): + retarr[k, kk] = ret[k*n+kk] + PyMem_Free(ret) + + return krank, retarr + + +def idd_id2svd( + cnp.ndarray[cnp.float64_t, mode='c', ndim=2] cols, + cnp.ndarray[cnp.int64_t, mode='c', ndim=1] perms, + cnp.ndarray[cnp.float64_t, ndim=2] proj, + ): + cdef int m = cols.shape[0], krank = cols.shape[1] + cdef int n = proj.shape[1] + krank, info, ci + cdef cnp.ndarray[cnp.float64_t, mode='fortran', ndim=2] C + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] tau1 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] tau2 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] UU + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] S + cdef cnp.ndarray[cnp.float64_t, ndim=2] V + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] VV + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds1 + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds2 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] p + + UU = cnp.PyArray_ZEROS(2, [m, krank], cnp.NPY_FLOAT64, 0) + VV = cnp.PyArray_ZEROS(2, [n, krank], cnp.NPY_FLOAT64, 0) + p = cnp.PyArray_ZEROS(2, [krank, n], cnp.NPY_FLOAT64, 0) + + # idd_reconint + for ci in range(krank): + p[ci, perms[ci]] = 1.0 + + p[:, perms[krank:]] = proj[:, :] + + inds1, tau1 = iddr_qrpiv(cols, krank) + # idd_rinqr and idd_rearr + r = np.triu(cols[:krank, :]) + for ci in range(krank-1, -1, -1): + r[:, [ci, inds1[ci]]] = r[:, [inds1[ci], ci]] + + t = p.T.copy() + inds2, tau2 = iddr_qrpiv(t, krank) + r2 = np.triu(t[:krank, :]) + for ci in range(krank-1, -1, -1): + r2[:, [ci, inds2[ci]]] = r2[:, [inds2[ci], ci]] + + r3 = r @ r2.T + UU[:krank, :krank], S, V = la.svd(r3, + full_matrices=False, + check_finite=False) + + # Apply Q of col to U from the left, use cols as scratch + C = cols[:, :krank].copy(order='F') + dorm2r('R', 'T', + &krank, &m, &krank, &C[0, 0], &m, &tau1[0], + &UU[0,0], &krank, &cols[0, 0], &info) + + VV[:krank, :krank] = V[:, :].T + # Apply Q of t to V from the left + C = t[:, :krank].copy(order='F') + dorm2r('R', 'T', + &krank, &n, &krank, &C[0, 0], &n, &tau2[0], + &VV[0, 0], &krank, &cols[0, 0], &info) + + return UU, S, VV + + +cdef inline int idd_ldiv(int l, int n) noexcept nogil: + cdef int m = l + while (n % m != 0): + m -= 1 + return m + + +cdef int idd_poweroftwo(int m) noexcept nogil: + """ + Find the integer solution to l = floor(log2(m)) + """ + cdef int n = 1 + while (n < m): + n <<= 1 # Times 2 + return n >> 1 # Divide by 2 + + +def idd_reconid(B, idx, proj): + cdef int m = B.shape[0], krank = B.shape[1] + cdef int n = len(idx) + approx = np.zeros([m, n], dtype=np.float64) + + approx[:, idx[:krank]] = B + approx[:, idx[krank:]] = B @ proj + + return approx + + +def idd_snorm(A: LinearOperator, int its=20, rng=None): + cdef int n = A.shape[1] + cdef int j = 0, intone = 1 + cdef cnp.float64_t snorm = 0.0 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] v + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] u + + if not rng: + rng = np.random.default_rng() + v = rng.uniform(low=-1., high=1., size=n) + v /= dnrm2(&n, &v[0], &intone) + + for j in range(its): + u = A.matvec(v) + v = A.rmatvec(u) + snorm = dnrm2(&n, &v[0], &intone) + if snorm > 0.0: + v /= snorm + + snorm = np.sqrt(snorm) + + return snorm + + +def iddp_aid(cnp.ndarray[cnp.float64_t, ndim=2] a: NDArray, eps: float, rng=None): + krank, proj = idd_estrank(a, eps, rng=rng) + if krank != 0: + proj = proj[:krank, :] + return iddp_id(proj, eps=eps) + + return iddp_id(a, eps=eps) + + +def iddp_asvd(cnp.ndarray[cnp.float64_t, ndim=2] a: NDArray, eps: float, rng=None): + cdef int m = a.shape[0], n = a.shape[1] + cdef int krank, info, ci + cdef cnp.ndarray[cnp.float64_t, mode='fortran', ndim=2] C + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] tau1 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] tau2 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] UU + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] S + cdef cnp.ndarray[cnp.float64_t, ndim=2] V + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] VV + cdef cnp.ndarray[cnp.float64_t, ndim=2] proj + cdef cnp.ndarray[cnp.npy_int64, ndim=1] perms + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds1 + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds2 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] p + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] col + + krank, perms, proj = iddp_aid(a.copy(), eps, rng=rng) + + if krank > 0: + UU = cnp.PyArray_ZEROS(2, [m, krank], cnp.NPY_FLOAT64, 0) + VV = cnp.PyArray_ZEROS(2, [n, krank], cnp.NPY_FLOAT64, 0) + + p = cnp.PyArray_ZEROS(2, [krank, n], cnp.NPY_FLOAT64, 0) + col = a[:, perms[:krank]].copy() + + # idd_reconint + for ci in range(krank): + p[ci, perms[ci]] = 1.0 + + # p[np.arange(krank), perms[:krank]] = 1. + p[:, perms[krank:]] = proj[:, :] + + inds1, tau1 = iddr_qrpiv(col, krank) + # idd_rinqr and idd_rearr + r = np.triu(col[:krank, :]) + for ci in range(krank-1, -1, -1): + r[:, [ci, inds1[ci]]] = r[:, [inds1[ci], ci]] + + t = p.T.copy() + inds2, tau2 = iddr_qrpiv(t, krank) + r2 = np.triu(t[:krank, :]) + for ci in range(krank-1, -1, -1): + r2[:, [ci, inds2[ci]]] = r2[:, [inds2[ci], ci]] + + r3 = r @ r2.T + UU[:krank, :krank], S, V = la.svd(r3, full_matrices=False) + + # Apply Q of col to U from the left + C = col[:, :krank].copy(order='F') + dorm2r('R', 'T', + &krank, &m, &krank, &C[0, 0], &m, &tau1[0], + &UU[0,0], &krank, &a[0, 0], &info) + + VV[:krank, :krank] = V[:, :].T + # Apply Q of t to V from the left + C = t[:, :krank].copy(order='F') + dorm2r('R', 'T', + &krank, &n, &krank, &C[0, 0], &n, &tau2[0], + &VV[0, 0], &krank, &a[0, 0], &info) + + return UU, S, VV + + +def iddp_id(cnp.ndarray[cnp.float64_t, ndim=2] a: NDArray, eps: float): + cdef int n = a.shape[1], krank, tmp_int, p + cdef cnp.float64_t one = 1 + krank, _, inds = iddp_qrpiv(a, eps) + + # Change pivots to permutation + perms = cnp.PyArray_ZEROS(1, [n], cnp.NPY_INT64, 0) + for p in range(n): + perms[p] = p + + if krank > 0: + for p in range(krank): + # Apply pivots + tmp_int = perms[p] + perms[p] = perms[inds[p]] + perms[inds[p]] = tmp_int + # perms[[p, inds[p]]] = perms[[inds[p], p]] + + # Let A = [A1, A2] and A1 has krank cols and upper triangular. + # Find X that satisfies A1 @ X = A2 + # In SciPy.linalg this amounts to; + # + # proj = la.solve_triangular(a[:krank, :krank], a[:krank, krank:], + # lower=False, check_finite=False) + # + # Push into BLAS without transposes. + # A1 = a[:krank, :krank] + # A2 = a[:krank, krank:] + # Instead solve X @ A1.T = A2.T + # Fortran already sees A1 as A1.T and becomes lower tri, side = R + + tmp_int = n - krank + # SIDE,UPLO,TRANSA,DIAG,M,N,ALPHA,A,LDA,B,LDB + dtrsm('R', 'L', 'N', 'N', + &tmp_int, &krank, &one, &a[0, 0], &n, &a[0, krank], &n) + + return krank, np.array(perms), a[:krank, krank:] + + +def iddp_qrpiv(cnp.ndarray[cnp.float64_t, mode="c", ndim=2] a, cnp.float64_t eps): + """ + This is a minimal version of ?GEQP3 from LAPACK with an + additional early stopping criterion over given precision. + + This function overwrites entries of "a" ! + """ + + cdef int m = a.shape[0], n = a.shape[1] + cdef cnp.ndarray col_norms = cnp.PyArray_ZEROS(1, [n], cnp.NPY_FLOAT64, 0) + cdef int k = 0, kpiv = 0, i = 0, tmp_int = 0, int_n = 0 + cdef cnp.float64_t tmp_sca = 0. + cdef cnp.ndarray taus = cnp.PyArray_ZEROS(1, [m], cnp.NPY_FLOAT64, 0) + cdef cnp.ndarray ind = cnp.PyArray_ZEROS(1, [n], cnp.NPY_INT64, 0) + cdef cnp.float64_t[::1] taus_v = taus + cdef cnp.float64_t feps = 0.1e-16 # np.finfo(np.float64).eps + cdef cnp.float64_t ssmax, ssmaxin + cdef int nupdate = 0 + + for i in range(n): + col_norms[i] = dnrm2(&m, &a[0, i], &n)**2 + + kpiv = np.argmax(col_norms) + ssmax = col_norms[kpiv] + ssmaxin = ssmax + + for k in range(min(m, n)): + + # Pivoting + ind[k] = kpiv + # Swap columns a[:, k] and a[:, kpiv] + a[:, [kpiv, k]] = a[:, [k, kpiv]] + + # Swap col_norms[krank] and col_norms[kpiv] + col_norms[[kpiv, k]] = col_norms[[k, kpiv]] + + if k < m-1: + # Compute the householder reflector for column k + tmp_sca = a[k, k] + # FIX: Convert these to F_INT + tmp_int = (m - k) + int_n = n + dlarfgp(&tmp_int, &tmp_sca, &a[k+1, k], &int_n, &taus_v[k]) + + # Overwrite with 1. for easy matmul + a[k, k] = 1 + if k < n-1: + # Apply the householder reflector to the rest on the right + a[k:, k+1:] -= np.outer(taus[k]*a[k:, k], a[k:, k] @ a[k:, k+1:]) + + # Put back the beta in place + a[k, k] = tmp_sca + + # Update the norms + col_norms[k] = 0 + col_norms[k+1:] -= a[k, k+1:]**2 + ssmax = 0 + kpiv = k+1 + if k < n-1: + kpiv = np.argmax(col_norms[k+1:]) + (k + 1) + ssmax = col_norms[kpiv] + + if (((ssmax < 1000*feps*ssmaxin) and (nupdate == 0)) or + ((ssmax < ((1000*feps)**2)*ssmaxin) and (nupdate == 1))): + nupdate += 1 + ssmax = 0 + kpiv = k+1 + + if k < n-1: + for i in range(k+1, n): + tmp_int = m-k-1 + col_norms[i] = dnrm2(&tmp_int, &a[k+1, i], &n)**2 + kpiv = np.argmax(col_norms[k+1:]) + (k + 1) + ssmax = col_norms[kpiv] + if (ssmax <= (eps**2)*ssmaxin): + break + # a is overwritten; return numerical rank and pivots + return k + 1, taus, ind + + +def iddp_rid(A: LinearOperator, cnp.float64_t eps, rng=None): + _, ret = idd_findrank(A, eps, rng) + return iddp_id(ret, eps) + + +def iddp_rsvd(A: LinearOperator, cnp.float64_t eps, rng=None): + cdef int n = A.shape[1] + cdef int krank, j + cdef cnp.ndarray[cnp.int64_t, mode='c', ndim=1] perms + cdef cnp.ndarray[cnp.float64_t, ndim=2] proj + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] col + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] x + + krank, perms, proj = iddp_rid(A, eps, rng) + if krank > 0: + # idd_getcols + col = cnp.PyArray_EMPTY(2, [n, krank], cnp.NPY_FLOAT64, 0) + x = cnp.PyArray_ZEROS(1, [n], cnp.NPY_FLOAT64, 0) + + for j in range(krank): + x[perms[j]] = 1. + col[:, j] = A.matvec(x) + x[perms[j]] = 0. + + return idd_id2svd(cols=col, perms=perms, proj=proj) + + # TODO: figure out empty return + return None + + +def iddp_svd(cnp.ndarray[cnp.float64_t, ndim=2] a: NDArray, eps: float): + """a is overwritten""" + cdef int m = a.shape[0], krank, info + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] taus + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] UU + cdef cnp.ndarray[cnp.float64_t, mode='fortran', ndim=2] C + + # Get the pivoted QR + krank, taus, inds = iddp_qrpiv(a, eps) + + if krank > 0: + r = np.triu(a[:krank, :]) + # Apply pivots in reverse + for p in range(krank-1, -1, -1): + r[:, [p, inds[p]]] = r[:, [inds[p], p]] + + # JOBU, JOBVT, M, N, A, LDA, S, U, LDU, VT, LDVT, WORK, LWORK, INFO + # dgesvd('S', 'O', &krank, &n) + U, S, V = la.svd(r, full_matrices=False) + + # Apply Q to U via dorm2r + # Possibly U is shorter than Q + UU = np.zeros([m, krank], dtype=a.dtype) + UU[:krank, :krank] = U + # Do the transpose dance for C-layout, use a for scratch + C = a[:, :krank].copy(order='F') + dorm2r('R', 'T', + &krank, &m, &krank, &C[0, 0], &m, &taus[0], + &UU[0,0], &krank, &a[0, 0], &info) + + return UU, S, V + + +def iddr_aid(cnp.ndarray[cnp.float64_t, mode="c", ndim=2] a: NDArray, int krank, + rng=None): + cdef int m = a.shape[0], n = a.shape[1], n2, nsteps = 3, row, r, nstep, L + cdef cnp.float64_t h, alpha, beta + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=3] albetas + cdef cnp.ndarray[cnp.npy_int64, mode='c', ndim=1] subselect + cdef cnp.float64_t *aa + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] giv2x2 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] rta + cdef cnp.ndarray[cnp.npy_int64, mode='c', ndim=1] marker + + if not rng: + rng = np.random.default_rng() + + # idd_aidi + L = krank + 8 + n2 = 0 + if (L >= n2) or (L > m): + inds, proj = iddr_id(a, krank) + return inds, proj + + n2 = idd_poweroftwo(m) + + # idd_sfrmi + # idd_pairsamps + ind = rng.permutation(n2) + ind2 = cnp.PyArray_ZEROS(1, [L], cnp.NPY_INT64, 0) + + marker = cnp.PyArray_ZEROS(1, [n2//2], cnp.NPY_INT64, 0) + for k in range(L): + marker[(ind[k]+1)//2] = marker[(ind[k]+1)//2]+1 + + for r in range(n2//2): + if marker[r] != 0: + l2 += 1 + ind2[r] = r + + # Draw (nsteps x m x 2) arrays from [-1, 1) uniformly and scale + # each 2-element row to unity norm + albetas = rng.uniform(low=-1.0, high=1.0, size=[nsteps, m, 2]) + aa = cnp.PyArray_DATA(albetas) + # Walk over every 2D row and normalize + for r in range(0, 2*nsteps*m, 2): + # ignoring the improbable zero generation by rng.uniform + h = 1.0/hypot(aa[r], aa[r+1]) + aa[r] *= h + aa[r+1] *= h + + # idd_random_transf + rta = a.copy() + + # Rotate and shuffle "a" nsteps-many times + giv2x2 = cnp.PyArray_ZEROS(2, [2, 2], cnp.NPY_FLOAT64, 0) + for nstep in range(nsteps): + for row in range(m-1): + alpha, beta = albetas[nstep, row, 0], albetas[nstep, row, 1] + giv2x2[0, 0] = alpha + giv2x2[0, 1] = beta + giv2x2[1, 0] = -beta + giv2x2[1, 1] = alpha + np.matmul(giv2x2, rta[row:row+2, :], out=rta[row:row+2, :]) + + rta = rta[rng.permutation(m), :] + + # idd_subselect pick randomly n2-many rows + subselect = rng.choice(m, n2, replace=False) + rta = rta[subselect, :] + + # idd_sffti + twopi = 2*np.pi + twopii = twopi*1.j + nblock = idd_ldiv(l2, n2) + fact = 1/np.sqrt(n2) + + if l2 == 1: + wsave = np.exp(-twopii*k*ind2[0]/np.arange(1, n2+1))*fact + else: + m = n2//nblock + + wsave = np.empty(m*l2, dtype=complex) + for j in range(l2): + i = ind2[j] + if (i+1) <= (n//2 - m//2): + idivm = i // m + imodm = i - m*idivm + for k in range(m): + wsave[m*j+k] = ( + np.exp(-twopii*(k)*imodm/m)* + np.exp(-twopii*(k)*(idivm+1)/n)* + fact + ) + else: + idivm = (i+1)//(m//2) + imodm = (i+1)-(m//2)*idivm + for k in range(m): + wsave[m*j+k] = np.exp(-twopii*(k-1)*imodm/m)*fact + + # idd_sfft.f + # There is some significant index olympics happening in the original Fortran code + # however I could not reverse engineer it to understand what is happening and kept + # as is with all its cryptic movements and their performance hits. + # See DOI:10.1016/j.acha.2007.12.002 - Section 3.3 + + # Perform partial FFT to each nblock + F = rfft(rta.reshape(nblock, m, -1), order='F', axis=0) + # Roll the first entry to the last in the first axis for + # the real frequency components. (faster than np.roll) + F = F[[x for x in range(1, F.shape[0])] + [0], :, :] + # Convert back to 2D array + F = F.reshape(F.shape[0]*F.shape[1], -1) + + csum = np.zeros_like(F[0, :]) + rsum = np.zeros_like(F[0, :]) + + for j in range(l2): + i = ind2[j] + if (i+1) <= (n//2 - m//2): + idivm = i // m + imodm = i - m*idivm + csum[:] = 0.0 + for k in range(m): + csum += F[m*idivm+k, :] * wsave[m*j+k] + rta[2*i, :] = csum.real + rta[2*i+1, :] = csum.imag + + else: + idivm = (i+1)//(m//2) + imodm = (i+1)-(m//2)*idivm + csum[:] = 0.0 + for k in range(m): + csum += F[m*(nblock//2)+k, :] * wsave[m*j+k] + rta[2*i, :] = csum.real + rta[2*i+1, :] = csum.imag + if i == (n//2) - 1: + for k in range(m): + rsum += F[m*(nblock//2)+k, :] + rta[n-2, :] = rsum + rta[n-2, :] *= fact + + rsum[:] = 0.0 + for k in range(m//2): + rsum += F[m*(nblock//2)+2*k-1] + rsum -= F[m*(nblock//2)+2*k] + rta[n-1, :] = rsum + rta[n-1, :] *= fact + + # idd_subselect pick randomly l2-many rows + subselect = rng.choice(n2, l2, replace=False) + rta = rta[subselect, :] + + perms, proj = iddr_id(rta, krank) + + return perms, proj + + +def iddr_asvd(cnp.ndarray[cnp.float64_t, mode="c", ndim=2] a: NDArray, int krank, + rng=None): + cdef int m = a.shape[0], n = a.shape[1] + cdef int info, ci + cdef cnp.ndarray[cnp.float64_t, mode='fortran', ndim=2] C + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] tau1 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] tau2 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] UU + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] S + cdef cnp.ndarray[cnp.float64_t, ndim=2] V + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] VV + cdef cnp.ndarray[cnp.float64_t, ndim=2] proj + cdef cnp.ndarray[cnp.npy_int64, ndim=1] perms + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds1 + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds2 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] p + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] col + + perms, proj = iddr_aid(a.copy(), krank=krank, rng=rng) + + UU = cnp.PyArray_ZEROS(2, [m, krank], cnp.NPY_FLOAT64, 0) + VV = cnp.PyArray_ZEROS(2, [n, krank], cnp.NPY_FLOAT64, 0) + + p = cnp.PyArray_ZEROS(2, [krank, n], cnp.NPY_FLOAT64, 0) + col = a[:, perms[:krank]].copy() + + # idd_reconint + for ci in range(krank): + p[ci, perms[ci]] = 1.0 + + p[:, perms[krank:]] = proj[:, :] + + inds1, tau1 = iddr_qrpiv(col, krank) + # idd_rinqr and idd_rearr + r = np.triu(col[:krank, :]) + for ci in range(krank-1, -1, -1): + r[:, [ci, inds1[ci]]] = r[:, [inds1[ci], ci]] + + t = p.T.copy() + inds2, tau2 = iddr_qrpiv(t, krank) + r2 = np.triu(t[:krank, :]) + for ci in range(krank-1, -1, -1): + r2[:, [ci, inds2[ci]]] = r2[:, [inds2[ci], ci]] + + r3 = r @ r2.T + UU[:krank, :krank], S, V = la.svd(r3, full_matrices=False) + + # Apply Q of col to U from the left + C = col[:, :krank].copy(order='F') + dorm2r('R', 'T', + &krank, &m, &krank, &C[0, 0], &m, &tau1[0], + &UU[0,0], &krank, &a[0, 0], &info) + + VV[:krank, :krank] = V[:, :].T + # Apply Q of t to V from the left + C = t[:, :krank].copy(order='F') + dorm2r('R', 'T', + &krank, &n, &krank, &C[0, 0], &n, &tau2[0], + &VV[0, 0], &krank, &a[0, 0], &info) + + return UU, S, VV + + +def iddr_id(cnp.ndarray[cnp.float64_t, ndim=2] a, int krank): + cdef int n = a.shape[1] + cdef int tmp_int + cdef cnp.float64_t one = 1.0 + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds + cdef cnp.ndarray[cnp.npy_int64, ndim=1] perms + + inds, _ = iddr_qrpiv(a, krank) + perms = cnp.PyArray_Arange(0, n, 1, cnp.NPY_INT64) + + if krank > 0: + for p in range(krank): + # Apply pivots + tmp_int = perms[p] + perms[p] = perms[inds[p]] + perms[inds[p]] = tmp_int + + # See iddp_id comments for below + tmp_int = n - krank + # SIDE,UPLO,TRANSA,DIAG,M,N,ALPHA,A,LDA,B,LDB + dtrsm('R', 'L', 'N', 'N', + &tmp_int, &krank, &one, &a[0, 0], &n, &a[0, krank], &n) + + return perms, a[:krank, krank:] + + +def iddr_qrpiv(cnp.ndarray[cnp.float64_t, mode="c", ndim=2] a: NDArray, krank: int): + cdef int m = a.shape[0], n = a.shape[1] + cdef cnp.ndarray col_norms = cnp.PyArray_ZEROS(1, [n], cnp.NPY_FLOAT64, 0) + cdef int loop = 0, loops, kpiv = 0, i = 0, tmp_int = 0, int_n = 0 + cdef cnp.float64_t tmp_sca = 0. + cdef cnp.ndarray taus = cnp.PyArray_ZEROS(1, [m], cnp.NPY_FLOAT64, 0) + cdef cnp.ndarray ind = cnp.PyArray_ZEROS(1, [n], cnp.NPY_INT64, 0) + cdef cnp.float64_t[::1] taus_v = taus + cdef cnp.float64_t feps = 0.1e-16 # np.finfo(np.float64).eps + cdef cnp.float64_t ssmax, ssmaxin + cdef int nupdate = 0 + + loops = min(krank, min(m, n)) + for i in range(n): + col_norms[i] = dnrm2(&m, &a[0, i], &n)**2 + + kpiv = np.argmax(col_norms) + ssmax = col_norms[kpiv] + ssmaxin = ssmax + + for loop in range(loops): + + ind[loop] = kpiv + # Swap columns a[:, k] and a[:, kpiv] + a[:, [kpiv, loop]] = a[:, [loop, kpiv]] + # Swap col_norms[krank] and col_norms[kpiv] + col_norms[[kpiv, loop]] = col_norms[[loop, kpiv]] + + if loop < m-1: + tmp_sca = a[loop, loop] + # FIX: Convert these to F_INT + tmp_int = (m - loop) + int_n = n + dlarfgp(&tmp_int, &tmp_sca, &a[loop+1, loop], &int_n, &taus_v[loop]) + + # Overwrite with 1. for easy matmul + a[loop, loop] = 1 + if loop < n-1: + # Apply the householder reflector to the rest on the right + a[loop:, loop+1:] -= np.outer(taus[loop]*a[loop:, loop], + a[loop:, loop] @ a[loop:, loop+1:]) + + # Put back the beta in place + a[loop, loop] = tmp_sca + + # Update the norms + col_norms[loop] = 0 + col_norms[loop+1:] -= a[loop, loop+1:]**2 + ssmax = 0 + kpiv = loop+1 + + if loop < n-1: + kpiv = np.argmax(col_norms[loop+1:]) + (loop + 1) + ssmax = col_norms[kpiv] + if (((ssmax < 1000*feps*ssmaxin) and (nupdate == 0)) or + ((ssmax < ((1000*feps)**2)*ssmaxin) and (nupdate == 1))): + nupdate += 1 + ssmax = 0 + kpiv = loop+1 + + if loop < n-1: + for i in range(loop+1, n): + tmp_int = m-loop-1 + col_norms[i] = dnrm2(&tmp_int, &a[loop+1, i], &n)**2 + kpiv = np.argmax(col_norms[loop+1:]) + (loop + 1) + ssmax = col_norms[kpiv] + + return ind, taus + + +def iddr_rid(A: LinearOperator, int krank, rng=None): + cdef int m = A.shape[0], n = A.shape[1], k = 0 + cdef int L = min(krank+2, min(m, n)) + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] r + + if not rng: + rng = np.random.default_rng() + + r = cnp.PyArray_EMPTY(2, [L, n], cnp.NPY_FLOAT64, 0) + for k in range(L): + r[k, :] = A.rmatvec(rng.uniform(size=m)) + + return iddr_id(a=r, krank=krank) + + +def iddr_rsvd(A: LinearOperator, int krank, rng=None): + cdef int n = A.shape[1], j + cdef cnp.ndarray[cnp.int64_t, mode='c', ndim=1] perms + cdef cnp.ndarray[cnp.float64_t, ndim=2] proj + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] col + + perms, proj = iddr_rid(A, krank, rng) + # idd_getcols + col = cnp.PyArray_EMPTY(2, [n, krank], cnp.NPY_FLOAT64, 0) + x = cnp.PyArray_ZEROS(1, [n], cnp.NPY_FLOAT64, 0) + for j in range(krank): + x[perms[j]] = 1. + col[:, j] = A.matvec(x) + x[perms[j]] = 0. + + return idd_id2svd(cols=col, perms=perms, proj=proj) + + +def iddr_svd(cnp.ndarray[cnp.float64_t, mode="c", ndim=2] a: NDArray, int krank): + cdef int m = a.shape[0], info = 0 + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] taus + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] UU + cdef cnp.ndarray[cnp.float64_t, mode='fortran', ndim=2] C + + # Get the pivoted QR + inds, taus = iddr_qrpiv(a, krank) + + r = np.triu(a[:krank, :]) + # Apply pivots in reverse + for p in range(krank-1, -1, -1): + r[:, [p, inds[p]]] = r[:, [inds[p], p]] + + # JOBU, JOBVT, M, N, A, LDA, S, U, LDU, VT, LDVT, WORK, LWORK, INFO + # dgesvd('S', 'O', &krank, &n) + U, S, V = la.svd(r, full_matrices=False) + + # Apply Q to U via dorm2r + # Possibly U is shorter than Q + UU = np.zeros([m, krank], dtype=a.dtype) + UU[:krank, :krank] = U + # Do the transpose dance for C-layout, use a for scratch + C = a[:, :krank].copy(order='F') + dorm2r('R', 'T', + &krank, &m, &krank, &C[0, 0], &m, &taus[0], + &UU[0,0], &krank, &a[0, 0], &info) + + return UU, S, V + + +def idz_diffsnorm(A: LinearOperator, B: LinearOperator, int its=20, rng=None): + cdef int n = A.shape[1], j = 0, intone = 1 + cdef cnp.float64_t snorm = 0.0 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] v1 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] v2 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] u1 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] u2 + + if not rng: + rng = np.random.default_rng() + v1 = rng.uniform(low=-1, high=1, size=(n, 2)).view(np.complex128).ravel() + v1 /= dznrm2(&n, &v1[0], &intone) + + for j in range(its): + u1 = A.matvec(v1) + u2 = B.matvec(v1) + u1 -= u2 + v1 = A.rmatvec(u1) + v2 = B.rmatvec(u1) + v1 -= v2 + + snorm = dznrm2(&n, &v1[0], &intone) + if snorm > 0.0: + v1 /= snorm + + snorm = np.sqrt(snorm) + + return snorm + + +def idz_estrank(cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] a: NDArray, eps: float, + rng=None): + cdef int m = a.shape[0], n = a.shape[1], n2, nsteps = 3, row, r, nstep, cols, k + cdef cnp.float64_t h, alpha, beta + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=3] albetas + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] tau_arr + cdef cnp.ndarray[cnp.npy_int64, mode='c', ndim=1] subselect + cdef double complex[:, ::1] ff + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=2] giv2x2 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] rta + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] F + + if not rng: + rng = np.random.default_rng() + + n2 = idd_poweroftwo(m) + # This part is the initialization that is done via idz_frmi + # for a Subsampled Randomized Fourier Transfmrom (SRFT). + + # Draw (nsteps x m x 4) array from [0, 2)*pi uniformly for + # random points on complex unit circle and unitary rotations + albetas = np.empty([nsteps, m, 4]) + albetas[:, :, 2:] = rng.uniform(low=0.0, high=2.0, size=[nsteps, m, 2]) + albetas[:, :, 2:] *= np.pi + np.cos(albetas[:, :, 2], out=albetas[:, :, 0]) + np.sin(albetas[:, :, 2], out=albetas[:, :, 1]) + np.cos(albetas[:, :, 3], out=albetas[:, :, 2]) + np.sin(albetas[:, :, 3], out=albetas[:, :, 3]) + + # idd_random_transf + rta = a.copy() + + # Rotate and shuffle "a" nsteps-many times + giv2x2 = cnp.PyArray_ZEROS(2, [2, 2], cnp.NPY_FLOAT64, 0) + for nstep in range(nsteps): + # Multiply with a point on the unit circle + rta *= albetas[nstep, :, 2:].view(np.complex128) + # Rotate + for row in range(m-1): + alpha, beta = albetas[nstep, row, 0], albetas[nstep, row, 1] + giv2x2[0, 0] = alpha + giv2x2[0, 1] = beta + giv2x2[1, 0] = -beta + giv2x2[1, 1] = alpha + np.matmul(giv2x2, rta[row:row+2, :], out=rta[row:row+2, :]) + + rta = rta[rng.permutation(m), :] + + # idd_subselect pick randomly n2-many rows + subselect = rng.choice(m, n2, replace=False) + rta = rta[subselect, :] + # Perform rfft on each column. + F = fft(rta, axis=0)[rng.permutation(n2), :] + + Fcopy = F.copy() + cols = F.shape[1] + row = F.shape[0] + sssmax = 0. + + for r in range(cols): + h = dznrm2(&row, &F[0, r], &cols) + if h > sssmax: + sssmax = h + + tau_arr = cnp.PyArray_ZEROS(1, [cols], cnp.NPY_COMPLEX128, 0) + k, nulls = 0, 0 + ff = F + # Loop until nulls = 7, or krank+nulls = n2, or krank+nulls = n. + while (nulls < 7) and (k+nulls < min(n, n2)): + # Apply previous Householder reflectors + if k > 0: + for kk in range(k): + F[k, kk:] -= ( + np.conj(tau_arr[kk])* + (F[kk, kk:].conj() @ F[k, kk:])* + F[kk, kk:] + ) + + # Get the next Householder reflector and store in F + r = cols-k + row = 1 + zlarfgp(&r, &ff[k, k], &ff[k, k+1], &row, &tau_arr[k]) + if (np.abs(F[k, k]) <= eps*sssmax): + nulls += 1 + F[k, k] = 1 + k += 1 + + if nulls < 7: + k = 0 + + return k, Fcopy + + +def idz_findrank(A: LinearOperator, cnp.float64_t eps, rng=None): + # Estimate the rank of A by repeatedly using A.rmatvec(random vec) + + cdef int m = A.shape[0], n = A.shape[1], k = 0, kk = 0,r = n, krank + cdef int no_of_cols = 4, intone = 1, info = 0 + cdef cnp.complex128_t[::1] tau = cnp.PyArray_ZEROS(1, [min(m, n)], + cnp.NPY_COMPLEX128, 0) + cdef cnp.complex128_t[::1] y = cnp.PyArray_ZEROS(1, [n], cnp.NPY_COMPLEX128, 0) + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] retarr + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] x + + # The size of the QR decomposition is rank dependent which is unknown + # at runtime. Hence we don't want to allocate a dense version of the + # linear operator which can be too big. Instead, a typical "realloc double + # if run out of space" strategy is used here. Starts with 4*n + # Also, we hold the A.T @ x results in a separate array to return + # and do the same for that too. + cdef cnp.complex128_t *ra = PyMem_Malloc( + sizeof(cnp.complex128_t)*no_of_cols*n + ) + cdef cnp.complex128_t *reallocated_ra + cdef cnp.complex128_t *ret = PyMem_Malloc( + sizeof(cnp.complex128_t)*no_of_cols*n + ) + cdef cnp.complex128_t *reallocated_ret + cdef cnp.complex128_t enorm = 0.0 + + if (not ra) or (not ret): + raise MemoryError("Failed to allocate at least required memory " + f"{no_of_cols*n*8} bytes for" + "'scipy.linalg.interpolative.idz_findrank()' " + "function.") + + if not rng: + rng = np.random.default_rng() + + krank = 0 + try: + while True: + + # Generate random vector and rmatvec then save the result + x = rng.uniform(size=(m,2)).view(np.complex128).ravel() + y = A.rmatvec(x) + + for kk in range(n): + ret[krank*n + kk] = y[kk] + + if krank == 0: + enorm = dznrm2(&n, &y[0], &intone) + else: # krank > 0 + # Transpose-Apply previous Householder reflectors, if any + # SIDE, TRANS, M, N, K, A, LDA, TAU, C, LDC, WORK, INFO + zunm2r('L','C', &n, &intone, &krank, &ra[0], &n, + &tau[0], &y[0], &n, &ra[(no_of_cols-1)*n], &info) + + # Get the next Householder reflector + r = n-krank + # N, ALPHA, X, INCX, TAU + zlarfgp(&r, &y[krank], &y[krank+1], &intone, &tau[krank]) + + for kk in range(n): + ra[krank*n + kk] = y[kk] + + # Running out of space; try to double the size of ra + if krank == (no_of_cols-2): + reallocated_ra = PyMem_Realloc( + ra, sizeof(cnp.complex128_t)*no_of_cols*n*2) + reallocated_ret = PyMem_Realloc( + ret, sizeof(cnp.complex128_t)*no_of_cols*n*2) + + if reallocated_ra and reallocated_ret: + ra = reallocated_ra + ret = reallocated_ret + no_of_cols *= 2 + else: + raise MemoryError( + "'scipy.linalg.interpolative.idz_findrank()' failed to " + f"allocate the required memory,{no_of_cols*n*16} bytes " + "while trying to determine the rank (currently " + f"{krank}) of a LinearOperator with precision {eps}." + ) + krank += 1 + if (np.abs(y[krank-1]) < eps*enorm) or (krank >= min(m, n)): + break + finally: + # Crashed or successfully ended up here + # Discard Householder vectors + PyMem_Free(ra) + retarr = cnp.PyArray_EMPTY(2, [krank, n], cnp.NPY_COMPLEX128, 0) + for k in range(krank): + for kk in range(n): + retarr[k, kk] = ret[k*n+kk] + PyMem_Free(ret) + + return krank, retarr + + +def idz_id2svd( + cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] cols, + cnp.ndarray[cnp.int64_t, mode='c', ndim=1] perms, + cnp.ndarray[cnp.complex128_t, ndim=2] proj, + ): + cdef int m = cols.shape[0], krank = cols.shape[1] + cdef int n = proj.shape[1] + krank, info, ci + cdef cnp.ndarray[cnp.complex128_t, mode='fortran', ndim=2] C + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] tau1 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] tau2 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] UU + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] S + cdef cnp.ndarray[cnp.complex128_t, ndim=2] V + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] VV + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds1 + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds2 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] p + + if krank > 0: + UU = cnp.PyArray_ZEROS(2, [m, krank], cnp.NPY_COMPLEX128, 0) + VV = cnp.PyArray_ZEROS(2, [n, krank], cnp.NPY_COMPLEX128, 0) + p = cnp.PyArray_ZEROS(2, [krank, n], cnp.NPY_COMPLEX128, 0) + + # idd_reconint + for ci in range(krank): + p[ci, perms[ci]] = 1.0 + + p[:, perms[krank:]] = proj[:, :] + inds1, tau1 = idzr_qrpiv(cols, krank) + # idz_rinqr and idz_rearr + r = np.triu(cols[:krank, :]) + for ci in range(krank-1, -1, -1): + r[:, [ci, inds1[ci]]] = r[:, [inds1[ci], ci]] + + t = p.T.conj().copy() + inds2, tau2 = idzr_qrpiv(t, krank) + r2 = np.triu(t[:krank, :]) + for ci in range(krank-1, -1, -1): + r2[:, [ci, inds2[ci]]] = r2[:, [inds2[ci], ci]] + + r3 = r @ r2.T.conj() + UU[:krank, :krank], S, V = la.svd(r3, full_matrices=False) + + # Apply Q of col to U from the left + # But do the adjoint dance for LAPACK via U.H @ Q.H + np.conjugate(tau1, out=tau1) + C = cols[:, :krank].conj().copy(order='F') + zunm2r('R', 'C', + &krank, &m, &krank, &C[0, 0], &m, &tau1[0], + &UU[0,0], &krank, &cols[0, 0], &info) + + VV[:krank, :krank] = V[:, :].conj().T + + # Apply Q of t to V from the left + # But do the adjoint dance for LAPACK via V.H @ Q.H + np.conjugate(tau2, out=tau2) + C = t[:, :krank].conj().copy(order='F') + zunm2r('R', 'C', + &krank, &n, &krank, &C[0, 0], &n, &tau2[0], + &VV[0, 0], &krank, &cols[0, 0], &info) + + return UU, S, VV + + +def idz_reconid(B, idx, proj): + cdef int m = B.shape[0], krank = B.shape[1] + cdef int n = len(idx) + approx = np.zeros([m, n], dtype=np.complex128) + + approx[:, idx[:krank]] = B + approx[:, idx[krank:]] = B @ proj + + return approx + + +def idz_snorm(A: LinearOperator, int its=20, rng=None): + cdef int n = A.shape[1] + cdef int j = 0, intone = 1 + cdef cnp.float64_t snorm = 0.0 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] v + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] u + + if not rng: + rng = np.random.default_rng() + + v = rng.uniform(low=-1, high=1, size=(n, 2)).view(np.complex128).ravel() + v /= dznrm2(&n, &v[0], &intone) + + for j in range(its): + u = A.matvec(v) + v = A.rmatvec(u) + snorm = dznrm2(&n, &v[0], &intone) + if snorm > 0.0: + v /= snorm + + snorm = np.sqrt(snorm) + + return snorm + + +def idzp_aid(cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] a: NDArray, eps: float, + rng=None): + krank, proj = idz_estrank(a, eps=eps, rng=rng) + if krank != 0: + proj = proj[:krank, :] + return idzp_id(proj, eps=eps) + + return idzp_id(a, eps=eps) + + +def idzp_asvd(cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] a, cnp.float64_t eps, + rng=None): + cdef int m = a.shape[0], n = a.shape[1] + cdef int krank, info, ci + cdef cnp.ndarray[cnp.complex128_t, mode='fortran', ndim=2] C + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] tau1 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] tau2 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] UU + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] S + cdef cnp.ndarray[cnp.complex128_t, ndim=2] V + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] VV + cdef cnp.ndarray[cnp.complex128_t, ndim=2] proj + cdef cnp.ndarray[cnp.npy_int64, ndim=1] perms + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds1 + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds2 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] p + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] col + + krank, perms, proj = idzp_aid(a.copy(), eps, rng) + + if krank > 0: + UU = cnp.PyArray_ZEROS(2, [m, krank], cnp.NPY_COMPLEX128, 0) + VV = cnp.PyArray_ZEROS(2, [n, krank], cnp.NPY_COMPLEX128, 0) + p = cnp.PyArray_ZEROS(2, [krank, n], cnp.NPY_COMPLEX128, 0) + col = a[:, perms[:krank]].copy() + + # idd_reconint + for ci in range(krank): + p[ci, perms[ci]] = 1.0 + + p[:, perms[krank:]] = proj[:, :] + inds1, tau1 = idzr_qrpiv(col, krank) + # idz_rinqr and idz_rearr + r = np.triu(col[:krank, :]) + for ci in range(krank-1, -1, -1): + r[:, [ci, inds1[ci]]] = r[:, [inds1[ci], ci]] + + t = p.T.conj().copy() + inds2, tau2 = idzr_qrpiv(t, krank) + r2 = np.triu(t[:krank, :]) + for ci in range(krank-1, -1, -1): + r2[:, [ci, inds2[ci]]] = r2[:, [inds2[ci], ci]] + + r3 = r @ r2.T.conj() + UU[:krank, :krank], S, V = la.svd(r3, full_matrices=False) + + # Apply Q of col to U from the left + # But do the adjoint dance for LAPACK via U.H @ Q.H + np.conjugate(tau1, out=tau1) + C = col[:, :krank].conj().copy(order='F') + zunm2r('R', 'C', + &krank, &m, &krank, &C[0, 0], &m, &tau1[0], + &UU[0,0], &krank, &a[0, 0], &info) + + VV[:krank, :krank] = V[:, :].conj().T + + # Apply Q of t to V from the left + # But do the adjoint dance for LAPACK via V.H @ Q.H + np.conjugate(tau2, out=tau2) + C = t[:, :krank].conj().copy(order='F') + zunm2r('R', 'C', + &krank, &n, &krank, &C[0, 0], &n, &tau2[0], + &VV[0, 0], &krank, &a[0, 0], &info) + + return UU, S, VV + + +def idzp_id(cnp.ndarray[cnp.complex128_t, mode="c", ndim=2] a, cnp.float64_t eps): + cdef int n = a.shape[1], krank, tmp_int, p + cdef double complex one = 1 + krank, _, inds = idzp_qrpiv(a, eps) + + # Change pivots to permutation + perms = cnp.PyArray_Arange(0, n, 1, cnp.NPY_INT64) + + if krank > 0: + for p in range(krank): + # Apply pivots + tmp_int = perms[p] + perms[p] = perms[inds[p]] + perms[inds[p]] = tmp_int + + tmp_int = n - krank + # SIDE,UPLO,TRANSA,DIAG,M,N,ALPHA,A,LDA,B,LDB + ztrsm('R', 'L', 'N', 'N', + &tmp_int, &krank, &one, &a[0, 0], &n, &a[0, krank], &n) + + return krank, perms, a[:krank, krank:] + + +def idzp_qrpiv(cnp.ndarray[cnp.complex128_t, mode="c", ndim=2] a, cnp.float64_t eps): + cdef int m = a.shape[0], n = a.shape[1] + cdef cnp.ndarray col_norms = cnp.PyArray_ZEROS(1, [n], cnp.NPY_FLOAT64, 0) + cdef int k = 0, kpiv = 0, i = 0, tmp_int = 0, int_n = 0 + cdef double complex tmp_sca = 0. + cdef cnp.ndarray taus = cnp.PyArray_ZEROS(1, [m], cnp.NPY_COMPLEX128, 0) + cdef cnp.ndarray ind = cnp.PyArray_ZEROS(1, [n], cnp.NPY_INT64, 0) + cdef double complex[::1] taus_v = taus + cdef cnp.float64_t feps = 0.1e-16 # Smaller than np.finfo(np.float64).eps + cdef cnp.float64_t ssmax, ssmaxin + cdef int nupdate = 0 + + for i in range(n): + col_norms[i] = dznrm2(&m, &a[0, i], &n)**2 + + kpiv = np.argmax(col_norms) + ssmax = col_norms[kpiv] + ssmaxin = ssmax + + for k in range(min(m, n)): + + # Pivoting + ind[k] = kpiv + # Swap columns a[:, k] and a[:, kpiv] + a[:, [kpiv, k]] = a[:, [k, kpiv]] + + # Swap col_norms[krank] and col_norms[kpiv] + col_norms[[kpiv, k]] = col_norms[[k, kpiv]] + + if k < m-1: + # Compute the householder reflector for column k + tmp_sca = a[k, k] + # FIX: Convert these to F_INT + tmp_int = (m - k) + int_n = n + zlarfgp(&tmp_int, &tmp_sca, &a[k+1, k], &int_n, &taus_v[k]) + + # Overwrite with 1. for easy matmul + a[k, k] = 1.0 + if k < n-1: + # Apply the householder reflector to the rest on the right. + # Note! Tau returned by zlarfgp is complex valued and thus, + # reflector is not Hermitian, hence the conjugates. See the + # documentation of zlarfgp. + a[k:, k+1:] -= np.outer(taus[k].conj()*a[k:, k], + a[k:, k].conj() @ a[k:, k+1:] + ) + + # Put back the beta in place + a[k, k] = tmp_sca + # Update the norms + col_norms[k] = 0 + col_norms[k+1:] -= (a[k, k+1:] * a[k, k+1:].conj()).real + ssmax = 0.0 + kpiv = k+1 + + if k < n-1: + kpiv = np.argmax(col_norms[k+1:]) + (k + 1) + ssmax = col_norms[kpiv] + + if (((ssmax < 1000*feps*ssmaxin) and (nupdate == 0)) or + ((ssmax < ((1000*feps)**2)*ssmaxin) and (nupdate == 1))): + nupdate += 1 + ssmax = 0 + kpiv = k+1 + if k < n-1: + for i in range(k+1, n): + tmp_int = m-k-1 + col_norms[i] = dznrm2(&tmp_int, &a[k+1, i], &n)**2 + kpiv = np.argmax(col_norms[k+1:]) + (k + 1) + ssmax = col_norms[kpiv] + if (ssmax <= (eps**2)*ssmaxin): + break + # a is overwritten; return numerical rank and pivots + + return k+1, taus, ind + + +def idzp_rid(A: LinearOperator, cnp.float64_t eps, rng=None): + _, ret = idz_findrank(A, eps, rng=rng) + return idzp_id(ret, eps=eps) + + +def idzp_rsvd(A: LinearOperator, cnp.float64_t eps, rng=None): + cdef int n = A.shape[1] + cdef int krank, j + cdef cnp.ndarray[cnp.int64_t, mode='c', ndim=1] perms + cdef cnp.ndarray[cnp.complex128_t, ndim=2] proj + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] col + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] x + + krank, perms, proj = idzp_rid(A, eps, rng=rng) + + if krank > 0: + # idd_getcols + col = cnp.PyArray_EMPTY(2, [n, krank], cnp.NPY_COMPLEX128, 0) + x = cnp.PyArray_ZEROS(1, [n], cnp.NPY_COMPLEX128, 0) + + for j in range(krank): + x[perms[j]] = 1. + col[:, j] = A.matvec(x) + x[perms[j]] = 0. + + return idz_id2svd(cols=col, perms=perms, proj=proj) + + # TODO: figure out empty return + return None + + +def idzp_svd(cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] a, cnp.float64_t eps): + cdef int m = a.shape[0], krank, info + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] taus + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] UU + cdef cnp.ndarray[cnp.complex128_t, ndim=2] V + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] r + cdef cnp.ndarray[cnp.complex128_t, mode='fortran', ndim=2] C + cdef cnp.ndarray[cnp.float64_t, ndim=1] S + + # Get the pivoted QR + krank, taus, inds = idzp_qrpiv(a, eps) + UU = cnp.PyArray_ZEROS(2, [m, krank], cnp.NPY_COMPLEX128, 0) + + if krank > 0: + r = np.triu(a[:krank, :]) + + for p in range(krank-1, -1, -1): + r[:, [p, inds[p]]] = r[:, [inds[p], p]] + + UU[:krank, :krank], S, V = la.svd(r, full_matrices=False) + # Apply Q to U via zunm2r + np.conjugate(taus, out=taus) + # But do the adjoint dance for LAPACK via U.H @ Q.H; use a for scratch + C = a[:, :krank].conj().copy(order='F') + zunm2r('R', 'C', + &krank, &m, &krank, &C[0, 0], &m, &taus[0], + &UU[0,0], &krank, &a[0, 0], &info) + + return UU, S, V + + +def idzr_aid(cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] a: NDArray, int krank, + rng=None): + cdef int m = a.shape[0], n2, L, nblock, nsteps = 3, mb + cdef cnp.float64_t twopi = 2*np.pi, fact + cdef double complex twopii = twopi*1.j + cdef cnp.ndarray[cnp.npy_int64, mode='c', ndim=1] ind + cdef cnp.ndarray[cnp.npy_int64, mode='c', ndim=1] subselect + cdef cnp.ndarray[cnp.npy_float64, mode='c', ndim=1] dm1 + cdef cnp.ndarray[cnp.npy_float64, mode='c', ndim=1] dm2 + cdef cnp.ndarray[cnp.npy_float64, mode='c', ndim=3] albetas + cdef cnp.ndarray[cnp.npy_float64, mode='c', ndim=2] rta + cdef cnp.ndarray[cnp.npy_float64, mode='c', ndim=2] giv2x2 + + if not rng: + rng = np.random.default_rng() + + n2 = 0 + L = krank + 8 + if (L >= n2) or (L > m): + inds, proj = idzr_id(a, krank) + return inds, proj + + n2 = idd_poweroftwo(m) + # This part is the initialization that is done via idz_frmi + # for a Subsampled Randomized Fourier Transfmrom (SRFT). + + # Draw (nsteps x m x 4) array from [0, 2)*pi uniformly for + # random points on complex unit circle and unitary rotations + albetas = np.empty([nsteps, m, 4]) + albetas[:, :, 2:] = rng.uniform(low=0.0, high=2.0, size=[nsteps, m, 2]) + albetas[:, :, 2:] *= np.pi + np.cos(albetas[:, :, 2], out=albetas[:, :, 0]) + np.sin(albetas[:, :, 2], out=albetas[:, :, 1]) + np.cos(albetas[:, :, 3], out=albetas[:, :, 2]) + np.sin(albetas[:, :, 3], out=albetas[:, :, 3]) + + # idd_random_transf + rta = a.copy() + + # Rotate and shuffle "a" nsteps-many times + giv2x2 = np.array([[0., 0. ], [0., 0.]]) + for nstep in range(nsteps): + # Multiply with a point on the unit circle + rta *= albetas[nstep, :, 2:].view(np.complex128) + # Rotate + for row in range(m-1): + alpha, beta = albetas[nstep, row, 0], albetas[nstep, row, 1] + giv2x2[0, 0] = alpha + giv2x2[0, 1] = beta + giv2x2[1, 0] = -beta + giv2x2[1, 1] = alpha + np.matmul(giv2x2, rta[row:row+2, :], out=rta[row:row+2, :]) + + rta = rta[rng.permutation(m), :] + + # idd_subselect pick randomly n2-many rows + subselect = rng.choice(m, n2, replace=False) + rta = rta[subselect, :] + ind = rng.choice(n2, L, replace=False) + + nblock = idd_ldiv(L, n2) + mb = n2 // nblock + fact = 1.0 / np.sqrt(n2) + + # Create (L x mb) DFT matrix + # wsave = np.empty([L, mb], dtype=np.complex128) + dm1, dm2 = np.divmod(ind, mb, dtype=np.float64) + dm1 /= n2 + dm1 += dm2 / mb + wsave = np.outer(dm1, -twopii*np.arange(mb)) + np.exp(wsave, out=wsave) + wsave *= fact + + # Perform partial FFT to each nblock then swap first two axes for transposition + # and subsample by ind // mb. This is basically a few options combined into one + # First we view each column as (nblock x mb) then take fft of each mb-long chunk. + # Then we transpose and multiply with DFT matrix and subselect. + # See DOI:10.1016/j.acha.2007.12.002 - Section 3.3 + + # Original fortran code does this single column at a time. We do a bit of array + # manipulation to do it in one go for all columns at once. + F = np.swapaxes( + fft(rta.reshape(nblock, mb, -1, order='F'), axis=0), 0, 1 + )[:, ind // mb, :] + # Perform direct calculation with DFT matrix + V = np.einsum('ij,jim->im', wsave, F) + + return idzr_id(V, krank) + + +def idzr_asvd(cnp.ndarray[cnp.complex128_t, mode="c", ndim=2] a, int krank, rng=None): + cdef int m = a.shape[0], n = a.shape[1] + cdef int info, ci + cdef cnp.ndarray[cnp.complex128_t, mode='fortran', ndim=2] C + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] tau1 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] tau2 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] UU + cdef cnp.ndarray[cnp.float64_t, mode='c', ndim=1] S + cdef cnp.ndarray[cnp.complex128_t, ndim=2] V + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] VV + cdef cnp.ndarray[cnp.complex128_t, ndim=2] proj + cdef cnp.ndarray[cnp.npy_int64, ndim=1] perms + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds1 + cdef cnp.ndarray[cnp.npy_int64, ndim=1] inds2 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] p + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] col + UU = cnp.PyArray_ZEROS(2, [m, krank], cnp.NPY_COMPLEX128, 0) + VV = cnp.PyArray_ZEROS(2, [n, krank], cnp.NPY_COMPLEX128, 0) + p = cnp.PyArray_ZEROS(2, [krank, n], cnp.NPY_COMPLEX128, 0) + + perms, proj = idzr_aid(a.copy(), krank=krank, rng=rng) + col = a[:, perms[:krank]].copy() + + # idd_reconint + for ci in range(krank): + p[ci, perms[ci]] = 1.0 + + p[:, perms[krank:]] = proj[:, :] + inds1, tau1 = idzr_qrpiv(col, krank) + # idz_rinqr and idz_rearr + r = np.triu(col[:krank, :]) + for ci in range(krank-1, -1, -1): + r[:, [ci, inds1[ci]]] = r[:, [inds1[ci], ci]] + + t = p.T.conj().copy() + inds2, tau2 = idzr_qrpiv(t, krank) + r2 = np.triu(t[:krank, :]) + for ci in range(krank-1, -1, -1): + r2[:, [ci, inds2[ci]]] = r2[:, [inds2[ci], ci]] + + r3 = r @ r2.T.conj() + UU[:krank, :krank], S, V = la.svd(r3, full_matrices=False) + + # Apply Q of col to U from the left + # But do the adjoint dance for LAPACK via U.H @ Q.H + np.conjugate(tau1, out=tau1) + C = col[:, :krank].conj().copy(order='F') + zunm2r('R', 'C', + &krank, &m, &krank, &C[0, 0], &m, &tau1[0], + &UU[0,0], &krank, &a[0, 0], &info) + + VV[:krank, :krank] = V[:, :].conj().T + + # Apply Q of t to V from the left + # But do the adjoint dance for LAPACK via V.H @ Q.H + np.conjugate(tau2, out=tau2) + C = t[:, :krank].conj().copy(order='F') + zunm2r('R', 'C', + &krank, &n, &krank, &C[0, 0], &n, &tau2[0], + &VV[0, 0], &krank, &a[0, 0], &info) + + return UU, S, VV + + +def idzr_id(cnp.ndarray[cnp.complex128_t, ndim=2] a, int krank): + cdef int n = a.shape[1], tmp_int, p + cdef double complex one = 1.0 + cdef cnp.ndarray[cnp.int64_t, ndim=1] inds + cdef cnp.ndarray[cnp.int64_t, ndim=1] perms + + inds, _ = idzr_qrpiv(a, krank) + perms = cnp.PyArray_Arange(0, n, 1, cnp.NPY_INT64) + + if krank > 0: + for p in range(krank): + # Apply pivots + tmp_int = perms[p] + perms[p] = perms[inds[p]] + perms[inds[p]] = tmp_int + tmp_int = n - krank + # SIDE,UPLO,TRANSA,DIAG,M,N,ALPHA,A,LDA,B,LDB + ztrsm('R', 'L', 'N', 'N', + &tmp_int, &krank, &one, &a[0, 0], &n, &a[0, krank], &n) + + return perms, a[:krank, krank:] + + +def idzr_qrpiv(cnp.ndarray[cnp.complex128_t, mode="c", ndim=2] a, int krank): + cdef int m = a.shape[0], n = a.shape[1] + cdef int loop = 0, loops, kpiv = 0, i = 0, tmp_int = 0 + cdef cnp.ndarray col_norms = cnp.PyArray_ZEROS(1, [n], cnp.NPY_FLOAT64, 0) + cdef double complex tmp_sca = 0. + cdef cnp.ndarray taus = cnp.PyArray_ZEROS(1, [m], cnp.NPY_COMPLEX128, 0) + cdef cnp.ndarray ind = cnp.PyArray_ZEROS(1, [n], cnp.NPY_INT64, 0) + cdef double complex[::1] taus_v = taus + cdef cnp.float64_t feps = 0.1e-16 # Smaller than np.finfo(np.float64).eps + cdef cnp.float64_t ssmax, ssmaxin + cdef int nupdate = 0 + + loops = min(krank, min(m, n)) + for i in range(n): + col_norms[i] = dznrm2(&m, &a[0, i], &n)**2 + + kpiv = np.argmax(col_norms) + ssmax = col_norms[kpiv] + ssmaxin = ssmax + + for loop in range(loops): + + ind[loop] = kpiv + # Swap columns a[:, k] and a[:, kpiv] + a[:, [kpiv, loop]] = a[:, [loop, kpiv]] + # Swap col_norms[krank] and col_norms[kpiv] + col_norms[[kpiv, loop]] = col_norms[[loop, kpiv]] + + if loop < m-1: + tmp_sca = a[loop, loop] + # FIX: Convert these to F_INT + tmp_int = (m - loop) + zlarfgp(&tmp_int, &tmp_sca, &a[loop+1, loop], &n, &taus_v[loop]) + + # Overwrite with 1. for easy matmul + a[loop, loop] = 1 + if loop < n-1: + # Apply the householder reflector to the rest on the right + a[loop:, loop+1:] -= np.outer( + np.conj(taus[loop])*a[loop:, loop], + a[loop:, loop].conj() @ a[loop:, loop+1:] + ) + # Put back the beta in place + a[loop, loop] = tmp_sca + + # Update the norms + col_norms[loop] = 0 + col_norms[loop+1:] -= (a[loop, loop+1:]*a[loop, loop+1:].conj()).real + ssmax = 0 + kpiv = loop+1 + + if loop < n-1: + kpiv = np.argmax(col_norms[loop+1:]) + (loop + 1) + ssmax = col_norms[kpiv] + if (((ssmax < 1000*feps*ssmaxin) and (nupdate == 0)) or + ((ssmax < ((1000*feps)**2)*ssmaxin) and (nupdate == 1))): + nupdate += 1 + ssmax = 0 + kpiv = loop+1 + + if loop < n-1: + for i in range(loop+1, n): + tmp_int = m-loop-1 + col_norms[i] = dznrm2(&tmp_int, &a[loop+1, i], &n)**2 + kpiv = np.argmax(col_norms[loop+1:]) + (loop + 1) + ssmax = col_norms[kpiv] + + return ind, taus + + +def idzr_rid(A: LinearOperator, int krank, rng=None): + cdef int m = A.shape[0], n = A.shape[1], k = 0 + cdef int L = min(krank+2, min(m, n)) + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] r + + if not rng: + rng = np.random.default_rng() + + r = cnp.PyArray_EMPTY(2, [L, n], cnp.NPY_COMPLEX128, 0) + for k in range(L): + r[k, :] = A.rmatvec(rng.uniform(size=(m,2)).view(np.complex128).ravel()) + + return idzr_id(a=r.conj(), krank=krank) + + +def idzr_rsvd(A: LinearOperator, int krank, rng=None): + cdef int n = A.shape[1], j + cdef cnp.ndarray[cnp.int64_t, mode='c', ndim=1] perms + cdef cnp.ndarray[cnp.complex128_t, ndim=2] proj + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] col + + perms, proj = idzr_rid(A, krank, rng) + # idd_getcols + col = cnp.PyArray_EMPTY(2, [n, krank], cnp.NPY_COMPLEX128, 0) + x = cnp.PyArray_ZEROS(1, [n], cnp.NPY_COMPLEX128, 0) + for j in range(krank): + x[perms[j]] = 1. + col[:, j] = A.matvec(x) + x[perms[j]] = 0. + + return idz_id2svd(cols=col, perms=perms, proj=proj) + + +def idzr_svd(cnp.ndarray[cnp.complex128_t, mode="c", ndim=2] a, int krank): + cdef int m = a.shape[0], n = a.shape[1], info = 0 + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=1] taus + cdef cnp.ndarray[cnp.int64_t, mode='c', ndim=1] inds + cdef cnp.ndarray[cnp.complex128_t, mode='c', ndim=2] UU + cdef cnp.ndarray[cnp.complex128_t, mode='fortran', ndim=2] C + UU = cnp.PyArray_ZEROS(2, [m, krank], cnp.NPY_COMPLEX128, 0) + + krank = min(krank, min(m, n)) + # Get the pivoted QR + inds, taus = idzr_qrpiv(a, krank) + r = np.triu(a[:krank, :]) + # Apply pivots in reverse + for p in range(krank-1, -1, -1): + r[:, [p, inds[p]]] = r[:, [inds[p], p]] + + # JOBU, JOBVT, M, N, A, LDA, S, U, LDU, VT, LDVT, WORK, LWORK, INFO + # zgesvd() + UU[:krank, :krank], S, V = la.svd(r, full_matrices=False) + + # Apply Q to U via zunm2r + np.conjugate(taus, out=taus) + # But do the adjoint dance for LAPACK via U.H @ Q.H; use a for scratch + C = a[:, :krank].conj().copy(order='F') + zunm2r('R', 'C', + &krank, &m, &krank, &C[0, 0], &m, &taus[0], + &UU[0,0], &krank, &a[0, 0], &info) + + return UU, S, V diff --git a/scipy/linalg/interpolative.py b/scipy/linalg/interpolative.py index 4f12d4366da7..ce9502607194 100644 --- a/scipy/linalg/interpolative.py +++ b/scipy/linalg/interpolative.py @@ -385,7 +385,7 @@ """ -import scipy.linalg._interpolative_backend as _backend +import scipy.linalg._decomp_interpolative as _backend import numpy as np import sys From 2f216c9fd7c15ad4c6d3bec14821823bc55c3f41 Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Tue, 23 Apr 2024 09:28:17 +0200 Subject: [PATCH 406/500] MAINT:linalg: Modify meson file for id_dist F77 translation [skip ci] --- scipy/linalg/meson.build | 55 ++++------------------------------------ 1 file changed, 5 insertions(+), 50 deletions(-) diff --git a/scipy/linalg/meson.build b/scipy/linalg/meson.build index a058a86aef44..45790d4061b1 100644 --- a/scipy/linalg/meson.build +++ b/scipy/linalg/meson.build @@ -111,57 +111,13 @@ py3.extension_module('_flapack', # TODO: cblas/clapack are built *only* for ATLAS. Why? Is it still needed? -# id_dist contains a copy of FFTPACK, which has type mismatch warnings -# that are hard to fix. This code is terrible and noisy during the build, -# silence it completely. -_suppress_all_warnings = ff.get_supported_arguments('-w') - -py3.extension_module('_interpolative', - [ - 'src/id_dist/src/dfft.f', - 'src/id_dist/src/id_rand.f', - 'src/id_dist/src/id_rtrans.f', - 'src/id_dist/src/idd_frm.f', - 'src/id_dist/src/idd_house.f', - 'src/id_dist/src/idd_id.f', - 'src/id_dist/src/idd_id2svd.f', - 'src/id_dist/src/idd_qrpiv.f', - 'src/id_dist/src/idd_sfft.f', - 'src/id_dist/src/idd_snorm.f', - 'src/id_dist/src/idd_svd.f', - 'src/id_dist/src/iddp_aid.f', - 'src/id_dist/src/iddp_asvd.f', - 'src/id_dist/src/iddp_rid.f', - 'src/id_dist/src/iddp_rsvd.f', - 'src/id_dist/src/iddr_aid.f', - 'src/id_dist/src/iddr_asvd.f', - 'src/id_dist/src/iddr_rid.f', - 'src/id_dist/src/iddr_rsvd.f', - 'src/id_dist/src/idz_frm.f', - 'src/id_dist/src/idz_house.f', - 'src/id_dist/src/idz_id.f', - 'src/id_dist/src/idz_id2svd.f', - 'src/id_dist/src/idz_qrpiv.f', - 'src/id_dist/src/idz_sfft.f', - 'src/id_dist/src/idz_snorm.f', - 'src/id_dist/src/idz_svd.f', - 'src/id_dist/src/idzp_aid.f', - 'src/id_dist/src/idzp_asvd.f', - 'src/id_dist/src/idzp_rid.f', - 'src/id_dist/src/idzp_rsvd.f', - 'src/id_dist/src/idzr_aid.f', - 'src/id_dist/src/idzr_asvd.f', - 'src/id_dist/src/idzr_rid.f', - 'src/id_dist/src/idzr_rsvd.f', - 'src/id_dist/src/prini.f', - f2py_gen.process('interpolative.pyf'), - ], - fortran_args: [fortran_ignore_warnings, _suppress_all_warnings], +# _decomp_interpolative +py3.extension_module('_decomp_interpolative', + linalg_init_cython_gen.process('_decomp_interpolative.pyx'), + c_args: cython_c_args, + dependencies: np_dep, link_args: version_link_args, - dependencies: [lapack_dep, fortranobject_dep], - override_options: ['b_lto=false'], install: true, - link_language: 'fortran', subdir: 'scipy/linalg' ) @@ -278,7 +234,6 @@ python_sources = [ '_decomp_schur.py', '_decomp_svd.py', '_expm_frechet.py', - '_interpolative_backend.py', '_matfuncs.py', '_matfuncs_expm.pyi', '_matfuncs_inv_ssq.py', From 1784da2ed8a99450541d89a56bc6612d2e83bd05 Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Thu, 13 Jun 2024 14:17:18 +0200 Subject: [PATCH 407/500] MAINT:linalg: Adjust public api for the translated funcs [skip ci] ENH:linalg: Modify function signatures for interpolative [skip ci] --- scipy/linalg/interpolative.py | 252 +++++++++++++++------------------- 1 file changed, 108 insertions(+), 144 deletions(-) diff --git a/scipy/linalg/interpolative.py b/scipy/linalg/interpolative.py index ce9502607194..b5c6cdc670e4 100644 --- a/scipy/linalg/interpolative.py +++ b/scipy/linalg/interpolative.py @@ -1,4 +1,4 @@ -#****************************************************************************** +# ****************************************************************************** # Copyright (C) 2013 Kenneth L. Ho # # Redistribution and use in source and binary forms, with or without @@ -25,7 +25,7 @@ # 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. -#****************************************************************************** +# ****************************************************************************** # Python module for interfacing with `id_dist`. @@ -387,7 +387,7 @@ import scipy.linalg._decomp_interpolative as _backend import numpy as np -import sys +import warnings __all__ = [ 'estimate_rank', @@ -405,9 +405,18 @@ _DTYPE_ERROR = ValueError("invalid input dtype (input must be float64 or complex128)") _TYPE_ERROR = TypeError("invalid input type (must be array or LinearOperator)") -_32BIT_ERROR = ValueError("interpolative decomposition on 32-bit systems " - "with complex128 is buggy") -_IS_32BIT = (sys.maxsize < 2**32) + + +def _C_contiguous_copy(A): + """ + Same as np.ascontiguousarray, but ensure a copy + """ + A = np.asarray(A) + if A.flags.c_contiguous: + A = A.copy() + else: + A = np.ascontiguousarray(A) + return A def _is_real(A): @@ -424,53 +433,31 @@ def _is_real(A): def seed(seed=None): """ - Seed the internal random number generator used in this ID package. - - The generator is a lagged Fibonacci method with 55-element internal state. - - Parameters - ---------- - seed : int, sequence, 'default', optional - If 'default', the random seed is reset to a default value. - - If `seed` is a sequence containing 55 floating-point numbers - in range [0,1], these are used to set the internal state of - the generator. - - If the value is an integer, the internal state is obtained - from `numpy.random.RandomState` (MT19937) with the integer - used as the initial seed. - - If `seed` is omitted (None), ``numpy.random.rand`` is used to - initialize the generator. + This function used to set the seed of the randomization algorithms used in the + `scipy.linalg.interpolative` functions written in Fortran77. + The library has been ported to Python and now the functions use the native NumPy + generators and this function has no content and returns None. Thus this function + should not be used and will be removed in SciPy version 1.17.0. """ - # For details, see :func:`_backend.id_srand`, :func:`_backend.id_srandi`, - # and :func:`_backend.id_srando`. - - if isinstance(seed, str) and seed == 'default': - _backend.id_srando() - elif hasattr(seed, '__len__'): - state = np.asfortranarray(seed, dtype=float) - if state.shape != (55,): - raise ValueError("invalid input size") - elif state.min() < 0 or state.max() > 1: - raise ValueError("values not in range [0,1]") - _backend.id_srandi(state) - elif seed is None: - _backend.id_srandi(np.random.rand(55)) - else: - rnd = np.random.RandomState(seed) - _backend.id_srandi(rnd.rand(55)) + warnings.warn("`scipy.linalg.interpolative.seed` is deprecated and will be " + "removed in SciPy 1.17.0.", DeprecationWarning, stacklevel=2) def rand(*shape): """ - Generate standard uniform pseudorandom numbers via a very efficient lagged - Fibonacci method. + This function used to set the seed of the randomization algorithms used in the + `scipy.linalg.interpolative` functions written in Fortran77. - This routine is used for all random number generation in this package and - can affect ID and SVD results. + The library has been ported to Python and now the functions use the native NumPy + generators. Thus this function should not be used and will be removed in the + SciPy version 1.17.0. + + If pseudo-random numbers are needed, NumPy should be used instead: + + >>> import numpy as np + >>> rng = np.random.default_rng() + >>> rng.uniform(low=0., high=1.0, size=shape) Parameters ---------- @@ -478,11 +465,13 @@ def rand(*shape): Shape of output array """ - # For details, see :func:`_backend.id_srand`, and :func:`_backend.id_srando`. - return _backend.id_srand(np.prod(shape)).reshape(shape) + warnings.warn("`scipy.linalg.interpolative.rand` is deprecated and will be " + "removed in SciPy 1.17.0.", DeprecationWarning, stacklevel=2) + rng = np.random.default_rng() + return rng.uniform(low=0., high=1.0, size=shape) -def interp_decomp(A, eps_or_k, rand=True): +def interp_decomp(A, eps_or_k, rand=True, rng=None): """ Compute ID of a matrix. @@ -546,6 +535,9 @@ def interp_decomp(A, eps_or_k, rand=True): Whether to use random sampling if `A` is of type :class:`numpy.ndarray` (randomized algorithms are always used if `A` is of type :class:`scipy.sparse.linalg.LinearOperator`). + rng : :class:`numpy.random.Generator` + NumPy generator for the randomization steps in the algorithm. If ``rand`` is + ``False``, the argument is ignored. Returns ------- @@ -562,57 +554,49 @@ def interp_decomp(A, eps_or_k, rand=True): real = _is_real(A) if isinstance(A, np.ndarray): + A = _C_contiguous_copy(A) if eps_or_k < 1: eps = eps_or_k if rand: if real: - k, idx, proj = _backend.iddp_aid(eps, A) + k, idx, proj = _backend.iddp_aid(A, eps, rng=rng) else: - if _IS_32BIT: - raise _32BIT_ERROR - k, idx, proj = _backend.idzp_aid(eps, A) + k, idx, proj = _backend.idzp_aid(A, eps, rng=rng) else: if real: - k, idx, proj = _backend.iddp_id(eps, A) + k, idx, proj = _backend.iddp_id(A, eps) else: - k, idx, proj = _backend.idzp_id(eps, A) - return k, idx - 1, proj + k, idx, proj = _backend.idzp_id(A, eps) + return k, idx, proj else: k = int(eps_or_k) if rand: if real: - idx, proj = _backend.iddr_aid(A, k) + idx, proj = _backend.iddr_aid(A, k, rng=rng) else: - if _IS_32BIT: - raise _32BIT_ERROR - idx, proj = _backend.idzr_aid(A, k) + idx, proj = _backend.idzr_aid(A, k, rng=rng) else: if real: idx, proj = _backend.iddr_id(A, k) else: idx, proj = _backend.idzr_id(A, k) - return idx - 1, proj + return idx, proj elif isinstance(A, LinearOperator): - m, n = A.shape - matveca = A.rmatvec + if eps_or_k < 1: eps = eps_or_k if real: - k, idx, proj = _backend.iddp_rid(eps, m, n, matveca) + k, idx, proj = _backend.iddp_rid(A, eps, rng=rng) else: - if _IS_32BIT: - raise _32BIT_ERROR - k, idx, proj = _backend.idzp_rid(eps, m, n, matveca) - return k, idx - 1, proj + k, idx, proj = _backend.idzp_rid(A, eps, rng=rng) + return k, idx, proj else: k = int(eps_or_k) if real: - idx, proj = _backend.iddr_rid(m, n, matveca, k) + idx, proj = _backend.iddr_rid(A, k, rng=rng) else: - if _IS_32BIT: - raise _32BIT_ERROR - idx, proj = _backend.idzr_rid(m, n, matveca, k) - return idx - 1, proj + idx, proj = _backend.idzr_rid(A, k, rng=rng) + return idx, proj else: raise _TYPE_ERROR @@ -648,9 +632,9 @@ def reconstruct_matrix_from_id(B, idx, proj): Reconstructed matrix. """ if _is_real(B): - return _backend.idd_reconid(B, idx + 1, proj) + return _backend.idd_reconid(B, idx, proj) else: - return _backend.idz_reconid(B, idx + 1, proj) + return _backend.idz_reconid(B, idx, proj) def reconstruct_interp_matrix(idx, proj): @@ -662,10 +646,8 @@ def reconstruct_interp_matrix(idx, proj): P = numpy.hstack([numpy.eye(proj.shape[0]), proj])[:,numpy.argsort(idx)] - The original matrix can then be reconstructed from its skeleton matrix `B` - via:: - - numpy.dot(B, P) + The original matrix can then be reconstructed from its skeleton matrix ``B`` + via ``A = B @ P`` See also :func:`reconstruct_matrix_from_id` and :func:`reconstruct_skel_matrix`. @@ -677,7 +659,7 @@ def reconstruct_interp_matrix(idx, proj): Parameters ---------- idx : :class:`numpy.ndarray` - Column index array. + 1D column index array. proj : :class:`numpy.ndarray` Interpolation coefficients. @@ -686,10 +668,17 @@ def reconstruct_interp_matrix(idx, proj): :class:`numpy.ndarray` Interpolation matrix. """ + n, krank = len(idx), proj.shape[0] if _is_real(proj): - return _backend.idd_reconint(idx + 1, proj) + p = np.zeros([krank, n], dtype=np.float64) else: - return _backend.idz_reconint(idx + 1, proj) + p = np.zeros([krank, n], dtype=np.complex128) + + for ci in range(krank): + p[ci, idx[ci]] = 1.0 + p[:, idx[krank:]] = proj[:, :] + + return p def reconstruct_skel_matrix(A, k, idx): @@ -726,10 +715,7 @@ def reconstruct_skel_matrix(A, k, idx): :class:`numpy.ndarray` Skeleton matrix. """ - if _is_real(A): - return _backend.idd_copycols(A, k, idx + 1) - else: - return _backend.idz_copycols(A, k, idx + 1) + return A[:, idx[:k]] def id_to_svd(B, idx, proj): @@ -753,7 +739,7 @@ def id_to_svd(B, idx, proj): B : :class:`numpy.ndarray` Skeleton matrix. idx : :class:`numpy.ndarray` - Column index array. + 1D column index array. proj : :class:`numpy.ndarray` Interpolation coefficients. @@ -766,14 +752,16 @@ def id_to_svd(B, idx, proj): V : :class:`numpy.ndarray` Right singular vectors. """ + B = _C_contiguous_copy(B) if _is_real(B): - U, V, S = _backend.idd_id2svd(B, idx + 1, proj) + U, S, V = _backend.idd_id2svd(B, idx, proj) else: - U, V, S = _backend.idz_id2svd(B, idx + 1, proj) + U, S, V = _backend.idz_id2svd(B, idx, proj) + return U, S, V -def estimate_spectral_norm(A, its=20): +def estimate_spectral_norm(A, its=20, rng=None): """ Estimate spectral norm of a matrix by the randomized power method. @@ -796,18 +784,14 @@ def estimate_spectral_norm(A, its=20): """ from scipy.sparse.linalg import aslinearoperator A = aslinearoperator(A) - m, n = A.shape - def matvec(x): - return A.matvec(x) - def matveca(x): - return A.rmatvec(x) + if _is_real(A): - return _backend.idd_snorm(m, n, matveca, matvec, its=its) + return _backend.idd_snorm(A, its=its, rng=rng) else: - return _backend.idz_snorm(m, n, matveca, matvec, its=its) + return _backend.idz_snorm(A, its=its, rng=rng) -def estimate_spectral_norm_diff(A, B, its=20): +def estimate_spectral_norm_diff(A, B, its=20, rng=None): """ Estimate spectral norm of the difference of two matrices by the randomized power method. @@ -835,24 +819,14 @@ def estimate_spectral_norm_diff(A, B, its=20): from scipy.sparse.linalg import aslinearoperator A = aslinearoperator(A) B = aslinearoperator(B) - m, n = A.shape - def matvec1(x): - return A.matvec(x) - def matveca1(x): - return A.rmatvec(x) - def matvec2(x): - return B.matvec(x) - def matveca2(x): - return B.rmatvec(x) + if _is_real(A): - return _backend.idd_diffsnorm( - m, n, matveca1, matveca2, matvec1, matvec2, its=its) + return _backend.idd_diffsnorm(A, B, its=its, rng=rng) else: - return _backend.idz_diffsnorm( - m, n, matveca1, matveca2, matvec1, matvec2, its=its) + return _backend.idz_diffsnorm(A, B, its=its, rng=rng) -def svd(A, eps_or_k, rand=True): +def svd(A, eps_or_k, rand=True, rng=None): """ Compute SVD of a matrix via an ID. @@ -904,20 +878,21 @@ def svd(A, eps_or_k, rand=True): real = _is_real(A) if isinstance(A, np.ndarray): + A = _C_contiguous_copy(A) if eps_or_k < 1: eps = eps_or_k if rand: if real: - U, V, S = _backend.iddp_asvd(eps, A) + U, S, V = _backend.iddp_asvd(A, eps, rng=rng) else: - if _IS_32BIT: - raise _32BIT_ERROR - U, V, S = _backend.idzp_asvd(eps, A) + U, S, V = _backend.idzp_asvd(A, eps, rng=rng) else: if real: - U, V, S = _backend.iddp_svd(eps, A) + U, S, V = _backend.iddp_svd(A, eps) + V = V.T.conj() else: - U, V, S = _backend.idzp_svd(eps, A) + U, S, V = _backend.idzp_svd(A, eps) + V = V.T.conj() else: k = int(eps_or_k) if k > min(A.shape): @@ -925,44 +900,35 @@ def svd(A, eps_or_k, rand=True): f" {min(A.shape)} ") if rand: if real: - U, V, S = _backend.iddr_asvd(A, k) + U, S, V = _backend.iddr_asvd(A, k, rng=rng) else: - if _IS_32BIT: - raise _32BIT_ERROR - U, V, S = _backend.idzr_asvd(A, k) + U, S, V = _backend.idzr_asvd(A, k, rng=rng) else: if real: - U, V, S = _backend.iddr_svd(A, k) + U, S, V = _backend.iddr_svd(A, k) + V = V.T.conj() else: - U, V, S = _backend.idzr_svd(A, k) + U, S, V = _backend.idzr_svd(A, k) + V = V.T.conj() elif isinstance(A, LinearOperator): - m, n = A.shape - def matvec(x): - return A.matvec(x) - def matveca(x): - return A.rmatvec(x) if eps_or_k < 1: eps = eps_or_k if real: - U, V, S = _backend.iddp_rsvd(eps, m, n, matveca, matvec) + U, S, V = _backend.iddp_rsvd(A, eps, rng=rng) else: - if _IS_32BIT: - raise _32BIT_ERROR - U, V, S = _backend.idzp_rsvd(eps, m, n, matveca, matvec) + U, S, V = _backend.idzp_rsvd(A, eps, rng=rng) else: k = int(eps_or_k) if real: - U, V, S = _backend.iddr_rsvd(m, n, matveca, matvec, k) + U, S, V = _backend.iddr_rsvd(A, k, rng=rng) else: - if _IS_32BIT: - raise _32BIT_ERROR - U, V, S = _backend.idzr_rsvd(m, n, matveca, matvec, k) + U, S, V = _backend.idzr_rsvd(A, k, rng=rng) else: raise _TYPE_ERROR return U, S, V -def estimate_rank(A, eps): +def estimate_rank(A, eps, rng=None): """ Estimate matrix rank to a specified relative precision using randomized methods. @@ -997,19 +963,17 @@ def estimate_rank(A, eps): if isinstance(A, np.ndarray): if real: - rank = _backend.idd_estrank(eps, A) + rank, _ = _backend.idd_estrank(A, eps, rng=rng) else: - rank = _backend.idz_estrank(eps, A) + rank, _ = _backend.idz_estrank(A, eps, rng=rng) if rank == 0: # special return value for nearly full rank rank = min(A.shape) return rank elif isinstance(A, LinearOperator): - m, n = A.shape - matveca = A.rmatvec if real: - return _backend.idd_findrank(eps, m, n, matveca) + return _backend.idd_findrank(A, eps, rng=rng)[0] else: - return _backend.idz_findrank(eps, m, n, matveca) + return _backend.idz_findrank(A, eps, rng=rng)[0] else: raise _TYPE_ERROR From d1f5e4fa23803d1b5acb713f7fa6d2179887db0b Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Fri, 14 Jun 2024 01:38:56 +0200 Subject: [PATCH 408/500] TST:linalg: Adjust tests for the id_dist translation --- scipy/linalg/tests/test_interpolative.py | 78 ++++++++---------------- 1 file changed, 26 insertions(+), 52 deletions(-) diff --git a/scipy/linalg/tests/test_interpolative.py b/scipy/linalg/tests/test_interpolative.py index ddc56f7c7366..95b83dfad766 100644 --- a/scipy/linalg/tests/test_interpolative.py +++ b/scipy/linalg/tests/test_interpolative.py @@ -1,4 +1,4 @@ -#****************************************************************************** +# ****************************************************************************** # Copyright (C) 2013 Kenneth L. Ho # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -24,7 +24,7 @@ # 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 scipy.linalg.interpolative as pymatrixid import numpy as np @@ -36,8 +36,6 @@ assert_array_equal) import pytest from pytest import raises as assert_raises -import sys -_IS_32BIT = (sys.maxsize < 2**32) @pytest.fixture() @@ -45,6 +43,12 @@ def eps(): yield 1e-12 +@pytest.fixture() +def rng(): + rng = np.random.default_rng(1718313768084012) + yield rng + + @pytest.fixture(params=[np.float64, np.complex128]) def A(request): # construct Hilbert matrix @@ -73,36 +77,32 @@ class TestInterpolativeDecomposition: @pytest.mark.parametrize( "rand,lin_op", [(False, False), (True, False), (True, True)]) - def test_real_id_fixed_precision(self, A, L, eps, rand, lin_op): - if _IS_32BIT and A.dtype == np.complex128 and rand: - pytest.xfail("bug in external fortran code") + def test_real_id_fixed_precision(self, A, L, eps, rand, lin_op, rng): # Test ID routines on a Hilbert matrix. A_or_L = A if not lin_op else L - k, idx, proj = pymatrixid.interp_decomp(A_or_L, eps, rand=rand) + k, idx, proj = pymatrixid.interp_decomp(A_or_L, eps, rand=rand, rng=rng) B = pymatrixid.reconstruct_matrix_from_id(A[:, idx[:k]], idx, proj) assert_allclose(A, B, rtol=eps, atol=1e-08) @pytest.mark.parametrize( "rand,lin_op", [(False, False), (True, False), (True, True)]) - def test_real_id_fixed_rank(self, A, L, eps, rank, rand, lin_op): - if _IS_32BIT and A.dtype == np.complex128 and rand: - pytest.xfail("bug in external fortran code") + def test_real_id_fixed_rank(self, A, L, eps, rank, rand, lin_op, rng): k = rank A_or_L = A if not lin_op else L - idx, proj = pymatrixid.interp_decomp(A_or_L, k, rand=rand) + idx, proj = pymatrixid.interp_decomp(A_or_L, k, rand=rand, rng=rng) B = pymatrixid.reconstruct_matrix_from_id(A[:, idx[:k]], idx, proj) assert_allclose(A, B, rtol=eps, atol=1e-08) @pytest.mark.parametrize("rand,lin_op", [(False, False)]) def test_real_id_skel_and_interp_matrices( - self, A, L, eps, rank, rand, lin_op): + self, A, L, eps, rank, rand, lin_op, rng): k = rank A_or_L = A if not lin_op else L - idx, proj = pymatrixid.interp_decomp(A_or_L, k, rand=rand) + idx, proj = pymatrixid.interp_decomp(A_or_L, k, rand=rand, rng=rng) P = pymatrixid.reconstruct_interp_matrix(idx, proj) B = pymatrixid.reconstruct_skel_matrix(A, k, idx) assert_allclose(B, A[:, idx[:k]], rtol=eps, atol=1e-08) @@ -111,25 +111,21 @@ def test_real_id_skel_and_interp_matrices( @pytest.mark.parametrize( "rand,lin_op", [(False, False), (True, False), (True, True)]) - def test_svd_fixed_precison(self, A, L, eps, rand, lin_op): - if _IS_32BIT and A.dtype == np.complex128 and rand: - pytest.xfail("bug in external fortran code") + def test_svd_fixed_precision(self, A, L, eps, rand, lin_op, rng): A_or_L = A if not lin_op else L - U, S, V = pymatrixid.svd(A_or_L, eps, rand=rand) + U, S, V = pymatrixid.svd(A_or_L, eps, rand=rand, rng=rng) B = U * S @ V.T.conj() assert_allclose(A, B, rtol=eps, atol=1e-08) @pytest.mark.parametrize( "rand,lin_op", [(False, False), (True, False), (True, True)]) - def test_svd_fixed_rank(self, A, L, eps, rank, rand, lin_op): - if _IS_32BIT and A.dtype == np.complex128 and rand: - pytest.xfail("bug in external fortran code") + def test_svd_fixed_rank(self, A, L, eps, rank, rand, lin_op, rng): k = rank A_or_L = A if not lin_op else L - U, S, V = pymatrixid.svd(A_or_L, k, rand=rand) + U, S, V = pymatrixid.svd(A_or_L, k, rand=rand, rng=rng) B = U * S @ V.T.conj() assert_allclose(A, B, rtol=eps, atol=1e-08) @@ -141,59 +137,39 @@ def test_id_to_svd(self, A, eps, rank): B = U * S @ V.T.conj() assert_allclose(A, B, rtol=eps, atol=1e-08) - def test_estimate_spectral_norm(self, A): + def test_estimate_spectral_norm(self, A, rng): s = svdvals(A) - norm_2_est = pymatrixid.estimate_spectral_norm(A) + norm_2_est = pymatrixid.estimate_spectral_norm(A, rng=rng) assert_allclose(norm_2_est, s[0], rtol=1e-6, atol=1e-8) - def test_estimate_spectral_norm_diff(self, A): + def test_estimate_spectral_norm_diff(self, A, rng): B = A.copy() B[:, 0] *= 1.2 s = svdvals(A - B) - norm_2_est = pymatrixid.estimate_spectral_norm_diff(A, B) + norm_2_est = pymatrixid.estimate_spectral_norm_diff(A, B, rng=rng) assert_allclose(norm_2_est, s[0], rtol=1e-6, atol=1e-8) - def test_rank_estimates_array(self, A): + def test_rank_estimates_array(self, A, rng): B = np.array([[1, 1, 0], [0, 0, 1], [0, 0, 1]], dtype=A.dtype) for M in [A, B]: rank_tol = 1e-9 rank_np = np.linalg.matrix_rank(M, norm(M, 2) * rank_tol) - rank_est = pymatrixid.estimate_rank(M, rank_tol) + rank_est = pymatrixid.estimate_rank(M, rank_tol, rng=rng) assert_(rank_est >= rank_np) assert_(rank_est <= rank_np + 10) - def test_rank_estimates_lin_op(self, A): + def test_rank_estimates_lin_op(self, A, rng): B = np.array([[1, 1, 0], [0, 0, 1], [0, 0, 1]], dtype=A.dtype) for M in [A, B]: ML = aslinearoperator(M) rank_tol = 1e-9 rank_np = np.linalg.matrix_rank(M, norm(M, 2) * rank_tol) - rank_est = pymatrixid.estimate_rank(ML, rank_tol) + rank_est = pymatrixid.estimate_rank(ML, rank_tol, rng=rng) assert_(rank_est >= rank_np - 4) assert_(rank_est <= rank_np + 4) - def test_rand(self): - pymatrixid.seed('default') - assert_allclose(pymatrixid.rand(2), [0.8932059, 0.64500803], - rtol=1e-4, atol=1e-8) - - pymatrixid.seed(1234) - x1 = pymatrixid.rand(2) - assert_allclose(x1, [0.7513823, 0.06861718], rtol=1e-4, atol=1e-8) - - np.random.seed(1234) - pymatrixid.seed() - x2 = pymatrixid.rand(2) - - np.random.seed(1234) - pymatrixid.seed(np.random.rand(55)) - x3 = pymatrixid.rand(2) - - assert_allclose(x1, x2) - assert_allclose(x1, x3) - def test_badcall(self): A = hilbert(5).astype(np.float32) with assert_raises(ValueError): @@ -228,8 +204,6 @@ def test_full_rank(self): @pytest.mark.parametrize("rand", [True, False]) @pytest.mark.parametrize("eps", [1, 0.1]) def test_bug_9793(self, dtype, rand, eps): - if _IS_32BIT and dtype == np.complex128 and rand: - pytest.xfail("bug in external fortran code") A = np.array([[-1, -1, -1, 0, 0, 0], [0, 0, 0, 1, 1, 1], [1, 0, 0, 1, 0, 0], From c7552b050bb137c99bfaa9ae92b37359a8457f27 Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Thu, 13 Jun 2024 14:16:53 +0200 Subject: [PATCH 409/500] MAINT:linalg:Remove fortran wrappers for id_dist [skip ci] --- scipy/linalg/_interpolative_backend.py | 1681 ------------------------ 1 file changed, 1681 deletions(-) delete mode 100644 scipy/linalg/_interpolative_backend.py diff --git a/scipy/linalg/_interpolative_backend.py b/scipy/linalg/_interpolative_backend.py deleted file mode 100644 index a9fd70948ef4..000000000000 --- a/scipy/linalg/_interpolative_backend.py +++ /dev/null @@ -1,1681 +0,0 @@ -#****************************************************************************** -# Copyright (C) 2013 Kenneth L. Ho -# -# 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. -# -# None of the names of the copyright holders 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. -#****************************************************************************** - -""" -Direct wrappers for Fortran `id_dist` backend. -""" - -import scipy.linalg._interpolative as _id -import numpy as np - -_RETCODE_ERROR = RuntimeError("nonzero return code") - - -def _asfortranarray_copy(A): - """ - Same as np.asfortranarray, but ensure a copy - """ - A = np.asarray(A) - if A.flags.f_contiguous: - A = A.copy(order="F") - else: - A = np.asfortranarray(A) - return A - - -#------------------------------------------------------------------------------ -# id_rand.f -#------------------------------------------------------------------------------ - -def id_srand(n): - """ - Generate standard uniform pseudorandom numbers via a very efficient lagged - Fibonacci method. - - :param n: - Number of pseudorandom numbers to generate. - :type n: int - - :return: - Pseudorandom numbers. - :rtype: :class:`numpy.ndarray` - """ - return _id.id_srand(n) - - -def id_srandi(t): - """ - Initialize seed values for :func:`id_srand` (any appropriately random - numbers will do). - - :param t: - Array of 55 seed values. - :type t: :class:`numpy.ndarray` - """ - t = np.asfortranarray(t) - _id.id_srandi(t) - - -def id_srando(): - """ - Reset seed values to their original values. - """ - _id.id_srando() - - -#------------------------------------------------------------------------------ -# idd_frm.f -#------------------------------------------------------------------------------ - -def idd_frm(n, w, x): - """ - Transform real vector via a composition of Rokhlin's random transform, - random subselection, and an FFT. - - In contrast to :func:`idd_sfrm`, this routine works best when the length of - the transformed vector is the power-of-two integer output by - :func:`idd_frmi`, or when the length is not specified but instead - determined a posteriori from the output. The returned transformed vector is - randomly permuted. - - :param n: - Greatest power-of-two integer satisfying ``n <= x.size`` as obtained from - :func:`idd_frmi`; `n` is also the length of the output vector. - :type n: int - :param w: - Initialization array constructed by :func:`idd_frmi`. - :type w: :class:`numpy.ndarray` - :param x: - Vector to be transformed. - :type x: :class:`numpy.ndarray` - - :return: - Transformed vector. - :rtype: :class:`numpy.ndarray` - """ - return _id.idd_frm(n, w, x) - - -def idd_sfrm(l, n, w, x): - """ - Transform real vector via a composition of Rokhlin's random transform, - random subselection, and an FFT. - - In contrast to :func:`idd_frm`, this routine works best when the length of - the transformed vector is known a priori. - - :param l: - Length of transformed vector, satisfying ``l <= n``. - :type l: int - :param n: - Greatest power-of-two integer satisfying ``n <= x.size`` as obtained from - :func:`idd_sfrmi`. - :type n: int - :param w: - Initialization array constructed by :func:`idd_sfrmi`. - :type w: :class:`numpy.ndarray` - :param x: - Vector to be transformed. - :type x: :class:`numpy.ndarray` - - :return: - Transformed vector. - :rtype: :class:`numpy.ndarray` - """ - return _id.idd_sfrm(l, n, w, x) - - -def idd_frmi(m): - """ - Initialize data for :func:`idd_frm`. - - :param m: - Length of vector to be transformed. - :type m: int - - :return: - Greatest power-of-two integer `n` satisfying ``n <= m``. - :rtype: int - :return: - Initialization array to be used by :func:`idd_frm`. - :rtype: :class:`numpy.ndarray` - """ - return _id.idd_frmi(m) - - -def idd_sfrmi(l, m): - """ - Initialize data for :func:`idd_sfrm`. - - :param l: - Length of output transformed vector. - :type l: int - :param m: - Length of the vector to be transformed. - :type m: int - - :return: - Greatest power-of-two integer `n` satisfying ``n <= m``. - :rtype: int - :return: - Initialization array to be used by :func:`idd_sfrm`. - :rtype: :class:`numpy.ndarray` - """ - return _id.idd_sfrmi(l, m) - - -#------------------------------------------------------------------------------ -# idd_id.f -#------------------------------------------------------------------------------ - -def iddp_id(eps, A): - """ - Compute ID of a real matrix to a specified relative precision. - - :param eps: - Relative precision. - :type eps: float - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - - :return: - Rank of ID. - :rtype: int - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - A = _asfortranarray_copy(A) - k, idx, rnorms = _id.iddp_id(eps, A) - n = A.shape[1] - proj = A.T.ravel()[:k*(n-k)].reshape((k, n-k), order='F') - return k, idx, proj - - -def iddr_id(A, k): - """ - Compute ID of a real matrix to a specified rank. - - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - :param k: - Rank of ID. - :type k: int - - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - A = _asfortranarray_copy(A) - idx, rnorms = _id.iddr_id(A, k) - n = A.shape[1] - proj = A.T.ravel()[:k*(n-k)].reshape((k, n-k), order='F') - return idx, proj - - -def idd_reconid(B, idx, proj): - """ - Reconstruct matrix from real ID. - - :param B: - Skeleton matrix. - :type B: :class:`numpy.ndarray` - :param idx: - Column index array. - :type idx: :class:`numpy.ndarray` - :param proj: - Interpolation coefficients. - :type proj: :class:`numpy.ndarray` - - :return: - Reconstructed matrix. - :rtype: :class:`numpy.ndarray` - """ - B = np.asfortranarray(B) - if proj.size > 0: - return _id.idd_reconid(B, idx, proj) - else: - return B[:, np.argsort(idx)] - - -def idd_reconint(idx, proj): - """ - Reconstruct interpolation matrix from real ID. - - :param idx: - Column index array. - :type idx: :class:`numpy.ndarray` - :param proj: - Interpolation coefficients. - :type proj: :class:`numpy.ndarray` - - :return: - Interpolation matrix. - :rtype: :class:`numpy.ndarray` - """ - return _id.idd_reconint(idx, proj) - - -def idd_copycols(A, k, idx): - """ - Reconstruct skeleton matrix from real ID. - - :param A: - Original matrix. - :type A: :class:`numpy.ndarray` - :param k: - Rank of ID. - :type k: int - :param idx: - Column index array. - :type idx: :class:`numpy.ndarray` - - :return: - Skeleton matrix. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - return _id.idd_copycols(A, k, idx) - - -#------------------------------------------------------------------------------ -# idd_id2svd.f -#------------------------------------------------------------------------------ - -def idd_id2svd(B, idx, proj): - """ - Convert real ID to SVD. - - :param B: - Skeleton matrix. - :type B: :class:`numpy.ndarray` - :param idx: - Column index array. - :type idx: :class:`numpy.ndarray` - :param proj: - Interpolation coefficients. - :type proj: :class:`numpy.ndarray` - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - B = np.asfortranarray(B) - U, V, S, ier = _id.idd_id2svd(B, idx, proj) - if ier: - raise _RETCODE_ERROR - return U, V, S - - -#------------------------------------------------------------------------------ -# idd_snorm.f -#------------------------------------------------------------------------------ - -def idd_snorm(m, n, matvect, matvec, its=20): - """ - Estimate spectral norm of a real matrix by the randomized power method. - - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matvect: - Function to apply the matrix transpose to a vector, with call signature - `y = matvect(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvect: function - :param matvec: - Function to apply the matrix to a vector, with call signature - `y = matvec(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvec: function - :param its: - Number of power method iterations. - :type its: int - - :return: - Spectral norm estimate. - :rtype: float - """ - snorm, v = _id.idd_snorm(m, n, matvect, matvec, its) - return snorm - - -def idd_diffsnorm(m, n, matvect, matvect2, matvec, matvec2, its=20): - """ - Estimate spectral norm of the difference of two real matrices by the - randomized power method. - - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matvect: - Function to apply the transpose of the first matrix to a vector, with - call signature `y = matvect(x)`, where `x` and `y` are the input and - output vectors, respectively. - :type matvect: function - :param matvect2: - Function to apply the transpose of the second matrix to a vector, with - call signature `y = matvect2(x)`, where `x` and `y` are the input and - output vectors, respectively. - :type matvect2: function - :param matvec: - Function to apply the first matrix to a vector, with call signature - `y = matvec(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvec: function - :param matvec2: - Function to apply the second matrix to a vector, with call signature - `y = matvec2(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvec2: function - :param its: - Number of power method iterations. - :type its: int - - :return: - Spectral norm estimate of matrix difference. - :rtype: float - """ - return _id.idd_diffsnorm(m, n, matvect, matvect2, matvec, matvec2, its) - - -#------------------------------------------------------------------------------ -# idd_svd.f -#------------------------------------------------------------------------------ - -def iddr_svd(A, k): - """ - Compute SVD of a real matrix to a specified rank. - - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - :param k: - Rank of SVD. - :type k: int - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - U, V, S, ier = _id.iddr_svd(A, k) - if ier: - raise _RETCODE_ERROR - return U, V, S - - -def iddp_svd(eps, A): - """ - Compute SVD of a real matrix to a specified relative precision. - - :param eps: - Relative precision. - :type eps: float - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - m, n = A.shape - k, iU, iV, iS, w, ier = _id.iddp_svd(eps, A) - if ier: - raise _RETCODE_ERROR - U = w[iU-1:iU+m*k-1].reshape((m, k), order='F') - V = w[iV-1:iV+n*k-1].reshape((n, k), order='F') - S = w[iS-1:iS+k-1] - return U, V, S - - -#------------------------------------------------------------------------------ -# iddp_aid.f -#------------------------------------------------------------------------------ - -def iddp_aid(eps, A): - """ - Compute ID of a real matrix to a specified relative precision using random - sampling. - - :param eps: - Relative precision. - :type eps: float - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - - :return: - Rank of ID. - :rtype: int - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - m, n = A.shape - n2, w = idd_frmi(m) - proj = np.empty(n*(2*n2 + 1) + n2 + 1, order='F') - k, idx, proj = _id.iddp_aid(eps, A, w, proj) - proj = proj[:k*(n-k)].reshape((k, n-k), order='F') - return k, idx, proj - - -def idd_estrank(eps, A): - """ - Estimate rank of a real matrix to a specified relative precision using - random sampling. - - The output rank is typically about 8 higher than the actual rank. - - :param eps: - Relative precision. - :type eps: float - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - - :return: - Rank estimate. - :rtype: int - """ - A = np.asfortranarray(A) - m, n = A.shape - n2, w = idd_frmi(m) - ra = np.empty(n*n2 + (n + 1)*(n2 + 1), order='F') - k, ra = _id.idd_estrank(eps, A, w, ra) - return k - - -#------------------------------------------------------------------------------ -# iddp_asvd.f -#------------------------------------------------------------------------------ - -def iddp_asvd(eps, A): - """ - Compute SVD of a real matrix to a specified relative precision using random - sampling. - - :param eps: - Relative precision. - :type eps: float - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - m, n = A.shape - n2, winit = _id.idd_frmi(m) - w = np.empty( - max((min(m, n) + 1)*(3*m + 5*n + 1) + 25*min(m, n)**2, - (2*n + 1)*(n2 + 1)), - order='F') - k, iU, iV, iS, w, ier = _id.iddp_asvd(eps, A, winit, w) - if ier: - raise _RETCODE_ERROR - U = w[iU-1:iU+m*k-1].reshape((m, k), order='F') - V = w[iV-1:iV+n*k-1].reshape((n, k), order='F') - S = w[iS-1:iS+k-1] - return U, V, S - - -#------------------------------------------------------------------------------ -# iddp_rid.f -#------------------------------------------------------------------------------ - -def iddp_rid(eps, m, n, matvect): - """ - Compute ID of a real matrix to a specified relative precision using random - matrix-vector multiplication. - - :param eps: - Relative precision. - :type eps: float - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matvect: - Function to apply the matrix transpose to a vector, with call signature - `y = matvect(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvect: function - - :return: - Rank of ID. - :rtype: int - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - proj = np.empty(m + 1 + 2*n*(min(m, n) + 1), order='F') - k, idx, proj, ier = _id.iddp_rid(eps, m, n, matvect, proj) - if ier != 0: - raise _RETCODE_ERROR - proj = proj[:k*(n-k)].reshape((k, n-k), order='F') - return k, idx, proj - - -def idd_findrank(eps, m, n, matvect): - """ - Estimate rank of a real matrix to a specified relative precision using - random matrix-vector multiplication. - - :param eps: - Relative precision. - :type eps: float - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matvect: - Function to apply the matrix transpose to a vector, with call signature - `y = matvect(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvect: function - - :return: - Rank estimate. - :rtype: int - """ - k, ra, ier = _id.idd_findrank(eps, m, n, matvect) - if ier: - raise _RETCODE_ERROR - return k - - -#------------------------------------------------------------------------------ -# iddp_rsvd.f -#------------------------------------------------------------------------------ - -def iddp_rsvd(eps, m, n, matvect, matvec): - """ - Compute SVD of a real matrix to a specified relative precision using random - matrix-vector multiplication. - - :param eps: - Relative precision. - :type eps: float - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matvect: - Function to apply the matrix transpose to a vector, with call signature - `y = matvect(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvect: function - :param matvec: - Function to apply the matrix to a vector, with call signature - `y = matvec(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvec: function - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - k, iU, iV, iS, w, ier = _id.iddp_rsvd(eps, m, n, matvect, matvec) - if ier: - raise _RETCODE_ERROR - U = w[iU-1:iU+m*k-1].reshape((m, k), order='F') - V = w[iV-1:iV+n*k-1].reshape((n, k), order='F') - S = w[iS-1:iS+k-1] - return U, V, S - - -#------------------------------------------------------------------------------ -# iddr_aid.f -#------------------------------------------------------------------------------ - -def iddr_aid(A, k): - """ - Compute ID of a real matrix to a specified rank using random sampling. - - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - :param k: - Rank of ID. - :type k: int - - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - m, n = A.shape - w = iddr_aidi(m, n, k) - idx, proj = _id.iddr_aid(A, k, w) - if k == n: - proj = np.empty((k, n-k), dtype='float64', order='F') - else: - proj = proj.reshape((k, n-k), order='F') - return idx, proj - - -def iddr_aidi(m, n, k): - """ - Initialize array for :func:`iddr_aid`. - - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param k: - Rank of ID. - :type k: int - - :return: - Initialization array to be used by :func:`iddr_aid`. - :rtype: :class:`numpy.ndarray` - """ - return _id.iddr_aidi(m, n, k) - - -#------------------------------------------------------------------------------ -# iddr_asvd.f -#------------------------------------------------------------------------------ - -def iddr_asvd(A, k): - """ - Compute SVD of a real matrix to a specified rank using random sampling. - - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - :param k: - Rank of SVD. - :type k: int - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - m, n = A.shape - w = np.empty((2*k + 28)*m + (6*k + 21)*n + 25*k**2 + 100, order='F') - w_ = iddr_aidi(m, n, k) - w[:w_.size] = w_ - U, V, S, ier = _id.iddr_asvd(A, k, w) - if ier != 0: - raise _RETCODE_ERROR - return U, V, S - - -#------------------------------------------------------------------------------ -# iddr_rid.f -#------------------------------------------------------------------------------ - -def iddr_rid(m, n, matvect, k): - """ - Compute ID of a real matrix to a specified rank using random matrix-vector - multiplication. - - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matvect: - Function to apply the matrix transpose to a vector, with call signature - `y = matvect(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvect: function - :param k: - Rank of ID. - :type k: int - - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - idx, proj = _id.iddr_rid(m, n, matvect, k) - proj = proj[:k*(n-k)].reshape((k, n-k), order='F') - return idx, proj - - -#------------------------------------------------------------------------------ -# iddr_rsvd.f -#------------------------------------------------------------------------------ - -def iddr_rsvd(m, n, matvect, matvec, k): - """ - Compute SVD of a real matrix to a specified rank using random matrix-vector - multiplication. - - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matvect: - Function to apply the matrix transpose to a vector, with call signature - `y = matvect(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvect: function - :param matvec: - Function to apply the matrix to a vector, with call signature - `y = matvec(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvec: function - :param k: - Rank of SVD. - :type k: int - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - U, V, S, ier = _id.iddr_rsvd(m, n, matvect, matvec, k) - if ier != 0: - raise _RETCODE_ERROR - return U, V, S - - -#------------------------------------------------------------------------------ -# idz_frm.f -#------------------------------------------------------------------------------ - -def idz_frm(n, w, x): - """ - Transform complex vector via a composition of Rokhlin's random transform, - random subselection, and an FFT. - - In contrast to :func:`idz_sfrm`, this routine works best when the length of - the transformed vector is the power-of-two integer output by - :func:`idz_frmi`, or when the length is not specified but instead - determined a posteriori from the output. The returned transformed vector is - randomly permuted. - - :param n: - Greatest power-of-two integer satisfying ``n <= x.size`` as obtained from - :func:`idz_frmi`; `n` is also the length of the output vector. - :type n: int - :param w: - Initialization array constructed by :func:`idz_frmi`. - :type w: :class:`numpy.ndarray` - :param x: - Vector to be transformed. - :type x: :class:`numpy.ndarray` - - :return: - Transformed vector. - :rtype: :class:`numpy.ndarray` - """ - return _id.idz_frm(n, w, x) - - -def idz_sfrm(l, n, w, x): - """ - Transform complex vector via a composition of Rokhlin's random transform, - random subselection, and an FFT. - - In contrast to :func:`idz_frm`, this routine works best when the length of - the transformed vector is known a priori. - - :param l: - Length of transformed vector, satisfying ``l <= n``. - :type l: int - :param n: - Greatest power-of-two integer satisfying ``n <= x.size`` as obtained from - :func:`idz_sfrmi`. - :type n: int - :param w: - Initialization array constructed by :func:`idd_sfrmi`. - :type w: :class:`numpy.ndarray` - :param x: - Vector to be transformed. - :type x: :class:`numpy.ndarray` - - :return: - Transformed vector. - :rtype: :class:`numpy.ndarray` - """ - return _id.idz_sfrm(l, n, w, x) - - -def idz_frmi(m): - """ - Initialize data for :func:`idz_frm`. - - :param m: - Length of vector to be transformed. - :type m: int - - :return: - Greatest power-of-two integer `n` satisfying ``n <= m``. - :rtype: int - :return: - Initialization array to be used by :func:`idz_frm`. - :rtype: :class:`numpy.ndarray` - """ - return _id.idz_frmi(m) - - -def idz_sfrmi(l, m): - """ - Initialize data for :func:`idz_sfrm`. - - :param l: - Length of output transformed vector. - :type l: int - :param m: - Length of the vector to be transformed. - :type m: int - - :return: - Greatest power-of-two integer `n` satisfying ``n <= m``. - :rtype: int - :return: - Initialization array to be used by :func:`idz_sfrm`. - :rtype: :class:`numpy.ndarray` - """ - return _id.idz_sfrmi(l, m) - - -#------------------------------------------------------------------------------ -# idz_id.f -#------------------------------------------------------------------------------ - -def idzp_id(eps, A): - """ - Compute ID of a complex matrix to a specified relative precision. - - :param eps: - Relative precision. - :type eps: float - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - - :return: - Rank of ID. - :rtype: int - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - A = _asfortranarray_copy(A) - k, idx, rnorms = _id.idzp_id(eps, A) - n = A.shape[1] - proj = A.T.ravel()[:k*(n-k)].reshape((k, n-k), order='F') - return k, idx, proj - - -def idzr_id(A, k): - """ - Compute ID of a complex matrix to a specified rank. - - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - :param k: - Rank of ID. - :type k: int - - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - A = _asfortranarray_copy(A) - idx, rnorms = _id.idzr_id(A, k) - n = A.shape[1] - proj = A.T.ravel()[:k*(n-k)].reshape((k, n-k), order='F') - return idx, proj - - -def idz_reconid(B, idx, proj): - """ - Reconstruct matrix from complex ID. - - :param B: - Skeleton matrix. - :type B: :class:`numpy.ndarray` - :param idx: - Column index array. - :type idx: :class:`numpy.ndarray` - :param proj: - Interpolation coefficients. - :type proj: :class:`numpy.ndarray` - - :return: - Reconstructed matrix. - :rtype: :class:`numpy.ndarray` - """ - B = np.asfortranarray(B) - if proj.size > 0: - return _id.idz_reconid(B, idx, proj) - else: - return B[:, np.argsort(idx)] - - -def idz_reconint(idx, proj): - """ - Reconstruct interpolation matrix from complex ID. - - :param idx: - Column index array. - :type idx: :class:`numpy.ndarray` - :param proj: - Interpolation coefficients. - :type proj: :class:`numpy.ndarray` - - :return: - Interpolation matrix. - :rtype: :class:`numpy.ndarray` - """ - return _id.idz_reconint(idx, proj) - - -def idz_copycols(A, k, idx): - """ - Reconstruct skeleton matrix from complex ID. - - :param A: - Original matrix. - :type A: :class:`numpy.ndarray` - :param k: - Rank of ID. - :type k: int - :param idx: - Column index array. - :type idx: :class:`numpy.ndarray` - - :return: - Skeleton matrix. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - return _id.idz_copycols(A, k, idx) - - -#------------------------------------------------------------------------------ -# idz_id2svd.f -#------------------------------------------------------------------------------ - -def idz_id2svd(B, idx, proj): - """ - Convert complex ID to SVD. - - :param B: - Skeleton matrix. - :type B: :class:`numpy.ndarray` - :param idx: - Column index array. - :type idx: :class:`numpy.ndarray` - :param proj: - Interpolation coefficients. - :type proj: :class:`numpy.ndarray` - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - B = np.asfortranarray(B) - U, V, S, ier = _id.idz_id2svd(B, idx, proj) - if ier: - raise _RETCODE_ERROR - return U, V, S - - -#------------------------------------------------------------------------------ -# idz_snorm.f -#------------------------------------------------------------------------------ - -def idz_snorm(m, n, matveca, matvec, its=20): - """ - Estimate spectral norm of a complex matrix by the randomized power method. - - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matveca: - Function to apply the matrix adjoint to a vector, with call signature - `y = matveca(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matveca: function - :param matvec: - Function to apply the matrix to a vector, with call signature - `y = matvec(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvec: function - :param its: - Number of power method iterations. - :type its: int - - :return: - Spectral norm estimate. - :rtype: float - """ - snorm, v = _id.idz_snorm(m, n, matveca, matvec, its) - return snorm - - -def idz_diffsnorm(m, n, matveca, matveca2, matvec, matvec2, its=20): - """ - Estimate spectral norm of the difference of two complex matrices by the - randomized power method. - - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matveca: - Function to apply the adjoint of the first matrix to a vector, with - call signature `y = matveca(x)`, where `x` and `y` are the input and - output vectors, respectively. - :type matveca: function - :param matveca2: - Function to apply the adjoint of the second matrix to a vector, with - call signature `y = matveca2(x)`, where `x` and `y` are the input and - output vectors, respectively. - :type matveca2: function - :param matvec: - Function to apply the first matrix to a vector, with call signature - `y = matvec(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvec: function - :param matvec2: - Function to apply the second matrix to a vector, with call signature - `y = matvec2(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvec2: function - :param its: - Number of power method iterations. - :type its: int - - :return: - Spectral norm estimate of matrix difference. - :rtype: float - """ - return _id.idz_diffsnorm(m, n, matveca, matveca2, matvec, matvec2, its) - - -#------------------------------------------------------------------------------ -# idz_svd.f -#------------------------------------------------------------------------------ - -def idzr_svd(A, k): - """ - Compute SVD of a complex matrix to a specified rank. - - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - :param k: - Rank of SVD. - :type k: int - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - U, V, S, ier = _id.idzr_svd(A, k) - if ier: - raise _RETCODE_ERROR - return U, V, S - - -def idzp_svd(eps, A): - """ - Compute SVD of a complex matrix to a specified relative precision. - - :param eps: - Relative precision. - :type eps: float - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - m, n = A.shape - k, iU, iV, iS, w, ier = _id.idzp_svd(eps, A) - if ier: - raise _RETCODE_ERROR - U = w[iU-1:iU+m*k-1].reshape((m, k), order='F') - V = w[iV-1:iV+n*k-1].reshape((n, k), order='F') - S = w[iS-1:iS+k-1] - return U, V, S - - -#------------------------------------------------------------------------------ -# idzp_aid.f -#------------------------------------------------------------------------------ - -def idzp_aid(eps, A): - """ - Compute ID of a complex matrix to a specified relative precision using - random sampling. - - :param eps: - Relative precision. - :type eps: float - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - - :return: - Rank of ID. - :rtype: int - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - m, n = A.shape - n2, w = idz_frmi(m) - proj = np.empty(n*(2*n2 + 1) + n2 + 1, dtype='complex128', order='F') - k, idx, proj = _id.idzp_aid(eps, A, w, proj) - proj = proj[:k*(n-k)].reshape((k, n-k), order='F') - return k, idx, proj - - -def idz_estrank(eps, A): - """ - Estimate rank of a complex matrix to a specified relative precision using - random sampling. - - The output rank is typically about 8 higher than the actual rank. - - :param eps: - Relative precision. - :type eps: float - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - - :return: - Rank estimate. - :rtype: int - """ - A = np.asfortranarray(A) - m, n = A.shape - n2, w = idz_frmi(m) - ra = np.empty(n*n2 + (n + 1)*(n2 + 1), dtype='complex128', order='F') - k, ra = _id.idz_estrank(eps, A, w, ra) - return k - - -#------------------------------------------------------------------------------ -# idzp_asvd.f -#------------------------------------------------------------------------------ - -def idzp_asvd(eps, A): - """ - Compute SVD of a complex matrix to a specified relative precision using - random sampling. - - :param eps: - Relative precision. - :type eps: float - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - m, n = A.shape - n2, winit = _id.idz_frmi(m) - w = np.empty( - max((min(m, n) + 1)*(3*m + 5*n + 11) + 8*min(m, n)**2, - (2*n + 1)*(n2 + 1)), - dtype=np.complex128, order='F') - k, iU, iV, iS, w, ier = _id.idzp_asvd(eps, A, winit, w) - if ier: - raise _RETCODE_ERROR - U = w[iU-1:iU+m*k-1].reshape((m, k), order='F') - V = w[iV-1:iV+n*k-1].reshape((n, k), order='F') - S = w[iS-1:iS+k-1] - return U, V, S - - -#------------------------------------------------------------------------------ -# idzp_rid.f -#------------------------------------------------------------------------------ - -def idzp_rid(eps, m, n, matveca): - """ - Compute ID of a complex matrix to a specified relative precision using - random matrix-vector multiplication. - - :param eps: - Relative precision. - :type eps: float - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matveca: - Function to apply the matrix adjoint to a vector, with call signature - `y = matveca(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matveca: function - - :return: - Rank of ID. - :rtype: int - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - proj = np.empty( - m + 1 + 2*n*(min(m, n) + 1), - dtype=np.complex128, order='F') - k, idx, proj, ier = _id.idzp_rid(eps, m, n, matveca, proj) - if ier: - raise _RETCODE_ERROR - proj = proj[:k*(n-k)].reshape((k, n-k), order='F') - return k, idx, proj - - -def idz_findrank(eps, m, n, matveca): - """ - Estimate rank of a complex matrix to a specified relative precision using - random matrix-vector multiplication. - - :param eps: - Relative precision. - :type eps: float - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matveca: - Function to apply the matrix adjoint to a vector, with call signature - `y = matveca(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matveca: function - - :return: - Rank estimate. - :rtype: int - """ - k, ra, ier = _id.idz_findrank(eps, m, n, matveca) - if ier: - raise _RETCODE_ERROR - return k - - -#------------------------------------------------------------------------------ -# idzp_rsvd.f -#------------------------------------------------------------------------------ - -def idzp_rsvd(eps, m, n, matveca, matvec): - """ - Compute SVD of a complex matrix to a specified relative precision using - random matrix-vector multiplication. - - :param eps: - Relative precision. - :type eps: float - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matveca: - Function to apply the matrix adjoint to a vector, with call signature - `y = matveca(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matveca: function - :param matvec: - Function to apply the matrix to a vector, with call signature - `y = matvec(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvec: function - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - k, iU, iV, iS, w, ier = _id.idzp_rsvd(eps, m, n, matveca, matvec) - if ier: - raise _RETCODE_ERROR - U = w[iU-1:iU+m*k-1].reshape((m, k), order='F') - V = w[iV-1:iV+n*k-1].reshape((n, k), order='F') - S = w[iS-1:iS+k-1] - return U, V, S - - -#------------------------------------------------------------------------------ -# idzr_aid.f -#------------------------------------------------------------------------------ - -def idzr_aid(A, k): - """ - Compute ID of a complex matrix to a specified rank using random sampling. - - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - :param k: - Rank of ID. - :type k: int - - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - m, n = A.shape - w = idzr_aidi(m, n, k) - idx, proj = _id.idzr_aid(A, k, w) - if k == n: - proj = np.empty((k, n-k), dtype='complex128', order='F') - else: - proj = proj.reshape((k, n-k), order='F') - return idx, proj - - -def idzr_aidi(m, n, k): - """ - Initialize array for :func:`idzr_aid`. - - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param k: - Rank of ID. - :type k: int - - :return: - Initialization array to be used by :func:`idzr_aid`. - :rtype: :class:`numpy.ndarray` - """ - return _id.idzr_aidi(m, n, k) - - -#------------------------------------------------------------------------------ -# idzr_asvd.f -#------------------------------------------------------------------------------ - -def idzr_asvd(A, k): - """ - Compute SVD of a complex matrix to a specified rank using random sampling. - - :param A: - Matrix. - :type A: :class:`numpy.ndarray` - :param k: - Rank of SVD. - :type k: int - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - A = np.asfortranarray(A) - m, n = A.shape - w = np.empty( - (2*k + 22)*m + (6*k + 21)*n + 8*k**2 + 10*k + 90, - dtype='complex128', order='F') - w_ = idzr_aidi(m, n, k) - w[:w_.size] = w_ - U, V, S, ier = _id.idzr_asvd(A, k, w) - if ier: - raise _RETCODE_ERROR - return U, V, S - - -#------------------------------------------------------------------------------ -# idzr_rid.f -#------------------------------------------------------------------------------ - -def idzr_rid(m, n, matveca, k): - """ - Compute ID of a complex matrix to a specified rank using random - matrix-vector multiplication. - - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matveca: - Function to apply the matrix adjoint to a vector, with call signature - `y = matveca(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matveca: function - :param k: - Rank of ID. - :type k: int - - :return: - Column index array. - :rtype: :class:`numpy.ndarray` - :return: - Interpolation coefficients. - :rtype: :class:`numpy.ndarray` - """ - idx, proj = _id.idzr_rid(m, n, matveca, k) - proj = proj[:k*(n-k)].reshape((k, n-k), order='F') - return idx, proj - - -#------------------------------------------------------------------------------ -# idzr_rsvd.f -#------------------------------------------------------------------------------ - -def idzr_rsvd(m, n, matveca, matvec, k): - """ - Compute SVD of a complex matrix to a specified rank using random - matrix-vector multiplication. - - :param m: - Matrix row dimension. - :type m: int - :param n: - Matrix column dimension. - :type n: int - :param matveca: - Function to apply the matrix adjoint to a vector, with call signature - `y = matveca(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matveca: function - :param matvec: - Function to apply the matrix to a vector, with call signature - `y = matvec(x)`, where `x` and `y` are the input and output vectors, - respectively. - :type matvec: function - :param k: - Rank of SVD. - :type k: int - - :return: - Left singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Right singular vectors. - :rtype: :class:`numpy.ndarray` - :return: - Singular values. - :rtype: :class:`numpy.ndarray` - """ - U, V, S, ier = _id.idzr_rsvd(m, n, matveca, matvec, k) - if ier: - raise _RETCODE_ERROR - return U, V, S From 8a16c57a6eb5e50334f6f23c445f41f7b6999b83 Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Fri, 14 Jun 2024 19:54:39 +0200 Subject: [PATCH 410/500] MAINT:linalg:Modify mypy.ini for interpolative Cython code --- mypy.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy.ini b/mypy.ini index 4417af39dca8..4bdbdf975097 100644 --- a/mypy.ini +++ b/mypy.ini @@ -140,7 +140,7 @@ ignore_missing_imports = True [mypy-scipy.linalg._solve_toeplitz] ignore_missing_imports = True -[mypy-scipy.linalg._interpolative] +[mypy-scipy.linalg._decomp_interpolative] ignore_missing_imports = True [mypy-scipy.optimize._group_columns] From 0be2e130d4dc946fb1f4ae5e164d6a3428584139 Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Fri, 14 Jun 2024 19:55:19 +0200 Subject: [PATCH 411/500] DOC:linalg: Adjust interpolative docs due to new Cython code DOC:linalg: Fix grammar and typos --- scipy/linalg/interpolative.py | 84 +++++++++++++++-------------------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/scipy/linalg/interpolative.py b/scipy/linalg/interpolative.py index b5c6cdc670e4..f12942887209 100644 --- a/scipy/linalg/interpolative.py +++ b/scipy/linalg/interpolative.py @@ -27,17 +27,17 @@ # POSSIBILITY OF SUCH DAMAGE. # ****************************************************************************** -# Python module for interfacing with `id_dist`. - r""" ====================================================================== Interpolative matrix decomposition (:mod:`scipy.linalg.interpolative`) ====================================================================== -.. moduleauthor:: Kenneth L. Ho - .. versionadded:: 0.13 +.. versionchanged:: 1.15.0 + The underlying algorithms have been ported to Python from the original Fortran77 + code. See references below for more details. + .. currentmodule:: scipy.linalg.interpolative An interpolative decomposition (ID) of a matrix :math:`A \in @@ -94,7 +94,7 @@ estimate_spectral_norm_diff estimate_rank -Support functions: +Following support functions are deprecated and will be removed in SciPy 1.17.0: .. autosummary:: :toctree: generated/ @@ -106,16 +106,13 @@ References ========== -This module uses the ID software package [1]_ by Martinsson, Rokhlin, -Shkolnisky, and Tygert, which is a Fortran library for computing IDs -using various algorithms, including the rank-revealing QR approach of -[2]_ and the more recent randomized methods described in [3]_, [4]_, -and [5]_. This module exposes its functionality in a way convenient -for Python users. Note that this module does not add any functionality -beyond that of organizing a simpler and more consistent interface. +This module uses the algorithms found in ID software package [1]_ by Martinsson, +Rokhlin, Shkolnisky, and Tygert, which is a Fortran library for computing IDs using +various algorithms, including the rank-revealing QR approach of [2]_ and the more +recent randomized methods described in [3]_, [4]_, and [5]_. -We advise the user to consult also the `documentation for the ID package -`_. +We advise the user to consult also the documentation for the `ID package +`_. .. [1] P.G. Martinsson, V. Rokhlin, Y. Shkolnisky, M. Tygert. "ID: a software package for low-rank approximation of matrices via interpolative @@ -356,25 +353,8 @@ of the numerical rank. Finally, the random number generation required for all randomized routines can -be controlled via :func:`scipy.linalg.interpolative.seed`. To reset the seed -values to their original values, use: - ->>> sli.seed('default') - -To specify the seed values, use: - ->>> s = 42 ->>> sli.seed(s) - -where ``s`` must be an integer or array of 55 floats. If an integer, the array -of floats is obtained by using ``numpy.random.rand`` with the given integer -seed. - -To simply generate some random numbers, type: - ->>> arr = sli.rand(n) - -where ``n`` is the number of random numbers to generate. +be controlled via providing NumPy pseudo-random generators with a fixed seed. See +:class:`numpy.random.Generator` and :func:`numpy.random.default_rng` for more details. Remarks ------- @@ -433,31 +413,29 @@ def _is_real(A): def seed(seed=None): """ - This function used to set the seed of the randomization algorithms used in the - `scipy.linalg.interpolative` functions written in Fortran77. + This function, historically, used to set the seed of the randomization algorithms + used in the `scipy.linalg.interpolative` functions written in Fortran77. The library has been ported to Python and now the functions use the native NumPy generators and this function has no content and returns None. Thus this function should not be used and will be removed in SciPy version 1.17.0. """ warnings.warn("`scipy.linalg.interpolative.seed` is deprecated and will be " - "removed in SciPy 1.17.0.", DeprecationWarning, stacklevel=2) + "removed in SciPy 1.17.0.", DeprecationWarning, stacklevel=3) def rand(*shape): """ - This function used to set the seed of the randomization algorithms used in the - `scipy.linalg.interpolative` functions written in Fortran77. + This function, historically, used to generate uniformly distributed random number + for the randomization algorithms used in the `scipy.linalg.interpolative` functions + written in Fortran77. The library has been ported to Python and now the functions use the native NumPy generators. Thus this function should not be used and will be removed in the SciPy version 1.17.0. - If pseudo-random numbers are needed, NumPy should be used instead: - - >>> import numpy as np - >>> rng = np.random.default_rng() - >>> rng.uniform(low=0., high=1.0, size=shape) + If pseudo-random numbers are needed, NumPy pseudo-random generators should be used + instead. Parameters ---------- @@ -466,7 +444,7 @@ def rand(*shape): """ warnings.warn("`scipy.linalg.interpolative.rand` is deprecated and will be " - "removed in SciPy 1.17.0.", DeprecationWarning, stacklevel=2) + "removed in SciPy 1.17.0.", DeprecationWarning, stacklevel=3) rng = np.random.default_rng() return rng.uniform(low=0., high=1.0, size=shape) @@ -776,6 +754,8 @@ def estimate_spectral_norm(A, its=20, rng=None): `matvec` and `rmatvec` methods (to apply the matrix and its adjoint). its : int, optional Number of power method iterations. + rng : :class:`numpy.random.Generator` + NumPy generator for the randomization steps in the algorithm. Returns ------- @@ -810,6 +790,8 @@ def estimate_spectral_norm_diff(A, B, its=20, rng=None): the `matvec` and `rmatvec` methods (to apply the matrix and its adjoint). its : int, optional Number of power method iterations. + rng : :class:`numpy.random.Generator` + NumPy generator for the randomization steps in the algorithm. Returns ------- @@ -832,7 +814,7 @@ def svd(A, eps_or_k, rand=True, rng=None): An SVD of a matrix `A` is a factorization:: - A = numpy.dot(U, numpy.dot(numpy.diag(S), V.conj().T)) + A = U @ np.diag(S) @ V.conj().T where `U` and `V` have orthonormal columns and `S` is nonnegative. @@ -863,15 +845,18 @@ def svd(A, eps_or_k, rand=True, rng=None): Whether to use random sampling if `A` is of type :class:`numpy.ndarray` (randomized algorithms are always used if `A` is of type :class:`scipy.sparse.linalg.LinearOperator`). + rng : :class:`numpy.random.Generator` + NumPy generator for the randomization steps in the algorithm. If ``rand`` is + ``False``, the argument is ignored. Returns ------- U : :class:`numpy.ndarray` - Left singular vectors. + 2D array of left singular vectors. S : :class:`numpy.ndarray` - Singular values. + 1D array of singular values. V : :class:`numpy.ndarray` - Right singular vectors. + 2D array right singular vectors. """ from scipy.sparse.linalg import LinearOperator @@ -951,6 +936,8 @@ def estimate_rank(A, eps, rng=None): with the `rmatvec` method (to apply the matrix adjoint). eps : float Relative error for numerical rank definition. + rng : :class:`numpy.random.Generator` + NumPy generator for the randomization steps in the algorithm. Returns ------- @@ -962,6 +949,7 @@ def estimate_rank(A, eps, rng=None): real = _is_real(A) if isinstance(A, np.ndarray): + A = _C_contiguous_copy(A) if real: rank, _ = _backend.idd_estrank(A, eps, rng=rng) else: From 07e9ffdf75d2d651a6f4f948c59c47206d7a254a Mon Sep 17 00:00:00 2001 From: "H. Vetinari" Date: Fri, 14 Jun 2024 22:14:09 +1100 Subject: [PATCH 412/500] TST: robustify test_nnls_inner_loop_case1 --- scipy/optimize/tests/test_nnls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/tests/test_nnls.py b/scipy/optimize/tests/test_nnls.py index 69b0f186a5af..aa4956febd84 100644 --- a/scipy/optimize/tests/test_nnls.py +++ b/scipy/optimize/tests/test_nnls.py @@ -97,7 +97,7 @@ def test_nnls_inner_loop_case1(self): # Small perturbations can already make the infinite loop go away (just # uncomment the next line) - # k = k + 1e-10 * np.random.normal(size=N) + k = k + 1e-10 * np.random.normal(size=N) dact, _ = nnls(W @ A, W @ k) assert_allclose(dact, d, rtol=0., atol=1e-10) From fb296a94c197b5e0175ed1b91d902b2286c656a0 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 14 Jun 2024 23:09:29 -0400 Subject: [PATCH 413/500] ENH: stats.xp_var: array-API compatible variance with scipy.stats interface --- scipy/stats/_stats_py.py | 40 ++++++++- scipy/stats/tests/test_axis_nan_policy.py | 6 ++ scipy/stats/tests/test_stats.py | 105 +++++++++++++++++++++- 3 files changed, 147 insertions(+), 4 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index e9c8521639da..0b95d7f8f76b 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -41,7 +41,7 @@ from scipy.optimize import milp, LinearConstraint from scipy._lib._util import (check_random_state, _get_nan, _rename_parameter, _contains_nan, - AxisError) + AxisError, _lazywhere) import scipy.special as special # Import unused here but needs to stay until end of deprecation periode @@ -1086,7 +1086,7 @@ def _moment(a, order, axis, *, mean=None, xp=None): keepdims=True) / xp.abs(mean) with np.errstate(invalid='ignore'): precision_loss = xp.any(rel_diff < eps) - n = a.shape[axis] if axis is not None else a.size + n = a.shape[axis] if axis is not None else xp_size(a) if precision_loss and n > 1: message = ("Precision loss occurred in moment calculation due to " "catastrophic cancellation. This occurs when the data " @@ -1111,7 +1111,7 @@ def _var(x, axis=0, ddof=0, mean=None, xp=None): xp = array_namespace(x) if xp is None else xp var = _moment(x, 2, axis, mean=mean, xp=xp) if ddof != 0: - n = x.shape[axis] if axis is not None else x.size + n = x.shape[axis] if axis is not None else xp_size(x) var *= np.divide(n, n-ddof) # to avoid error on division by zero return var @@ -11102,6 +11102,40 @@ def _xp_mean(x, /, *, axis=None, weights=None, keepdims=False, nan_policy='propa return res[()] if res.ndim == 0 else res +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 + xp = array_namespace(x) if xp is None else xp + x = xp.asarray(x) + + # 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 = xp.asarray(x, dtype=mean.dtype) + var = _xp_mean((x - mean)**2, keepdims=keepdims, **kwargs) + + if correction != 0: + n = (xp_size(x) if axis is None + # compact way to deal with axis tuples or ints + else np.prod(np.asarray(x.shape)[np.asarray(axis)])) + n = xp.asarray(n, dtype=var.dtype) + + 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 + factor = _lazywhere(n-correction > 0, (n, n-correction), xp.divide, 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 diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index 786cde6362e1..a12910de2ed1 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -51,6 +51,11 @@ def xp_mean_2samp(*args, **kwargs): 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) + + axis_nan_policy_cases = [ # function, args, kwds, number of samples, number of outputs, # ... paired, unpacker function @@ -128,6 +133,7 @@ def xp_mean_2samp(*args, **kwargs): (stats.combine_pvalues, tuple(), {}, 1, 2, False, 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,)), ] # If the message is one of those expected, put nans in diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index d6a701037f00..fd9154b6b2b6 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -38,7 +38,7 @@ too_small_nd_omit, too_small_nd_not_omit, too_small_1d_omit, too_small_1d_not_omit) from scipy.stats._stats_py import (_permutation_distribution_t, _chk_asarray, _moment, - LinregressResult, _xp_mean) + LinregressResult, _xp_mean, _xp_var) from scipy._lib._util import AxisError from scipy.conftest import array_api_compatible, skip_xp_invalid_arg from scipy._lib._array_api import (xp_assert_close, xp_assert_equal, array_namespace, @@ -9218,6 +9218,109 @@ def test_integer(self, xp): xp_assert_equal(_xp_mean(y, weights=x), _xp_mean(y, weights=y)) +@array_api_compatible +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 suppress_warnings() as sup: + sup.filter(RuntimeWarning, "Degrees of freedom <= 0 for slice") + 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.asarray(xp.nan), x) + + # nan_policy='raise' raises an error + message = 'The input contains nan values' + with pytest.raises(ValueError, match=message): + _xp_var(x, nan_policy='raise') + + # `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') + xp_test = array_namespace(x) # torch has different default correction + ref = xp_test.var(x[~mask]) + xp_assert_close(res, ref) + + # Check for warning if omitting NaNs causes empty slice + message = 'After omitting NaNs...' + with pytest.warns(RuntimeWarning, match=message): + res = _xp_var(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_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) + + 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_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)) + + @array_api_compatible def test_chk_asarray(xp): rng = np.random.default_rng(2348923425434) From 65761ec962629a54e48860751e7881dcc54edce1 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Sat, 15 Jun 2024 04:18:12 +0100 Subject: [PATCH 414/500] TST: stats.combine_pvalues: parameterise tests and update reference values from R (#20934) * TST: stats.combine_pvalues: parameterise tests and update reference values from R * TST: add missing reference values and test the statistic as well * TST/STY: stats.combine_pvalues: refactor and test weight Stouffer statistic --------- Co-authored-by: Matt Haberland --- scipy/stats/tests/test_stats.py | 79 +++++++++++++++------------------ 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index f0afece4ffb2..81dc659daa31 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -7969,50 +7969,43 @@ def test_no_args_gh20661(self): @array_api_compatible 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)) - def test_fisher(self, xp): - # Example taken from https://en.wikipedia.org/wiki/Fisher%27s_exact_test#Example - xsq, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='fisher') - xp_assert_close(p, xp.asarray(0.02156), rtol=1e-4) - - def test_stouffer(self, xp): - Z, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='stouffer') - xp_assert_close(p, xp.asarray(0.01651), rtol=1e-3) - - def test_stouffer2(self, xp): - Z, p = stats.combine_pvalues(xp.asarray([.5, .5, .5]), method='stouffer') - xp_assert_close(p, xp.asarray(0.5), rtol=1e-4) - - def test_weighted_stouffer(self, xp): - pvalues = xp.asarray([.01, .2, .3]) - Z, p = stats.combine_pvalues(pvalues, method='stouffer', - weights=xp.ones(3, dtype=pvalues.dtype)) - xp_assert_close(p, xp.asarray(0.01651), rtol=1e-3) - - def test_weighted_stouffer2(self, xp): - Z, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='stouffer', - weights=xp.asarray([1., 4., 9.])) - xp_assert_close(p, xp.asarray(0.1464), rtol=1e-3) - - def test_pearson(self, xp): - Z, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='pearson') - xp_assert_close(p, xp.asarray(0.02213), rtol=1e-3) - - def test_tippett(self, xp): - Z, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='tippett') - xp_assert_close(p, xp.asarray(0.0297), rtol=1e-4) - - def test_mudholkar_george(self, xp): - Z, p = stats.combine_pvalues(xp.asarray([.1, .1, .1]), - method='mudholkar_george') - xp_assert_close(p, xp.asarray(0.019462), rtol=1e-4) - - def test_mudholkar_george_equal_fisher_pearson_average(self, xp): - Z, p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), - method='mudholkar_george') - Z_f, p_f = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='fisher') - Z_p, p_p = stats.combine_pvalues(xp.asarray([.01, .2, .3]), method='pearson') - xp_assert_close(0.5 * (Z_f+Z_p), Z, rtol=1e-4) + @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"] From 113b6ca90a22a710590a119680d45595e8df8be3 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 15 Jun 2024 02:37:41 -0600 Subject: [PATCH 415/500] DOC: update distributing section (#20944) Fixes gh-20552 The above-linked issue requested an update to our docs about distributing SciPy, and this is an attempt to modernize the docs a bit to reflect recent changes. --- doc/source/dev/core-dev/distributing.rst.inc | 46 ++++++++++---------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/doc/source/dev/core-dev/distributing.rst.inc b/doc/source/dev/core-dev/distributing.rst.inc index be31b009b5d5..7bde487ac68d 100644 --- a/doc/source/dev/core-dev/distributing.rst.inc +++ b/doc/source/dev/core-dev/distributing.rst.inc @@ -32,10 +32,10 @@ There are some issues with how Python packaging tools handle dependencies reported by projects. Because SciPy gets regular bug reports about this, we go in a bit of detail here. -SciPy only reports its dependency on NumPy via ``install_requires`` if NumPy -isn't installed at all on a system, or when building wheels with -``bdist_wheel``. SciPy no longer uses ``setup_requires`` (which in the past -invoked ``easy_install``); build dependencies are now handled only via +SciPy reports its dependency on NumPy via ``pyproject.toml`` for build purposes, +and SciPy also has a runtime check that an appropriate version of NumPy is +available. SciPy no longer uses ``setup_requires`` (which in +the past invoked ``easy_install``); build dependencies are now handled only via ``pyproject.toml``. ``pyproject.toml`` relies on PEP 517; ``pip`` has ``--no-use-pep517`` and ``--no-build-isolation`` flags that may ignore ``pyproject.toml`` or treat it differently - if users use those flags, they @@ -50,13 +50,14 @@ For dependencies it's important to set lower and upper bounds on their versions. For *build-time* dependencies, they are specified in ``pyproject.toml`` and the versions will *only* apply to the SciPy build itself. It's fine to specify either a range or a specific version for a -dependency like ``wheel`` or ``setuptools``. For NumPy we have to worry -about ABI compatibility too, hence we specify the version with ``==`` -to the lowest supported version (because NumPy's ABI is backward but not -forward compatible). +dependency like ``meson-python`` or ``pybind11``. For NumPy we have to worry +about ABI compatibility too. However, with NumPy ``>=2.0.0rc1`` backwards +compatibility is guaranteed as far back as the NumPy ``1.19`` series so +specification of a lowest supported version of NumPy at build time is no +longer required in ``pyproject.toml``. For *run-time dependencies* (currently only ``numpy``), we specify the range -of versions in ``pyproject.toml`` and in ``install_requires`` in ``setup.py``. +of versions in ``pyproject.toml`` and in ``scipy/__init__.py``. Getting the upper bound right is slightly tricky. If we don't set any bound, a too-new version will be pulled in a few years down the line, and NumPy may have deprecated and removed some API that SciPy depended on by then. On the other @@ -64,8 +65,8 @@ hand if we set the upper bound to the newest already-released version, then as soon as a new NumPy version is released there will be no matching SciPy version that works with it. Given that NumPy and SciPy both release in a 6-monthly cadence and that features that get deprecated in NumPy should stay around for -another two releases, we specify the upper bound as ``<1.xx+3.0`` (where ``xx`` -is the minor version of the latest already-released NumPy. +another two releases, we specify the upper bound as ``<2.xx+3.0`` (where ``xx`` +is the minor version of the latest already-released NumPy). .. _supported-py-numpy-versions: @@ -73,16 +74,16 @@ is the minor version of the latest already-released NumPy. Supported Python and NumPy versions ----------------------------------- The Python_ versions that SciPy supports are listed in the list of PyPI -classifiers in ``setup.py``, and mentioned in the release notes for each +classifiers in ``pyproject.toml``, and mentioned in the release notes for each release. All newly released Python versions will be supported as soon as possible. For the general policy on dropping support for a Python or NumPy version, see :ref:`NEP 29 `. The final decision on dropping support is always taken on the scipy-dev forum. -The lowest supported Numpy_ version for a SciPy version is mentioned in the -release notes and is encoded in ``pyproject.toml``, ``scipy/__init__.py`` and the -``install_requires`` field of ``setup.py``. Typically the latest SciPy release -supports ~5-7 minor versions of NumPy: up to 2.5 years' old NumPy versions, +The lowest supported NumPy_ version for a SciPy version is mentioned in the +release notes and is encoded in ``pyproject.toml`` and ``scipy/__init__.py``. +Typically the latest SciPy release supports ~5-7 minor versions of NumPy: up +to 2.5 years' old NumPy versions, (given that the frequency of NumPy releases is about 2x/year at the time of writing) plus two versions into the future. @@ -106,11 +107,12 @@ and distributing them on PyPI or elsewhere. **General** -- A binary is specific for a single Python version (because different Python - versions aren't ABI-compatible, at least up to Python 3.4). -- Build against the lowest NumPy version that you need to support, then it will - work for all NumPy versions with the same major version number (NumPy does - maintain backwards ABI compatibility). +- A binary is specific for a single (major) Python version (because different + major Python versions aren't ABI-compatible, at least up to Python 3.12). +- Build against NumPy ``2.0.0``, then it will work for all NumPy versions with + the same major version number (NumPy + does maintain backwards ABI compatibility), and as far back as NumPy ``1.19`` + series at the time of writing. - The easiest available toolchain for building portable SciPy binaries is our ``cibuildwheel`` infrastructure for common platforms, with details available in our CI infrastructure code and available via the @@ -152,7 +154,7 @@ Wheelhouse_, see at the wheel_ and Wheelhouse_ docs. .. _`SciPy's configuration file`: https://github.com/scipy/scipy/blob/main/pyproject.toml -.. _Numpy: https://numpy.org +.. _NumPy: https://numpy.org .. _Python: https://www.python.org .. _nose: https://nose.readthedocs.io .. _asv: https://asv.readthedocs.org From 5045fc0ad8c109c8f64b4addabb5e54210cd3ca6 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 15 Jun 2024 15:29:48 -0700 Subject: [PATCH 416/500] ENH: stats.tmean: add array API support --- scipy/stats/_stats_py.py | 34 +++++++++++--------- scipy/stats/tests/test_stats.py | 56 ++++++++++++++++----------------- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 4409cc632cea..3bfc985df43c 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -41,7 +41,7 @@ from scipy.optimize import milp, LinearConstraint from scipy._lib._util import (check_random_state, _get_nan, _rename_parameter, _contains_nan, - AxisError) + AxisError, _lazywhere) import scipy.special as special # Import unused here but needs to stay until end of deprecation periode @@ -534,7 +534,7 @@ def mode(a, axis=0, nan_policy='propagate', keepdims=False): return ModeResult(modes[()], counts[()]) -def _put_nan_to_limits(a, limits, inclusive): +def _put_val_to_limits(a, limits, inclusive, val=np.nan, xp=None): """Put NaNs in an array for values outside of given limits. This is primarily a utility function. @@ -551,21 +551,21 @@ def _put_nan_to_limits(a, limits, inclusive): determine whether values exactly equal to lower or upper are allowed. """ + xp = array_namespace(a) if xp is None else xp + mask = xp.zeros(a.shape, dtype=xp.bool) if limits is None: - return a - mask = np.full_like(a, False, dtype=np.bool_) + return a, mask lower_limit, upper_limit = limits lower_include, upper_include = inclusive if lower_limit is not None: mask |= (a < lower_limit) if lower_include else a <= lower_limit if upper_limit is not None: mask |= (a > upper_limit) if upper_include else a >= upper_limit - if np.all(mask): + if xp.all(mask): raise ValueError("No array values within given limits") - if np.any(mask): - a = a.copy() if np.issubdtype(a.dtype, np.inexact) else a.astype(np.float64) - a[mask] = np.nan - return a + if xp.any(mask): + a = xp.where(mask, xp.asarray(val), a) + return a, mask @_axis_nan_policy_factory( @@ -614,8 +614,12 @@ def tmean(a, limits=None, inclusive=(True, True), axis=None): 10.0 """ - a = _put_nan_to_limits(a, limits, inclusive) - return np.nanmean(a, axis=axis) + xp = array_namespace(a) + a, mask = _put_val_to_limits(a, limits, inclusive, val=0., xp=xp) + sum = xp.sum(a, axis=axis) + n = xp.sum(xp.asarray(~mask, dtype=a.dtype), axis=axis) + mean = _lazywhere(n != 0, (sum, n), xp.divide, xp.nan) + return mean[()] if mean.ndim == 0 else mean @_axis_nan_policy_factory( @@ -667,7 +671,7 @@ def tvar(a, limits=None, inclusive=(True, True), axis=0, ddof=1): 20.0 """ - a = _put_nan_to_limits(a, limits, inclusive) + a, _ = _put_val_to_limits(a, limits, inclusive) return np.nanvar(a, ddof=ddof, axis=axis) @@ -717,7 +721,7 @@ def tmin(a, lowerlimit=None, axis=0, inclusive=True, nan_policy='propagate'): """ dtype = a.dtype - a = _put_nan_to_limits(a, (lowerlimit, None), (inclusive, None)) + a, _ = _put_val_to_limits(a, (lowerlimit, None), (inclusive, None)) res = np.nanmin(a, axis=axis) if not np.any(np.isnan(res)): # needed if input is of integer dtype @@ -770,7 +774,7 @@ def tmax(a, upperlimit=None, axis=0, inclusive=True, nan_policy='propagate'): """ dtype = a.dtype - a = _put_nan_to_limits(a, (None, upperlimit), (None, inclusive)) + a, _ = _put_val_to_limits(a, (None, upperlimit), (None, inclusive)) res = np.nanmax(a, axis=axis) if not np.any(np.isnan(res)): # needed if input is of integer dtype @@ -879,7 +883,7 @@ def tsem(a, limits=None, inclusive=(True, True), axis=0, ddof=1): 1.1547005383792515 """ - a = _put_nan_to_limits(a, limits, inclusive) + a, _ = _put_val_to_limits(a, limits, inclusive) sd = np.sqrt(np.nanvar(a, ddof=ddof, axis=axis)) n_obs = (~np.isnan(a)).sum(axis=axis) return sd / np.sqrt(n_obs, dtype=sd.dtype) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 02e68c76ee6a..9d920ab63917 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -43,7 +43,7 @@ from scipy.conftest import array_api_compatible, skip_xp_invalid_arg from scipy._lib._array_api import (xp_assert_close, xp_assert_equal, array_namespace, copy, is_numpy, is_torch, SCIPY_ARRAY_API, - size as xp_size) + size as xp_size, copy as xp_copy) skip_xp_backends = pytest.mark.skip_xp_backends @@ -77,52 +77,52 @@ class TestTrimmedStats: # TODO: write these tests to handle missing values properly dprec = np.finfo(np.float64).precision - def test_tmean(self): - y = stats.tmean(X, (2, 8), (True, True)) - assert_approx_equal(y, 5.0, significant=self.dprec) + @array_api_compatible + def test_tmean(self, xp): + x = xp.asarray(X) + + y = stats.tmean(x, (2, 8), (True, True)) + xp_assert_close(y, xp.asarray(5.0, dtype=x.dtype)) - y1 = stats.tmean(X, limits=(2, 8), inclusive=(False, False)) - y2 = stats.tmean(X, limits=None) - assert_approx_equal(y1, y2, significant=self.dprec) + y1 = stats.tmean(x, limits=(2, 8), inclusive=(False, False)) + y2 = stats.tmean(x, limits=None) + xp_assert_close(y1, y2) - x_2d = arange(63, dtype=float64).reshape(9, 7) + x_2d = xp.reshape(xp.arange(63.), (9, 7)) y = stats.tmean(x_2d, axis=None) - assert_approx_equal(y, x_2d.mean(), significant=self.dprec) + xp_assert_close(y, xp.mean(x_2d)) y = stats.tmean(x_2d, axis=0) - assert_array_almost_equal(y, x_2d.mean(axis=0), decimal=8) + xp_assert_close(y, xp.mean(x_2d, axis=0)) y = stats.tmean(x_2d, axis=1) - assert_array_almost_equal(y, x_2d.mean(axis=1), decimal=8) + xp_assert_close(y, xp.mean(x_2d, axis=1)) y = stats.tmean(x_2d, limits=(2, 61), axis=None) - assert_approx_equal(y, 31.5, significant=self.dprec) + 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] - assert_array_almost_equal(y, y_true, decimal=8) + 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] - assert_array_almost_equal(y, y_true, decimal=8) + xp_assert_close(y, xp.asarray(y_true)) - x_2d_with_nan = np.array(x_2d) - x_2d_with_nan[-1, -3:] = np.nan + x_2d_with_nan = xp_copy(x_2d) + x_2d_with_nan[-1, -3:] = xp.nan y = stats.tmean(x_2d_with_nan, limits=(1, 13), axis=0) - y_true = [7, 4.5, 5.5, 6.5, np.nan, np.nan, np.nan] - assert_array_almost_equal(y, y_true, decimal=8) - - with suppress_warnings() as sup: - sup.record(RuntimeWarning, "Mean of empty slice") + 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, np.nan, np.nan, np.nan, np.nan, np.nan] - assert_array_almost_equal(y, y_true, decimal=8) + 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, np.nan, np.nan, np.nan, np.nan, np.nan] - assert_array_almost_equal(y, y_true, decimal=8) + 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)) def test_tvar(self): y = stats.tvar(X, limits=(2, 8), inclusive=(True, True)) From 27c3681a7ec647090c91426fae4bda6cc92ae1dc Mon Sep 17 00:00:00 2001 From: Simon Waldherr Date: Sun, 16 Jun 2024 13:36:45 +0200 Subject: [PATCH 417/500] MAINT: fix some misspellings (#20968) --- scipy/conftest.py | 2 +- scipy/integrate/_ivp/ivp.py | 2 +- scipy/interpolate/_cubic.py | 2 +- scipy/interpolate/_fitpack_py.py | 2 +- scipy/signal/_fir_filter_design.py | 2 +- scipy/signal/_signaltools.py | 2 +- scipy/sparse/_construct.py | 4 ++-- scipy/stats/_discrete_distns.py | 2 +- scipy/stats/_stats_py.py | 4 ++-- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scipy/conftest.py b/scipy/conftest.py index c9bd9bf99afb..428a9e118b03 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -293,7 +293,7 @@ def warnings_errors_and_rng(test=None): known_warnings[name] = dict(category=DeprecationWarning) from scipy import integrate - # the funcions are known to emit IntergrationWarnings + # the functions are known to emit IntegrationWarnings integration_w = ['scipy.special.ellip_normal', 'scipy.special.ellip_harm_2', ] diff --git a/scipy/integrate/_ivp/ivp.py b/scipy/integrate/_ivp/ivp.py index 13d4732bd644..0ac43f2169c1 100644 --- a/scipy/integrate/_ivp/ivp.py +++ b/scipy/integrate/_ivp/ivp.py @@ -262,7 +262,7 @@ def solve_ivp(fun, t_span, y0, method='RK45', t_eval=None, dense_output=False, terminal: bool or int, optional When boolean, whether to terminate integration if this event occurs. When integral, termination occurs after the specified the number of - occurences of this event. + occurrences of this event. Implicitly False if not assigned. direction: float, optional Direction of a zero crossing. If `direction` is positive, diff --git a/scipy/interpolate/_cubic.py b/scipy/interpolate/_cubic.py index 878a30904de7..4ef4af895ac4 100644 --- a/scipy/interpolate/_cubic.py +++ b/scipy/interpolate/_cubic.py @@ -482,7 +482,7 @@ class Akima1DInterpolator(CubicHermiteSpline): >>> ax.legend() >>> fig.show() - The overshoot that occured in ``"akima"`` has been avoided in ``"makima"``. + The overshoot that occurred in ``"akima"`` has been avoided in ``"makima"``. References ---------- diff --git a/scipy/interpolate/_fitpack_py.py b/scipy/interpolate/_fitpack_py.py index 88bd91bc6791..3ca3258f3a21 100644 --- a/scipy/interpolate/_fitpack_py.py +++ b/scipy/interpolate/_fitpack_py.py @@ -587,7 +587,7 @@ def spalde(x, tck): ... (0, 0, 0, 6, 0, 0, 0), # coefficients ... 3) # degree (cubic) >>> # Instance a B-spline object - >>> # `BSpline` objects are prefered, except for spalde() + >>> # `BSpline` objects are preferred, except for spalde() >>> bspl = BSpline(tck[0], tck[1], tck[2]) >>> # Generate extra points to get a smooth curve >>> x = np.linspace(min(tck[0]), max(tck[0]), 100) diff --git a/scipy/signal/_fir_filter_design.py b/scipy/signal/_fir_filter_design.py index 476df46a0336..42b881462659 100644 --- a/scipy/signal/_fir_filter_design.py +++ b/scipy/signal/_fir_filter_design.py @@ -1222,7 +1222,7 @@ def minimum_phase(h: np.ndarray, 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` wheras the other minimum phase filters have only half the order + linear filter `h` whereas the other minimum phase filters have only half the order and the square root of the magnitude response. """ h = np.asarray(h) diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index f2efc0a455e5..5096aac086db 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -3527,7 +3527,7 @@ def detrend(data: np.ndarray, axis: int = -1, Notes ----- - Detrending can be interpreted as substracting a least squares fit polyonimial: + Detrending can be interpreted as subtracting a least squares fit polyonimial: Setting the parameter `type` to 'constant' corresponds to fitting a zeroth degree polynomial, 'linear' to a first degree polynomial. Consult the example below. diff --git a/scipy/sparse/_construct.py b/scipy/sparse/_construct.py index 1cdde1b26a7b..f655b81285f2 100644 --- a/scipy/sparse/_construct.py +++ b/scipy/sparse/_construct.py @@ -1161,7 +1161,7 @@ def random_array(shape, *, density=0.01, format='coo', dtype=None, >>> S = sp.sparse.random_array((3, 4), density=0.25, random_state=rng, ... data_sampler=sp_stats_normal_squared) - Or we can subclass sp.stats rv_continous or rv_discrete: + 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): @@ -1319,7 +1319,7 @@ def random(m, n, density=0.01, format='coo', dtype=None, >>> S = sp.sparse.random(3, 4, density=0.25, random_state=rng, ... data_rvs=sp_stats_normal_squared) - Or we can subclass sp.stats rv_continous or rv_discrete: + 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): diff --git a/scipy/stats/_discrete_distns.py b/scipy/stats/_discrete_distns.py index 087e4b042967..ec088fdbb47c 100644 --- a/scipy/stats/_discrete_distns.py +++ b/scipy/stats/_discrete_distns.py @@ -464,7 +464,7 @@ def kurtosis(n, a, 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 substract 3 to get + # 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: diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 59803c9efe9c..27f670ed9940 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -9888,7 +9888,7 @@ def wasserstein_distance_nd(u_values, v_values, u_weights=None, v_weights=None): :math:`d_{ij} = d(x_i, y_j)`. Given :math:`\Gamma`, :math:`D`, :math:`b`, the Monge problem can be - tranformed into a linear programming problem by + 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 @@ -10589,7 +10589,7 @@ def _rankdata(x, method, return_ties=False): # - 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 unnecesarily reorder them. + # 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 = np.zeros(shape, dtype=float) From a6fdc85893cccf545405d7b456a179272a40da80 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 16 Jun 2024 15:14:28 +0300 Subject: [PATCH 418/500] DOC: linalg: add # may vary to a linalg.schur example The ordering of complex conjugate pairs is not guaranteed and was reported to differ in https://github.com/scipy/scipy/issues/20960 --- scipy/linalg/_decomp_schur.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/linalg/_decomp_schur.py b/scipy/linalg/_decomp_schur.py index 54a5ce92dd58..6f55f4fe8d37 100644 --- a/scipy/linalg/_decomp_schur.py +++ b/scipy/linalg/_decomp_schur.py @@ -107,7 +107,7 @@ def schur(a, output='real', lwork=None, overwrite_a=False, sort=None, [ 0. , -0.32948354+0.80225456j, -0.59877807+0.56192146j], [ 0. , 0. , -0.32948354-0.80225456j]]) >>> eigvals(T2) - array([2.65896708, -0.32948354+0.80225456j, -0.32948354-0.80225456j]) + array([2.65896708, -0.32948354+0.80225456j, -0.32948354-0.80225456j]) # may vary An arbitrary custom eig-sorting condition, having positive imaginary part, which is satisfied by only one eigenvalue From 07b2f13d834c438ff0d44f8a6a837a334328cb1a Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Mon, 17 Jun 2024 08:52:09 +0200 Subject: [PATCH 419/500] TST:sparse.linalg: Skip test due to sensitivity to numerical noise --- scipy/sparse/linalg/_isolve/tests/test_iterative.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scipy/sparse/linalg/_isolve/tests/test_iterative.py b/scipy/sparse/linalg/_isolve/tests/test_iterative.py index f3235260b39f..02fd6e3c1662 100644 --- a/scipy/sparse/linalg/_isolve/tests/test_iterative.py +++ b/scipy/sparse/linalg/_isolve/tests/test_iterative.py @@ -510,8 +510,12 @@ def test_x0_working(solver): def test_x0_equals_Mb(case): + 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' From 562f71973499c397a23d73e8321fb2c81517a56b Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Mon, 17 Jun 2024 03:44:22 -0700 Subject: [PATCH 420/500] DOC: single to double backticks (#20975) A few more target that can't be in single backticks because they afre not identifiers and woudl trigger issues if the default role was changed :any: or :py:obj: --- doc/source/tutorial/fft.rst | 6 +++--- doc/source/tutorial/integrate.rst | 2 +- doc/source/tutorial/interpolate/ND_regular_grid.rst | 2 +- .../tutorial/interpolate/splines_and_polynomials.rst | 2 +- scipy/cluster/_hierarchy.pyx | 10 +++++----- scipy/signal/_signaltools.py | 4 ++-- scipy/sparse/__init__.py | 4 ++-- scipy/spatial/distance.py | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/source/tutorial/fft.rst b/doc/source/tutorial/fft.rst index 45b45e427bff..079b70cae904 100644 --- a/doc/source/tutorial/fft.rst +++ b/doc/source/tutorial/fft.rst @@ -27,7 +27,7 @@ Fast Fourier transforms 1-D discrete Fourier transforms ___________________________________________ -The FFT `y[k]` of length :math:`N` of the length-:math:`N` sequence `x[n]` is +The FFT ``y[k]`` of length :math:`N` of the length-:math:`N` sequence ``x[n]`` is defined as .. math:: @@ -369,7 +369,7 @@ ____________ The (unnormalized) DCT-III is the inverse of the (unnormalized) DCT-II, up to a -factor of `2N`. The orthonormalized DCT-III is exactly the inverse of the +factor of ``2N``. The orthonormalized DCT-III is exactly the inverse of the orthonormalized DCT- II. The function :func:`idct` performs the mappings between the DCT and IDCT types, as well as the correct normalization. @@ -485,7 +485,7 @@ definition of the unnormalized DST-I (``norm=None``): \right), \qquad 0 \le k < N. Note also that the DST-I is only supported for input size > 1. The -(unnormalized) DST-I is its own inverse, up to a factor of `2(N+1)`. +(unnormalized) DST-I is its own inverse, up to a factor of ``2(N+1)``. Type II DST ___________ diff --git a/doc/source/tutorial/integrate.rst b/doc/source/tutorial/integrate.rst index 970b6353ddce..79d401eab2dc 100644 --- a/doc/source/tutorial/integrate.rst +++ b/doc/source/tutorial/integrate.rst @@ -466,7 +466,7 @@ has an exact solution using the matrix exponential: However, in this case, :math:`\mathbf{A}\left(t\right)` and its integral do not commute. This differential equation can be solved using the function :obj:`solve_ivp`. -It requires the derivative, *fprime*, the time span `[t_start, t_end]` +It requires the derivative, *fprime*, the time span ``[t_start, t_end]`` and the initial conditions vector, *y0*, as input arguments and returns an object whose *y* field is an array with consecutive solution values as columns. The initial conditions are therefore given in the first output column. diff --git a/doc/source/tutorial/interpolate/ND_regular_grid.rst b/doc/source/tutorial/interpolate/ND_regular_grid.rst index 5add4d5a38f0..0ffbfc08e751 100644 --- a/doc/source/tutorial/interpolate/ND_regular_grid.rst +++ b/doc/source/tutorial/interpolate/ND_regular_grid.rst @@ -63,7 +63,7 @@ using each method. interpolation. If your data is such that spline methods produce ringing, you may consider -using `method="pchip"`, which uses the tensor product of PCHIP interpolators, +using ``method="pchip"``, which uses the tensor product of PCHIP interpolators, a `PchipInterpolator` per dimension. If you prefer a functional interface opposed to explicitly creating a class instance, diff --git a/doc/source/tutorial/interpolate/splines_and_polynomials.rst b/doc/source/tutorial/interpolate/splines_and_polynomials.rst index d79cc534118a..9751a30f4c1c 100644 --- a/doc/source/tutorial/interpolate/splines_and_polynomials.rst +++ b/doc/source/tutorial/interpolate/splines_and_polynomials.rst @@ -260,7 +260,7 @@ at a given evaluation point, thus a design matrix built on b-splines has at most As an illustration, we consider a toy example. Suppose our data are one-dimensional and are confined to an interval :math:`[0, 6]`. We construct a 4-regular knot vector which corresponds to 7 data points and -cubic, `k=3`, splines: +cubic, ``k=3``, splines: >>> t = [0., 0., 0., 0., 2., 3., 4., 6., 6., 6., 6.] diff --git a/scipy/cluster/_hierarchy.pyx b/scipy/cluster/_hierarchy.pyx index 5cc3bdb72b84..511e47fa0e6b 100644 --- a/scipy/cluster/_hierarchy.pyx +++ b/scipy/cluster/_hierarchy.pyx @@ -84,7 +84,7 @@ def cluster_dist(const double[:, :] Z, int[:] T, double cutoff, int n): The linkage matrix. T : ndarray The array to store the cluster numbers. The i'th observation belongs to - cluster `T[i]`. + cluster ``T[i]``. cutoff : double Clusters are formed when distances are less than or equal to `cutoff`. n : int @@ -107,7 +107,7 @@ def cluster_in(const double[:, :] Z, const double[:, :] R, int[:] T, double cuto The inconsistent matrix. T : ndarray The array to store the cluster numbers. The i'th observation belongs to - cluster `T[i]`. + cluster ``T[i]``. cutoff : double Clusters are formed when the inconsistent values are less than or or equal to `cutoff`. @@ -129,7 +129,7 @@ def cluster_maxclust_dist(const double[:, :] Z, int[:] T, int n, int mc): The linkage matrix. T : ndarray The array to store the cluster numbers. The i'th observation belongs to - cluster `T[i]`. + cluster ``T[i]``. n : int The number of observations. mc : int @@ -154,7 +154,7 @@ cpdef void cluster_maxclust_monocrit(const double[:, :] Z, const double[:] MC, i The monotonic criterion array. T : ndarray The array to store the cluster numbers. The i'th observation belongs to - cluster `T[i]`. + cluster ``T[i]``. n : int The number of observations. max_nc : int @@ -240,7 +240,7 @@ cpdef void cluster_monocrit(const double[:, :] Z, const double[:] MC, int[:] T, The monotonic criterion array. T : ndarray The array to store the cluster numbers. The i'th observation belongs to - cluster `T[i]`. + cluster ``T[i]``. cutoff : double Clusters are formed when the MC values are less than or equal to `cutoff`. diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index 5096aac086db..fc680e6dad26 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -3672,7 +3672,7 @@ def lfilter_zi(b, a): A = scipy.linalg.companion(a).T B = b[1:] - a[1:]*b[0] - assuming `a[0]` is 1.0; if `a[0]` is not 1, `a` and `b` are first + assuming ``a[0]`` is 1.0; if ``a[0]`` is not 1, `a` and `b` are first divided by a[0]. Examples @@ -3700,7 +3700,7 @@ def lfilter_zi(b, a): 0.44399389, 0.35505241]) Note that the `zi` argument to `lfilter` was computed using - `lfilter_zi` and scaled by `x[0]`. Then the output `y` has no + `lfilter_zi` and scaled by ``x[0]``. Then the output `y` has no transient until the input drops from 0.5 to 0.0. """ diff --git a/scipy/sparse/__init__.py b/scipy/sparse/__init__.py index b710a22325cd..fbf2da8b0ad2 100644 --- a/scipy/sparse/__init__.py +++ b/scipy/sparse/__init__.py @@ -127,8 +127,8 @@ Identifying sparse arrays: -- use `isinstance(A, sp.sparse.sparray)` to check whether an array or matrix. -- use `A.format == 'csr'` to check the sparse format +- use ``isinstance(A, sp.sparse.sparray)`` to check whether an array or matrix. +- use ``A.format == 'csr'`` to check the sparse format Identifying sparse matrices: diff --git a/scipy/spatial/distance.py b/scipy/spatial/distance.py index 6eb0fd30a099..ab058b7f04ed 100644 --- a/scipy/spatial/distance.py +++ b/scipy/spatial/distance.py @@ -1156,7 +1156,7 @@ def canberra(u, v, w=None): Notes ----- - When `u[i]` and `v[i]` are 0 for given i, then the fraction 0/0 = 0 is + When ``u[i]`` and ``v[i]`` are 0 for given i, then the fraction 0/0 = 0 is used in the calculation. Examples From ad1652e03527476b622e75d95d610b639c206e43 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Mon, 17 Jun 2024 13:07:10 +0200 Subject: [PATCH 421/500] BUG: Update scipy-optimise directive in view of new default role. There is some work being done to update the default role from autolink to something more generic like py:obj or any, this is not yet possible as those do not yet cross-reference parameters. Nonetheless if one tries to change the default role, sphinx choke on some docutils node sources as the parent was improperly set. This small patch should fix it by using the parent kwarg, and also using a more specific subclass. See https://github.com/sphinx-doc/sphinx/issues/12429 --- doc/source/scipyoptdoc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/source/scipyoptdoc.py b/doc/source/scipyoptdoc.py index 14f32454b471..3bf08c5d7efe 100644 --- a/doc/source/scipyoptdoc.py +++ b/doc/source/scipyoptdoc.py @@ -30,7 +30,7 @@ raise RuntimeError("Sphinx 1.0.1 or newer is required") from numpydoc.numpydoc import mangle_docstrings -from docutils.statemachine import ViewList +from docutils.statemachine import StringList from sphinx.domains.python import PythonDomain from scipy._lib._util import getfullargspec_no_self @@ -149,7 +149,8 @@ def remove_arg(arg): new_lines.append(':Options:') else: new_lines.append(line) - self.content = ViewList(new_lines, self.content.parent) + + self.content = StringList(new_lines, parent=self.content.parent) return base_directive.run(self) option_spec = dict(base_directive.option_spec) From d74c9d6c7f6e980dca59e5c5fbeef97df3b62bc0 Mon Sep 17 00:00:00 2001 From: m-maggi <124086916+m-maggi@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:27:37 +0200 Subject: [PATCH 422/500] DOC: Mention that ``sparse.bsr_array`` does not support slicing. (#20916) --- scipy/sparse/_bsr.py | 8 ++++++++ scipy/sparse/_dia.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/scipy/sparse/_bsr.py b/scipy/sparse/_bsr.py index 40bc949d4a93..2352a535855f 100644 --- a/scipy/sparse/_bsr.py +++ b/scipy/sparse/_bsr.py @@ -727,6 +727,10 @@ class bsr_array(_bsr_base, sparray): 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 @@ -834,6 +838,10 @@ class bsr_matrix(spmatrix, _bsr_base): 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 diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index c45e90cd4aeb..c48ca8c104b7 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -481,6 +481,7 @@ class dia_array(_dia_base, sparray): 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. Examples -------- @@ -556,6 +557,7 @@ class dia_matrix(spmatrix, _dia_base): Sparse matrices can be used in arithmetic operations: they support addition, subtraction, multiplication, division, and matrix power. + Sparse matrices with DIAgonal storage do not support slicing. Examples -------- From 07f84dcac8a6f6a46c8745810db53a4ef7a6f5af Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Mon, 17 Jun 2024 08:28:28 -0700 Subject: [PATCH 423/500] DOC: More single to double backtick (#20977) A quick scan of sphinx errors when building with default role beeing `py:obj`, a few references look impossible as they are invalid python identifiers, and most of them need double backticks. --- doc/source/release/1.1.0-notes.rst | 2 +- doc/source/release/1.11.0-notes.rst | 4 +-- doc/source/release/1.2.0-notes.rst | 2 +- doc/source/release/1.5.0-notes.rst | 4 +-- doc/source/tutorial/ndimage.rst | 2 +- .../tutorial/stats/continuous_geninvgauss.rst | 2 +- .../stats/continuous_norminvgauss.rst | 2 +- doc/source/tutorial/stats/sampling_srou.rst | 2 +- scipy/interpolate/_cubic.py | 2 +- scipy/interpolate/_interpolate.py | 4 +-- scipy/ndimage/_morphology.py | 6 ++-- scipy/odr/_odrpack.py | 10 +++---- scipy/optimize/_bracket.py | 2 +- scipy/optimize/_differentialevolution.py | 4 +-- scipy/optimize/_differentiate.py | 2 +- scipy/optimize/_lbfgsb_py.py | 16 +++++------ scipy/optimize/_optimize.py | 12 ++++---- scipy/optimize/_root_scalar.py | 14 +++++----- scipy/optimize/_slsqp_py.py | 12 ++++---- scipy/optimize/_tnc.py | 2 +- scipy/optimize/_zeros_py.py | 2 +- scipy/signal/_signaltools.py | 2 +- scipy/special/_add_newdocs.py | 6 ++-- scipy/stats/_continuous_distns.py | 4 +-- scipy/stats/_crosstab.py | 2 +- scipy/stats/_distn_infrastructure.py | 2 +- scipy/stats/_mgc.py | 28 +++++++++---------- scipy/stats/_multivariate.py | 2 +- scipy/stats/_qmvnt.py | 2 +- 29 files changed, 78 insertions(+), 78 deletions(-) diff --git a/doc/source/release/1.1.0-notes.rst b/doc/source/release/1.1.0-notes.rst index 6aa80d749f89..2afbfb7295e6 100644 --- a/doc/source/release/1.1.0-notes.rst +++ b/doc/source/release/1.1.0-notes.rst @@ -501,7 +501,7 @@ Pull requests for 1.1.0 * `#8179 `__: TST: Added pdist to asv spatial benchmark suite * `#8180 `__: TST: ensure constraint test improved * `#8183 `__: 0d conj correlate -* `#8186 `__: BUG: special: fix derivative of `spherical_jn(1, 0)` +* `#8186 `__: BUG: special: fix derivative of ``spherical_jn(1, 0)`` * `#8194 `__: Fix warning message * `#8196 `__: BUG: correctly handle inputs with nan's and ties in spearmanr * `#8198 `__: MAINT: stats.triang edge case fixes #6036 diff --git a/doc/source/release/1.11.0-notes.rst b/doc/source/release/1.11.0-notes.rst index 44266c91ae22..0cfea6821485 100644 --- a/doc/source/release/1.11.0-notes.rst +++ b/doc/source/release/1.11.0-notes.rst @@ -108,8 +108,8 @@ New features - A new public base class `scipy.sparse.sparray` was introduced, allowing further extension of the sparse array API (such as the support for 1-dimensional sparse arrays) without breaking backwards compatibility. - `isinstance(x, scipy.sparse.sparray)` to select the new sparse array classes, - while `isinstance(x, scipy.sparse.spmatrix)` selects only the old sparse + ``isinstance(x, scipy.sparse.sparray)`` to select the new sparse array classes, + while ``isinstance(x, scipy.sparse.spmatrix)`` selects only the old sparse matrix classes. - Division of sparse arrays by a dense array now returns sparse arrays. - `scipy.sparse.isspmatrix` now only returns `True` for the sparse matrices instances. diff --git a/doc/source/release/1.2.0-notes.rst b/doc/source/release/1.2.0-notes.rst index 3cd06cb9fa3b..164e490b2aed 100644 --- a/doc/source/release/1.2.0-notes.rst +++ b/doc/source/release/1.2.0-notes.rst @@ -435,7 +435,7 @@ Issues closed for 1.2.0 * `#8900 `__: Missing complex conjugation in scipy.sparse.linalg.LinearOperator * `#8904 `__: BUG: if zero derivative at root, then Newton fails with RuntimeWarning * `#8911 `__: make_interp_spline bc_type incorrect input interpretation -* `#8942 `__: MAINT: Refactor `_linprog.py` and `_linprog_ip.py` to remove... +* `#8942 `__: MAINT: Refactor ``_linprog.py`` and ``_linprog_ip.py`` to remove... * `#8947 `__: np.int64 in scipy.fftpack.next_fast_len * `#9020 `__: BUG: linalg.subspace_angles gives wrong results * `#9033 `__: scipy.stats.normaltest sometimes gives incorrect returns b/c... diff --git a/doc/source/release/1.5.0-notes.rst b/doc/source/release/1.5.0-notes.rst index 5bac0fd5a677..b5b17e1acd0e 100644 --- a/doc/source/release/1.5.0-notes.rst +++ b/doc/source/release/1.5.0-notes.rst @@ -275,7 +275,7 @@ cases is more accurate p-values, especially for two-sample testing with smaller (or quite different) sizes. `scipy.stats.maxwell` performance improvements include a 20 % speed up for -`fit()`` and 5 % for ``pdf()`` +``fit()`` and 5 % for ``pdf()`` `scipy.stats.shapiro` and `scipy.stats.jarque_bera` now return a named tuple for greater consistency with other ``stats`` functions @@ -506,7 +506,7 @@ Issues closed for 1.5.0 * `#9212 `__: EIGH very very slow --> suggesting an easy fix * `#9553 `__: ndimage routines behave badly when output has memory overlap... * `#9632 `__: ndimage.maximum_filter undocumented behaviour using footprint... -* `#9658 `__: `scipy.optimize.minimize(method='COBYLA')` not threadsafe +* `#9658 `__: \`scipy.optimize.minimize(method='COBYLA')\` not threadsafe * `#9710 `__: stats.weightedtau([1], [1.0]) SEGFAULTs * `#9797 `__: Master Tracker for some Kolmogorov-Smirnov test Issues * `#9844 `__: scipy.signal.upfirdn gives different length matrix versus MATLAB... diff --git a/doc/source/tutorial/ndimage.rst b/doc/source/tutorial/ndimage.rst index 3ccae05cf741..0b5c193db974 100644 --- a/doc/source/tutorial/ndimage.rst +++ b/doc/source/tutorial/ndimage.rst @@ -809,7 +809,7 @@ parameter that determines how the boundaries are handled, and a *cval* parameter that gives a constant value in case that the 'constant' mode is used. The behavior of all modes, including at non-integer locations is illustrated below. Note the boundaries are not handled the same for all modes; -`reflect` (aka `grid-mirror`) and `grid-wrap` involve symmetry or repetition +`reflect` (aka ``grid-mirror``) and ``grid-wrap`` involve symmetry or repetition about a point that is half way between image samples (dashed vertical lines) while modes `mirror` and `wrap` treat the image as if it's extent ends exactly at the first and last sample point rather than 0.5 samples past it. diff --git a/doc/source/tutorial/stats/continuous_geninvgauss.rst b/doc/source/tutorial/stats/continuous_geninvgauss.rst index 739fe243a46d..a0db70a43a1d 100644 --- a/doc/source/tutorial/stats/continuous_geninvgauss.rst +++ b/doc/source/tutorial/stats/continuous_geninvgauss.rst @@ -15,6 +15,6 @@ The probability density function is given by: where :math:`x > 0` is a real number and the parameters :math:`p, b` satisfy :math:`b > 0`. :math:`K_v` is the modified Bessel function of second kind of order :math:`v` (`scipy.special.kv`). -If `X` is `geninvgauss(p, b)`, then the distribution of `1/X` is `geninvgauss(-p, b)`. The inverse Gaussian distribution (`scipy.stats.invgauss`) is a special case with p=-1/2. +If `X` is ``geninvgauss(p, b)``, then the distribution of `1/X` is ``geninvgauss(-p, b)``. The inverse Gaussian distribution (`scipy.stats.invgauss`) is a special case with p=-1/2. Implementation: `scipy.stats.geninvgauss` diff --git a/doc/source/tutorial/stats/continuous_norminvgauss.rst b/doc/source/tutorial/stats/continuous_norminvgauss.rst index fe60cabd3064..4141e73d4453 100644 --- a/doc/source/tutorial/stats/continuous_norminvgauss.rst +++ b/doc/source/tutorial/stats/continuous_norminvgauss.rst @@ -15,7 +15,7 @@ The probability density function is given by: 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| \leq a`. :math:`K_1` is the modified Bessel function of second kind (`scipy.special.k1`). -A normal inverse Gaussian random variable with parameters :math:`a` and :math:`b` can be expressed as :math:`X = b V + \sqrt(V) X` where :math:`X` is `norm(0,1)` and :math:`V` is `invgauss(mu=1/sqrt(a**2 - b**2))`. Hence, the normal inverse Gaussian distribution is a special case of normal variance-mean mixtures. +A normal inverse Gaussian random variable with parameters :math:`a` and :math:`b` can be expressed as :math:`X = b V + \sqrt(V) X` where :math:`X` is ``norm(0,1)`` and :math:`V` is ``invgauss(mu=1/sqrt(a**2 - b**2))``. Hence, the normal inverse Gaussian distribution is a special case of normal variance-mean mixtures. Another common parametrization of the distribution is given by the following expression of the pdf: diff --git a/doc/source/tutorial/stats/sampling_srou.rst b/doc/source/tutorial/stats/sampling_srou.rst index ec74a6ba9421..e0965202fe3e 100644 --- a/doc/source/tutorial/stats/sampling_srou.rst +++ b/doc/source/tutorial/stats/sampling_srou.rst @@ -85,7 +85,7 @@ different shape parameters. In such a situation, the setup step of `sampling.NumericalInverseHermite` or `sampling.NumericalInversePolynomial` will lead to poor performance. As an example, assume we are interested to generate 100 samples for the Gamma distribution with 1000 different shape parameters -given by `np.arange(1.5, 5, 1000)`. +given by ``np.arange(1.5, 5, 1000)``. >>> import math >>> class GammaDist: diff --git a/scipy/interpolate/_cubic.py b/scipy/interpolate/_cubic.py index 4ef4af895ac4..65c27924ff50 100644 --- a/scipy/interpolate/_cubic.py +++ b/scipy/interpolate/_cubic.py @@ -607,7 +607,7 @@ class CubicSpline(CubicHermiteSpline): If `bc_type` is a 2-tuple, the first and the second value will be applied at the curve start and end respectively. The tuple values can be one of the previously mentioned strings (except 'periodic') or a - tuple `(order, deriv_values)` allowing to specify arbitrary + tuple ``(order, deriv_values)`` allowing to specify arbitrary derivatives at curve ends: * `order`: the derivative order, 1 or 2. diff --git a/scipy/interpolate/_interpolate.py b/scipy/interpolate/_interpolate.py index 93797be9d639..617d070156cd 100644 --- a/scipy/interpolate/_interpolate.py +++ b/scipy/interpolate/_interpolate.py @@ -1604,7 +1604,7 @@ def from_derivatives(cls, xi, yi, orders=None, extrapolate=None): >>> from scipy.interpolate import BPoly >>> BPoly.from_derivatives([0, 1], [[1, 2], [3, 4]]) - Creates a polynomial `f(x)` of degree 3, defined on `[0, 1]` + Creates a polynomial `f(x)` of degree 3, defined on ``[0, 1]`` such that `f(0) = 1, df/dx(0) = 2, f(1) = 3, df/dx(1) = 4` >>> BPoly.from_derivatives([0, 1, 2], [[0, 1], [0], [2]]) @@ -1612,7 +1612,7 @@ def from_derivatives(cls, xi, yi, orders=None, extrapolate=None): Creates a piecewise polynomial `f(x)`, such that `f(0) = f(1) = 0`, `f(2) = 2`, and `df/dx(0) = 1`. Based on the number of derivatives provided, the order of the - local polynomials is 2 on `[0, 1]` and 1 on `[1, 2]`. + local polynomials is 2 on ``[0, 1]`` and 1 on ``[1, 2]``. Notice that no restriction is imposed on the derivatives at ``x = 1`` and ``x = 2``. diff --git a/scipy/ndimage/_morphology.py b/scipy/ndimage/_morphology.py index 22ada0b130f9..5f099495b225 100644 --- a/scipy/ndimage/_morphology.py +++ b/scipy/ndimage/_morphology.py @@ -1929,7 +1929,7 @@ def distance_transform_bf(input, metric="euclidean", sampling=None, An output array to store the calculated feature transform, instead of returning it. `return_indicies` must be True. - Its shape must be `(input.ndim,) + input.shape`. + Its shape must be ``(input.ndim,) + input.shape``. Returns ------- @@ -2168,7 +2168,7 @@ def distance_transform_cdt(input, metric='chessboard', return_distances=True, An output array to store the calculated feature transform, instead of returning it. `return_indicies` must be True. - Its shape must be `(input.ndim,) + input.shape`. + Its shape must be ``(input.ndim,) + input.shape``. Returns ------- @@ -2373,7 +2373,7 @@ def distance_transform_edt(input, sampling=None, return_distances=True, An output array to store the calculated feature transform, instead of returning it. `return_indicies` must be True. - Its shape must be `(input.ndim,) + input.shape`. + Its shape must be ``(input.ndim,) + input.shape``. Returns ------- diff --git a/scipy/odr/_odrpack.py b/scipy/odr/_odrpack.py index ac1b97e3726d..59604d30b847 100644 --- a/scipy/odr/_odrpack.py +++ b/scipy/odr/_odrpack.py @@ -492,14 +492,14 @@ class Model: i.e. ``beta = array([B_1, B_2, ..., B_p])`` `fjacb` if the response variable is multi-dimensional, then the - return array's shape is `(q, p, n)` such that ``fjacb(x,beta)[l,k,i] = + return array's shape is ``(q, p, n)`` such that ``fjacb(x,beta)[l,k,i] = d f_l(X,B)/d B_k`` evaluated at the ith data point. If ``q == 1``, then - the return array is only rank-2 and with shape `(p, n)`. + the return array is only rank-2 and with shape ``(p, n)``. `fjacd` - as with fjacb, only the return array's shape is `(q, m, n)` + as with fjacb, only the return array's shape is ``(q, m, n)`` such that ``fjacd(x,beta)[l,j,i] = d f_l(X,B)/d X_j`` at the ith data - point. If ``q == 1``, then the return array's shape is `(m, n)`. If - ``m == 1``, the shape is (q, n). If `m == q == 1`, the shape is `(n,)`. + point. If ``q == 1``, then the return array's shape is ``(m, n)``. If + ``m == 1``, the shape is (q, n). If `m == q == 1`, the shape is ``(n,)``. """ diff --git a/scipy/optimize/_bracket.py b/scipy/optimize/_bracket.py index 6abfaaa94408..5660f6c07968 100644 --- a/scipy/optimize/_bracket.py +++ b/scipy/optimize/_bracket.py @@ -123,7 +123,7 @@ def _bracket_root(func, xl0, xr0=None, *, xmin=None, xmax=None, factor=None, Notes ----- This function generalizes an algorithm found in pieces throughout - `scipy.stats`. The strategy is to iteratively grow the bracket `(l, r)` + `scipy.stats`. The strategy is to iteratively grow the bracket ``(l, r)`` until ``func(l) < 0 < func(r)``. The bracket grows to the left as follows. - If `xmin` is not provided, the distance between `xl0` and `l` is iteratively diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index 4d2c0d339055..8555723d4a5b 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -86,7 +86,7 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', total population size), and ``rng`` is the random number generator being used within the solver. ``candidate`` will be in the range ``[0, S)``. - ``strategy`` must return a trial vector with shape `(N,)`. The + ``strategy`` must return a trial vector with shape ``(N,)``. The fitness of this trial vector is compared against the fitness of ``population[candidate]``. @@ -560,7 +560,7 @@ class DifferentialEvolutionSolver: total population size), and ``rng`` is the random number generator being used within the solver. ``candidate`` will be in the range ``[0, S)``. - ``strategy`` must return a trial vector with shape `(N,)`. The + ``strategy`` must return a trial vector with shape ``(N,)``. The fitness of this trial vector is compared against the fitness of ``population[candidate]``. maxiter : int, optional diff --git a/scipy/optimize/_differentiate.py b/scipy/optimize/_differentiate.py index 959c17e3ffae..3b919f8e6209 100644 --- a/scipy/optimize/_differentiate.py +++ b/scipy/optimize/_differentiate.py @@ -412,7 +412,7 @@ def pre_func_eval(work): in one call to the function. For improvement: - - Consider measuring the step size actually taken, since `(x + h) - x` + - Consider measuring the step size actually taken, since ``(x + h) - x`` is not identically equal to `h` with floating point arithmetic. - Adjust the step size automatically if `x` is too big to resolve the step. diff --git a/scipy/optimize/_lbfgsb_py.py b/scipy/optimize/_lbfgsb_py.py index 42ad9038ef0c..59339b727d89 100644 --- a/scipy/optimize/_lbfgsb_py.py +++ b/scipy/optimize/_lbfgsb_py.py @@ -173,11 +173,11 @@ def fmin_l_bfgs_b(func, x0, fprime=None, args=(), Examples -------- - Solve a linear regression problem via `fmin_l_bfgs_b`. To do this, first we define - an objective function ``f(m, b) = (y - y_model)**2``, where `y` describes the - observations and `y_model` the prediction of the linear model as - ``y_model = m*x + b``. The bounds for the parameters, ``m`` and ``b``, are arbitrarily - chosen as ``(0,5)`` and ``(5,10)`` for this example. + Solve a linear regression problem via `fmin_l_bfgs_b`. To do this, first we + define an objective function ``f(m, b) = (y - y_model)**2``, where `y` + describes the observations and `y_model` the prediction of the linear model + as ``y_model = m*x + b``. The bounds for the parameters, ``m`` and ``b``, + are arbitrarily chosen as ``(0,5)`` and ``(5,10)`` for this example. >>> import numpy as np >>> from scipy.optimize import fmin_l_bfgs_b @@ -201,8 +201,8 @@ def fmin_l_bfgs_b(func, x0, fprime=None, args=(), array([1.99999999, 3.00000006]), 1.7746231151323805e-14 # may vary The optimized parameters in ``x_opt`` agree with the ground truth parameters - ``m`` and ``b``. Next, let us perform a bound contrained optimization using the `bounds` - parameter. + ``m`` and ``b``. Next, let us perform a bound constrained optimization using + the `bounds` parameter. >>> bounds = [(0, 5), (5, 10)] >>> x_opt, f_op, info = fmin_l_bfgs_b(func, x0=initial_values, args=(X, Y), @@ -293,7 +293,7 @@ def _minimize_lbfgsb(fun, x0, args=(), jac=None, bounds=None, maxls : int, optional Maximum number of line search steps (per iteration). Default is 20. finite_diff_rel_step : None or array_like, optional - If `jac in ['2-point', '3-point', 'cs']` the relative step size to + If ``jac in ['2-point', '3-point', 'cs']`` the relative step size to use for numerical approximation of the jacobian. The absolute step size is computed as ``h = rel_step * sign(x) * max(1, abs(x))``, possibly adjusted to fit into the bounds. For ``method='3-point'`` diff --git a/scipy/optimize/_optimize.py b/scipy/optimize/_optimize.py index 684a38b0faee..a1cc6ab84a0f 100644 --- a/scipy/optimize/_optimize.py +++ b/scipy/optimize/_optimize.py @@ -58,8 +58,8 @@ class MemoizeJac: - """ Decorator that caches the return values of a function returning `(fun, grad)` - each time it is called. """ + """Decorator that caches the return values of a function returning ``(fun, grad)`` + each time it is called.""" def __init__(self, fun): self.fun = fun @@ -232,10 +232,10 @@ def _prepare_scalar_function(fun, x0, jac=None, args=(), bounds=None, bounds : sequence, optional Bounds on variables. 'new-style' bounds are required. eps : float or ndarray - If `jac is None` the absolute step size used for numerical + If ``jac is None`` the absolute step size used for numerical approximation of the jacobian via forward differences. finite_diff_rel_step : None or array_like, optional - If `jac in ['2-point', '3-point', 'cs']` the relative step size to + If ``jac in ['2-point', '3-point', 'cs']`` the relative step size to use for numerical approximation of the jacobian. The absolute step size is computed as ``h = rel_step * sign(x0) * max(1, abs(x0))``, possibly adjusted to fit into the bounds. For ``jac='3-point'`` @@ -1329,7 +1329,7 @@ def _minimize_bfgs(fun, x0, args=(), jac=None, callback=None, Set to True to return a list of the best solution at each of the iterations. finite_diff_rel_step : None or array_like, optional - If `jac in ['2-point', '3-point', 'cs']` the relative step size to + If ``jac in ['2-point', '3-point', 'cs']`` the relative step size to use for numerical approximation of the jacobian. The absolute step size is computed as ``h = rel_step * sign(x) * max(1, abs(x))``, possibly adjusted to fit into the bounds. For ``jac='3-point'`` @@ -1696,7 +1696,7 @@ def _minimize_cg(fun, x0, args=(), jac=None, callback=None, Set to True to return a list of the best solution at each of the iterations. finite_diff_rel_step : None or array_like, optional - If `jac in ['2-point', '3-point', 'cs']` the relative step size to + If ``jac in ['2-point', '3-point', 'cs']`` the relative step size to use for numerical approximation of the jacobian. The absolute step size is computed as ``h = rel_step * sign(x) * max(1, abs(x))``, possibly adjusted to fit into the bounds. For ``jac='3-point'`` diff --git a/scipy/optimize/_root_scalar.py b/scipy/optimize/_root_scalar.py index 550098bbe677..a1d9fde0af0a 100644 --- a/scipy/optimize/_root_scalar.py +++ b/scipy/optimize/_root_scalar.py @@ -22,7 +22,7 @@ class MemoizeDer: time it is called. This is a simplistic memoizer that calls and caches a single value - of `f(x, *args)`. + of ``f(x, *args)``. It assumes that `args` does not change between invocations. It supports the use case of a root-finder where `args` is fixed, `x` changes, and only rarely, if at all, does x assume the same value @@ -86,7 +86,7 @@ def root_scalar(f, args=(), method=None, bracket=None, - 'halley' :ref:`(see here) ` bracket: A sequence of 2 floats, optional - An interval bracketing a root. `f(x, *args)` must have different + An interval bracketing a root. ``f(x, *args)`` must have different signs at the two endpoints. x0 : float, optional Initial guess. @@ -343,7 +343,7 @@ def _root_scalar_brentq_doc(): args : tuple, optional Extra arguments passed to the objective function. bracket: A sequence of 2 floats, optional - An interval bracketing a root. `f(x, *args)` must have different + An interval bracketing a root. ``f(x, *args)`` must have different signs at the two endpoints. xtol : float, optional Tolerance (absolute) for termination. @@ -365,7 +365,7 @@ def _root_scalar_brenth_doc(): args : tuple, optional Extra arguments passed to the objective function. bracket: A sequence of 2 floats, optional - An interval bracketing a root. `f(x, *args)` must have different + An interval bracketing a root. ``f(x, *args)`` must have different signs at the two endpoints. xtol : float, optional Tolerance (absolute) for termination. @@ -386,7 +386,7 @@ def _root_scalar_toms748_doc(): args : tuple, optional Extra arguments passed to the objective function. bracket: A sequence of 2 floats, optional - An interval bracketing a root. `f(x, *args)` must have different + An interval bracketing a root. ``f(x, *args)`` must have different signs at the two endpoints. xtol : float, optional Tolerance (absolute) for termination. @@ -488,7 +488,7 @@ def _root_scalar_ridder_doc(): args : tuple, optional Extra arguments passed to the objective function. bracket: A sequence of 2 floats, optional - An interval bracketing a root. `f(x, *args)` must have different + An interval bracketing a root. ``f(x, *args)`` must have different signs at the two endpoints. xtol : float, optional Tolerance (absolute) for termination. @@ -510,7 +510,7 @@ def _root_scalar_bisect_doc(): args : tuple, optional Extra arguments passed to the objective function. bracket: A sequence of 2 floats, optional - An interval bracketing a root. `f(x, *args)` must have different + An interval bracketing a root. ``f(x, *args)`` must have different signs at the two endpoints. xtol : float, optional Tolerance (absolute) for termination. diff --git a/scipy/optimize/_slsqp_py.py b/scipy/optimize/_slsqp_py.py index b6b78aafdd2c..19b5541c9cbf 100644 --- a/scipy/optimize/_slsqp_py.py +++ b/scipy/optimize/_slsqp_py.py @@ -103,15 +103,15 @@ def fmin_slsqp(func, x0, eqcons=(), f_eqcons=None, ieqcons=(), f_ieqcons=None, A list of tuples specifying the lower and upper bound for each independent variable [(xl0, xu0),(xl1, xu1),...] Infinite values will be interpreted as large floating values. - fprime : callable `f(x,*args)`, optional + fprime : callable ``f(x,*args)``, optional A function that evaluates the partial derivatives of func. - fprime_eqcons : callable `f(x,*args)`, optional - A function of the form `f(x, *args)` that returns the m by n + fprime_eqcons : callable ``f(x,*args)``, optional + A function of the form ``f(x, *args)`` that returns the m by n array of equality constraint normals. If not provided, the normals will be approximated. The array returned by fprime_eqcons should be sized as ( len(eqcons), len(x0) ). - fprime_ieqcons : callable `f(x,*args)`, optional - A function of the form `f(x, *args)` that returns the m by n + fprime_ieqcons : callable ``f(x,*args)``, optional + A function of the form ``f(x, *args)`` that returns the m by n array of inequality constraint normals. If not provided, the normals will be approximated. The array returned by fprime_ieqcons should be sized as ( len(ieqcons), len(x0) ). @@ -233,7 +233,7 @@ def _minimize_slsqp(func, x0, args=(), jac=None, bounds=None, maxiter : int Maximum number of iterations. finite_diff_rel_step : None or array_like, optional - If `jac in ['2-point', '3-point', 'cs']` the relative step size to + If ``jac in ['2-point', '3-point', 'cs']`` the relative step size to use for numerical approximation of `jac`. The absolute step size is computed as ``h = rel_step * sign(x) * max(1, abs(x))``, possibly adjusted to fit into the bounds. For ``method='3-point'`` diff --git a/scipy/optimize/_tnc.py b/scipy/optimize/_tnc.py index 0f0b3be74036..cdd461829a5f 100644 --- a/scipy/optimize/_tnc.py +++ b/scipy/optimize/_tnc.py @@ -341,7 +341,7 @@ def _minimize_tnc(fun, x0, args=(), jac=None, bounds=None, rescaling. If 0, rescale at each iteration. If a large value, never rescale. If < 0, rescale is set to 1.3. finite_diff_rel_step : None or array_like, optional - If `jac in ['2-point', '3-point', 'cs']` the relative step size to + If ``jac in ['2-point', '3-point', 'cs']`` the relative step size to use for numerical approximation of the jacobian. The absolute step size is computed as ``h = rel_step * sign(x) * max(1, abs(x))``, possibly adjusted to fit into the bounds. For ``method='3-point'`` diff --git a/scipy/optimize/_zeros_py.py b/scipy/optimize/_zeros_py.py index 986031920d69..ccffcb207d6c 100644 --- a/scipy/optimize/_zeros_py.py +++ b/scipy/optimize/_zeros_py.py @@ -1276,7 +1276,7 @@ def toms748(f, a, b, args=(), k=1, Find a root using TOMS Algorithm 748 method. Implements the Algorithm 748 method of Alefeld, Potro and Shi to find a - root of the function `f` on the interval `[a , b]`, where `f(a)` and + root of the function `f` on the interval ``[a , b]``, where ``f(a)`` and `f(b)` must have opposite signs. It uses a mixture of inverse cubic interpolation and diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index fc680e6dad26..0ce6676e1cb5 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -3837,7 +3837,7 @@ def sosfilt_zi(sos): 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 + 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, diff --git a/scipy/special/_add_newdocs.py b/scipy/special/_add_newdocs.py index 44079df5df3c..74d98b7abfe8 100644 --- a/scipy/special/_add_newdocs.py +++ b/scipy/special/_add_newdocs.py @@ -8288,7 +8288,7 @@ def add_newdoc(name, doc): 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 + ``y = nbdtr(k, n, p)``, the negative binomial cumulative distribution function. Parameters @@ -8394,7 +8394,7 @@ def add_newdoc(name, doc): Negative binomial percentile function. Returns the inverse with respect to the parameter `k` of - `y = nbdtr(k, n, p)`, the negative binomial cumulative distribution + ``y = nbdtr(k, n, p)``, the negative binomial cumulative distribution function. Parameters @@ -8501,7 +8501,7 @@ def add_newdoc(name, doc): 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 + ``y = nbdtr(k, n, p)``, the negative binomial cumulative distribution function. Parameters diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py index 2234cc3fd75b..787203f6752b 100644 --- a/scipy/stats/_continuous_distns.py +++ b/scipy/stats/_continuous_distns.py @@ -5274,8 +5274,8 @@ class norminvgauss_gen(rv_continuous): 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 + ``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 diff --git a/scipy/stats/_crosstab.py b/scipy/stats/_crosstab.py index 6c267ff85eaf..e938eacad044 100644 --- a/scipy/stats/_crosstab.py +++ b/scipy/stats/_crosstab.py @@ -85,7 +85,7 @@ def crosstab(*args, levels=None, sparse=False): array([[2, 3, 0], [1, 0, 4]]) - So `('A', 'X')` occurs twice, `('A', 'Y')` occurs three times, etc. + So ``('A', 'X')`` occurs twice, ``('A', 'Y')`` occurs three times, etc. Higher dimensional contingency tables can be created. diff --git a/scipy/stats/_distn_infrastructure.py b/scipy/stats/_distn_infrastructure.py index 50611b9b9f3d..256810ba0913 100644 --- a/scipy/stats/_distn_infrastructure.py +++ b/scipy/stats/_distn_infrastructure.py @@ -1616,7 +1616,7 @@ def __init__(self, name, integrality=False, domain=(-np.inf, np.inf), def _get_fixed_fit_value(kwds, names): """ - Given names such as `['f0', 'fa', 'fix_a']`, check that there is + Given names such as ``['f0', 'fa', 'fix_a']``, check that there is at most one non-None value in `kwds` associaed 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 diff --git a/scipy/stats/_mgc.py b/scipy/stats/_mgc.py index ea905bba9994..4df5da449ec2 100644 --- a/scipy/stats/_mgc.py +++ b/scipy/stats/_mgc.py @@ -38,7 +38,7 @@ def _perm_test(x, y, stat, reps=1000, workers=-1, random_state=None): Parameters ---------- x, y : ndarray - `x` and `y` have shapes `(n, p)` and `(n, q)`. + `x` and `y` have shapes ``(n, p)`` and ``(n, q)``. stat : float The sample test statistic. reps : int, optional @@ -167,7 +167,7 @@ def multiscale_graphcorr(x, y, compute_distance=_euclidean_dist, reps=1000, An object containing attributes: statistic : float - The sample MGC test statistic within `[-1, 1]`. + The sample MGC test statistic within ``[-1, 1]``. pvalue : float The p-value obtained via permutation. mgc_dict : dict @@ -177,7 +177,7 @@ def multiscale_graphcorr(x, y, compute_distance=_euclidean_dist, reps=1000, A 2D representation of the latent geometry of the relationship. - opt_scale : (int, int) - The estimated optimal scale as a `(x, y)` pair. + The estimated optimal scale as a ``(x, y)`` pair. - null_dist : list The null distribution derived from the permuted matrices. @@ -384,14 +384,14 @@ def _mgc_stat(distx, disty): Parameters ---------- distx, disty : ndarray - `distx` and `disty` have shapes `(n, p)` and `(n, q)` or - `(n, n)` and `(n, n)` + `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]`. + The sample MGC test statistic within ``[-1, 1]``. stat_dict : dict Contains additional useful additional returns containing the following keys: @@ -399,7 +399,7 @@ def _mgc_stat(distx, disty): - stat_mgc_map : ndarray MGC-map of the statistics. - opt_scale : (float, float) - The estimated optimal scale as a `(x, y)` pair. + The estimated optimal scale as a ``(x, y)`` pair. """ # calculate MGC map and optimal scale @@ -434,7 +434,7 @@ def _threshold_mgc_map(stat_mgc_map, samp_size): Parameters ---------- stat_mgc_map : ndarray - All local correlations within `[-1,1]`. + All local correlations within ``[-1,1]``. samp_size : int The sample size of original data. @@ -483,14 +483,14 @@ def _smooth_mgc_map(sig_connect, stat_mgc_map): sig_connect : ndarray A binary matrix with 1's indicating the significant region. stat_mgc_map : ndarray - All local correlations within `[-1, 1]`. + All local correlations within ``[-1, 1]``. Returns ------- stat : float - The sample MGC statistic within `[-1, 1]`. + The sample MGC statistic within ``[-1, 1]``. opt_scale: (float, float) - The estimated optimal scale as an `(x, y)` pair. + The estimated optimal scale as an ``(x, y)`` pair. """ m, n = stat_mgc_map.shape @@ -531,16 +531,16 @@ def _two_sample_transform(u, v): Parameters ---------- u, v : ndarray - `u` and `v` have shapes `(n, p)` and `(m, p)`. + `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)`. + ``(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)`. + 1 refers to samples that come from `v`. `y` thus has shape ``(2n, 1)``. """ nx = u.shape[0] diff --git a/scipy/stats/_multivariate.py b/scipy/stats/_multivariate.py index 5d8c3cb0298c..fe226704a7d5 100644 --- a/scipy/stats/_multivariate.py +++ b/scipy/stats/_multivariate.py @@ -798,7 +798,7 @@ def fit(self, x, fix_mean=None, fix_cov=None): fix_mean : ndarray(n, ) Fixed mean vector. Must have length `n`. fix_cov: ndarray (n, n) - Fixed covariance matrix. Must have shape `(n, n)`. + Fixed covariance matrix. Must have shape ``(n, n)``. Returns ------- diff --git a/scipy/stats/_qmvnt.py b/scipy/stats/_qmvnt.py index a0e9c5ebb3cb..55da67a3b722 100644 --- a/scipy/stats/_qmvnt.py +++ b/scipy/stats/_qmvnt.py @@ -101,7 +101,7 @@ def _cbc_lattice(n_dim, n_qmc_samples): ------- q : float array : shape=(n_dim,) The lattice generator vector. All values are in the open interval - `(0, 1)`. + ``(0, 1)``. actual_n_qmc_samples : int The prime number of QMC samples that must be used with this lattice, no more, no less. From 5383624e90f02a3f1924ee8439a41be919bd8eb5 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:59:36 +0100 Subject: [PATCH 424/500] DEP: `integrate.cumulative_trapezoid`: raise `ValueError` for values of `initial` other than `None` and `0` (#20867) Co-authored-by: Lucas Colley --- scipy/integrate/_quadrature.py | 12 +----------- scipy/integrate/tests/test_quadrature.py | 7 +++---- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/scipy/integrate/_quadrature.py b/scipy/integrate/_quadrature.py index 9da7b85329ff..6aa36c7ab567 100644 --- a/scipy/integrate/_quadrature.py +++ b/scipy/integrate/_quadrature.py @@ -271,11 +271,6 @@ def cumulative_trapezoid(y, x=None, dx=1.0, axis=-1, initial=None): 0 or None are the only values accepted. Default is None, which means `res` has one element less than `y` along the axis of integration. - .. deprecated:: 1.12.0 - The option for non-zero inputs for `initial` will be deprecated in - SciPy 1.15.0. After this time, a ValueError will be raised if - `initial` is not None or 0. - Returns ------- res : ndarray @@ -337,12 +332,7 @@ def cumulative_trapezoid(y, x=None, dx=1.0, axis=-1, initial=None): if initial is not None: if initial != 0: - warnings.warn( - "The option for values for `initial` other than None or 0 is " - "deprecated as of SciPy 1.12.0 and will raise a value error in" - " SciPy 1.15.0.", - DeprecationWarning, stacklevel=2 - ) + raise ValueError("`initial` must be `None` or `0`.") if not np.isscalar(initial): raise ValueError("`initial` parameter should be a scalar.") diff --git a/scipy/integrate/tests/test_quadrature.py b/scipy/integrate/tests/test_quadrature.py index 25c28e342530..f2c2d25b32e8 100644 --- a/scipy/integrate/tests/test_quadrature.py +++ b/scipy/integrate/tests/test_quadrature.py @@ -253,12 +253,11 @@ def test_x_none(self): @pytest.mark.parametrize( "initial", [1, 0.5] ) - def test_initial_warning(self, initial): + def test_initial_error(self, initial): """If initial is not None or 0, a ValueError is raised.""" y = np.linspace(0, 10, num=10) - with pytest.deprecated_call(match="`initial`"): - res = cumulative_trapezoid(y, initial=initial) - assert_allclose(res, [initial, *np.cumsum(y[1:] + y[:-1])/2]) + with pytest.raises(ValueError, match="`initial`"): + cumulative_trapezoid(y, initial=initial) def test_zero_len_y(self): with pytest.raises(ValueError, match="At least one point is required"): From 78a3420e86f15ed6ac8bf49682f43ca253b13a32 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 17 Jun 2024 09:24:40 -0700 Subject: [PATCH 425/500] STY: _lib._util: silence mypy [skip cirrus] [skip circle] --- scipy/_lib/_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py index ee55282dd532..b4f6a07422be 100644 --- a/scipy/_lib/_util.py +++ b/scipy/_lib/_util.py @@ -28,7 +28,7 @@ DTypePromotionError ) else: - from numpy import ( + from numpy import ( # type: ignore[attr-defined, no-redef] AxisError, ComplexWarning, VisibleDeprecationWarning # noqa: F401 ) DTypePromotionError = TypeError # type: ignore From 00318dfd24c48d8645858db36049955148a5dba8 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 17 Jun 2024 12:41:57 -0700 Subject: [PATCH 426/500] DOC: stats.tmean: improve documentation of put_val_to_limits --- scipy/_lib/_util.py | 13 ++++++++++--- scipy/stats/_stats_py.py | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py index ee55282dd532..c7569e6ff458 100644 --- a/scipy/_lib/_util.py +++ b/scipy/_lib/_util.py @@ -141,9 +141,16 @@ def _lazywhere(cond, arrays, f, fillvalue=None, f2=None): temp1 = xp.asarray(f(*(arr[cond] for arr in arrays))) if f2 is None: - fillvalue = xp.asarray(fillvalue) - dtype = xp.result_type(temp1.dtype, fillvalue.dtype) - out = xp.full(cond.shape, fill_value=fillvalue, dtype=dtype) + # If `fillvalue` is a Python scalar and we convert to `xp.asarray`, it gets the + # default `int` or `float` type of `xp`, so `result_type` could be wrong. + # `result_type` should/will handle mixed array/Python scalars + # (data-apis/array-api#805) but doesn't yet. So in the meantime, we need our + # own logic. + if type(fillvalue) in {int, float, complex, bool}: + dtype = (xp.asarray([0], dtype=temp1.dtype) * fillvalue).dtype + else: + dtype = xp.result_type(temp1.dtype, fillvalue.dtype) + out = xp.full(cond.shape, fill_value=xp.asarray(fillvalue), dtype=dtype) else: ncond = ~cond temp2 = xp.asarray(f2(*(arr[ncond] for arr in arrays))) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 3bfc985df43c..6d4b3904cb39 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -535,7 +535,7 @@ def mode(a, axis=0, nan_policy='propagate', keepdims=False): def _put_val_to_limits(a, limits, inclusive, val=np.nan, xp=None): - """Put NaNs in an array for values outside of given limits. + """Replace elements outside limits with a value. This is primarily a utility function. @@ -543,12 +543,14 @@ def _put_val_to_limits(a, limits, inclusive, val=np.nan, xp=None): ---------- a : array limits : (float or None, float or None) - A tuple consisting of the (lower limit, upper limit). Values in the + 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 `np.nan`. None implies no 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 @@ -564,7 +566,10 @@ def _put_val_to_limits(a, limits, inclusive, val=np.nan, xp=None): if xp.all(mask): raise ValueError("No array values within given limits") if xp.any(mask): - a = xp.where(mask, xp.asarray(val), a) + # hopefully this (and many other instances of this idiom) are temporary when + # data-apis/array-api#807 is resolved + dtype = xp.asarray(1.).dtype if xp.isdtype(a.dtype, 'integral') else a.dtype + a = xp.where(mask, xp.asarray(val, dtype=dtype), a) return a, mask @@ -616,8 +621,9 @@ def tmean(a, limits=None, inclusive=(True, True), axis=None): """ xp = array_namespace(a) a, mask = _put_val_to_limits(a, limits, inclusive, val=0., xp=xp) - sum = xp.sum(a, axis=axis) - n = xp.sum(xp.asarray(~mask, dtype=a.dtype), axis=axis) + # 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), axis=axis, dtype=a.dtype) mean = _lazywhere(n != 0, (sum, n), xp.divide, xp.nan) return mean[()] if mean.ndim == 0 else mean From 055ea4532f2a15c68ac0db6786af005933bd0b87 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Tue, 18 Jun 2024 00:05:20 +0100 Subject: [PATCH 427/500] ENH: stats.combine_pvalues: add native axis support (#20974) * ENH: stats.combine_pvalues: add native axis support Co-authored-by: Matt Haberland --- scipy/stats/_stats_py.py | 22 +++++++++++++--------- scipy/stats/tests/test_stats.py | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 27f670ed9940..c48763a7090e 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -9168,7 +9168,7 @@ def brunnermunzel(x, y, alternative="two-sided", distribution="t", @_axis_nan_policy_factory(SignificanceResult, kwd_samples=['weights'], paired=True) -def combine_pvalues(pvalues, method='fisher', weights=None): +def combine_pvalues(pvalues, method='fisher', weights=None, *, axis=0): """ Combine p-values from independent tests that bear upon the same hypothesis. @@ -9292,43 +9292,47 @@ def combine_pvalues(pvalues, method='fisher', weights=None): xp = array_namespace(pvalues) pvalues = xp.asarray(pvalues) 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 = pvalues.shape[0] + n = pvalues.shape[axis] # used to convert Python scalar to the right dtype one = xp.asarray(1, dtype=pvalues.dtype) if method == 'fisher': - statistic = -2 * xp.sum(xp.log(pvalues)) + statistic = -2 * xp.sum(xp.log(pvalues), axis=axis) chi2 = _SimpleChi2(2*n*one) pval = _get_pvalue(statistic, chi2, alternative='greater', symmetric=False, xp=xp) elif method == 'pearson': - statistic = 2 * xp.sum(xp.log1p(-pvalues)) + statistic = 2 * xp.sum(xp.log1p(-pvalues), axis=axis) chi2 = _SimpleChi2(2*n*one) pval = _get_pvalue(-statistic, chi2, alternative='less', symmetric=False, xp=xp) elif method == 'mudholkar_george': normalizing_factor = math.sqrt(3/n)/xp.pi - statistic = -xp.sum(xp.log(pvalues)) + xp.sum(xp.log1p(-pvalues)) + statistic = (-xp.sum(xp.log(pvalues), axis=axis) + + xp.sum(xp.log1p(-pvalues), axis=axis)) nu = 5*n + 4 approx_factor = math.sqrt(nu / (nu - 2)) t = _SimpleStudentT(nu*one) pval = _get_pvalue(statistic * normalizing_factor * approx_factor, t, alternative="greater", xp=xp) elif method == 'tippett': - statistic = xp.min(pvalues) + statistic = xp.min(pvalues, axis=axis) beta = _SimpleBeta(one, n*one) 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) - elif weights.shape[0] != n: - raise ValueError("pvalues and weights must be of the same size.") + elif weights.shape[axis] != n: + raise ValueError("pvalues and weights must be of the same " + "length along `axis`.") norm = _SimpleNormal() Zi = norm.isf(pvalues) - statistic = weights @ Zi / xp.linalg.vector_norm(weights) + statistic = weights @ Zi / xp.linalg.vector_norm(weights, axis=axis) pval = _get_pvalue(statistic, norm, alternative="greater", xp=xp) else: diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 81dc659daa31..e937f94c15f4 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -8042,6 +8042,33 @@ def test_result(self, method, xp): xp_assert_equal(res.statistic, res[0]) xp_assert_equal(res.pvalue, res[1]) + @pytest.mark.parametrize("method", methods) + # axis=None is currently broken for array API; will be handled when + # axis_nan_policy decorator is updated + @pytest.mark.parametrize("axis", [0, 1]) + 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) + + if axis is None: + x = xp.reshape(x, (-1,)) + ref = stats.combine_pvalues(x) + 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) + ref1 = stats.combine_pvalues(x1) + + 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: """ From 3a809e85627ccd331efce8cc98c31953ff504285 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 17 Jun 2024 19:21:08 -0700 Subject: [PATCH 428/500] MAINT: _lib._util._lazywhere: preserve type with Python scalar fillvalue --- scipy/_lib/_array_api.py | 50 ++++++++++++++++++------- scipy/_lib/_util.py | 9 ++--- scipy/_lib/tests/test__util.py | 31 ++++++++------- scipy/_lib/tests/test_array_api.py | 19 +++++++++- scipy/constants/tests/test_constants.py | 7 ++-- scipy/stats/tests/test_stats.py | 4 ++ 6 files changed, 81 insertions(+), 39 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 5cce1726013f..2e483e754d33 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -229,16 +229,23 @@ def is_cupy(xp: ModuleType) -> bool: def is_torch(xp: ModuleType) -> bool: return xp.__name__ in ('torch', 'scipy._lib.array_api_compat.torch') + def is_jax(xp): return xp.__name__ in ('jax.numpy', 'jax.experimental.array_api') +def is_array_api_strict(xp): + return xp.__name__ == 'array_api_strict' + + def _strict_check(actual, desired, xp, - check_namespace=True, check_dtype=True, check_shape=True): + check_namespace=True, check_dtype=True, check_shape=True, + allow_0d=False): __tracebackhide__ = True # Hide traceback for py.test if check_namespace: _assert_matching_namespace(actual, desired) + was_scalar = np.isscalar(desired) desired = xp.asarray(desired) if check_dtype: @@ -248,7 +255,7 @@ def _strict_check(actual, desired, xp, if check_shape: _msg = f"Shapes do not match.\nActual: {actual.shape}\nDesired: {desired.shape}" assert actual.shape == desired.shape, _msg - _check_scalar(actual, desired, xp) + _check_scalar(actual, desired, xp, allow_0d=allow_0d, was_scalar=was_scalar) desired = xp.broadcast_to(desired, actual.shape) return desired @@ -266,7 +273,7 @@ def _assert_matching_namespace(actual, desired): assert arr_space == desired_space, _msg -def _check_scalar(actual, desired, xp): +def _check_scalar(actual, desired, xp, *, allow_0d, was_scalar): __tracebackhide__ = True # Hide traceback for py.test # Shape check alone is sufficient unless desired.shape == (). Also, # only NumPy distinguishes between scalars and arrays. @@ -285,19 +292,31 @@ def _check_scalar(actual, desired, xp): # array for `desired`, we would typically want the type of `actual` to be # the type of `desired[()]`. If the developer wants to override this # behavior, they can set `check_shape=False`. - desired = desired[()] - _msg = f"Types do not match:\n Actual: {type(actual)}\n Desired: {type(desired)}" - assert (xp.isscalar(actual) and xp.isscalar(desired) - or (not xp.isscalar(actual) and not xp.isscalar(desired))), _msg + if was_scalar: + desired = desired[()] + + if allow_0d: + _msg = ("Types do not match:\n Actual: " + f"{type(actual)}\n Desired: {type(desired)}") + assert ((xp.isscalar(actual) and xp.isscalar(desired)) + or (not xp.isscalar(actual) and not xp.isscalar(desired))), _msg + else: + _msg = ("Result is a NumPy 0d array. Many SciPy functions intend to follow " + "the convention of many NumPy functions, returning a scalar when a " + "0d array would be correct. `xp_assert_` functions err on the side of " + "caution and do not accept 0d arrays by default. If the correct result " + "may be a 0d NumPy array, pass `allow_0d=True`.") + assert xp.isscalar(actual), _msg def xp_assert_equal(actual, desired, check_namespace=True, check_dtype=True, - check_shape=True, err_msg='', xp=None): + check_shape=True, allow_0d=False, err_msg='', xp=None): __tracebackhide__ = True # Hide traceback for py.test if xp is None: xp = array_namespace(actual) desired = _strict_check(actual, desired, xp, check_namespace=check_namespace, - check_dtype=check_dtype, check_shape=check_shape) + check_dtype=check_dtype, check_shape=check_shape, + allow_0d=allow_0d) if is_cupy(xp): return xp.testing.assert_array_equal(actual, desired, err_msg=err_msg) elif is_torch(xp): @@ -311,12 +330,14 @@ def xp_assert_equal(actual, desired, check_namespace=True, check_dtype=True, def xp_assert_close(actual, desired, rtol=None, atol=0, check_namespace=True, - check_dtype=True, check_shape=True, err_msg='', xp=None): + check_dtype=True, check_shape=True, allow_0d=False, + err_msg='', xp=None): __tracebackhide__ = True # Hide traceback for py.test if xp is None: xp = array_namespace(actual) desired = _strict_check(actual, desired, xp, check_namespace=check_namespace, - check_dtype=check_dtype, check_shape=check_shape) + check_dtype=check_dtype, check_shape=check_shape, + allow_0d=allow_0d) floating = xp.isdtype(actual.dtype, ('real floating', 'complex floating')) if rtol is None and floating: @@ -340,12 +361,13 @@ def xp_assert_close(actual, desired, rtol=None, atol=0, check_namespace=True, def xp_assert_less(actual, desired, check_namespace=True, check_dtype=True, - check_shape=True, err_msg='', verbose=True, xp=None): + check_shape=True, allow_0d=False, err_msg='', verbose=True, xp=None): __tracebackhide__ = True # Hide traceback for py.test if xp is None: xp = array_namespace(actual) desired = _strict_check(actual, desired, xp, check_namespace=check_namespace, - check_dtype=check_dtype, check_shape=check_shape) + check_dtype=check_dtype, check_shape=check_shape, + allow_0d=allow_0d) if is_cupy(xp): return xp.testing.assert_array_less(actual, desired, err_msg=err_msg, verbose=verbose) @@ -428,7 +450,7 @@ def get_xp_devices(xp: ModuleType) -> list[str] | list[None]: # given namespace is not known to have a list of available devices; # return `[None]` so that one can use this in tests for `device=None`. - return [None] + return [None] def scipy_namespace_for(xp: ModuleType) -> ModuleType: diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py index c7569e6ff458..ff5d192bfdfa 100644 --- a/scipy/_lib/_util.py +++ b/scipy/_lib/_util.py @@ -144,12 +144,9 @@ def _lazywhere(cond, arrays, f, fillvalue=None, f2=None): # If `fillvalue` is a Python scalar and we convert to `xp.asarray`, it gets the # default `int` or `float` type of `xp`, so `result_type` could be wrong. # `result_type` should/will handle mixed array/Python scalars - # (data-apis/array-api#805) but doesn't yet. So in the meantime, we need our - # own logic. - if type(fillvalue) in {int, float, complex, bool}: - dtype = (xp.asarray([0], dtype=temp1.dtype) * fillvalue).dtype - else: - dtype = xp.result_type(temp1.dtype, fillvalue.dtype) + # (data-apis/array-api#805) but doesn't yet. So in the meantime, this fails + # for array-api-strict. + dtype = xp.result_type(temp1.dtype, fillvalue) out = xp.full(cond.shape, fill_value=xp.asarray(fillvalue), dtype=dtype) else: ncond = ~cond diff --git a/scipy/_lib/tests/test__util.py b/scipy/_lib/tests/test__util.py index 163c0ba201c4..663945f276d7 100644 --- a/scipy/_lib/tests/test__util.py +++ b/scipy/_lib/tests/test__util.py @@ -13,7 +13,7 @@ from scipy.conftest import array_api_compatible, skip_xp_invalid_arg from scipy._lib._array_api import (xp_assert_equal, xp_assert_close, is_numpy, - copy as xp_copy) + copy as xp_copy, is_array_api_strict) from scipy._lib._util import (_aligned_zeros, check_random_state, MapWrapper, getfullargspec_no_self, FullArgSpec, rng_integers, _validate_int, _rename_parameter, @@ -407,7 +407,10 @@ def test_basic(self, n_arrays, rng_seed, dtype, p, data, xp): min_side=0) input_shapes, result_shape = data.draw(mbs) cond_shape, *shapes = input_shapes - fillvalue = xp.asarray(data.draw(npst.arrays(dtype=dtype, shape=tuple()))) + elements = {'allow_subnormal': False} # cupy/cupy#8382 + fillvalue = xp.asarray(data.draw(npst.arrays(dtype=dtype, shape=tuple(), + elements=elements))) + float_fillvalue = float(fillvalue) arrays = [xp.asarray(data.draw(npst.arrays(dtype=dtype, shape=shape))) for shape in shapes] @@ -422,6 +425,8 @@ def f2(*args): res1 = _lazywhere(cond, arrays, f, fillvalue) res2 = _lazywhere(cond, arrays, f, f2=f2) + if not is_array_api_strict(xp): + res3 = _lazywhere(cond, arrays, f, float_fillvalue) # Ensure arrays are at least 1d to follow sane type promotion rules. if xp == np: @@ -429,19 +434,17 @@ def f2(*args): ref1 = xp.where(cond, f(*arrays), fillvalue) ref2 = xp.where(cond, f(*arrays), f2(*arrays)) + if not is_array_api_strict(xp): + # Array API standard doesn't currently define behavior when fillvalue is a + # Python scalar. When it does, test can be run with array_api_strict, too. + ref3 = xp.where(cond, f(*arrays), float_fillvalue) if xp == np: ref1 = ref1.reshape(result_shape) ref2 = ref2.reshape(result_shape) - res1 = xp.asarray(res1)[()] - res2 = xp.asarray(res2)[()] - - isinstance(res1, type(xp.asarray([]))) - xp_assert_close(res1, ref1, rtol=2e-16) - assert_equal(res1.shape, ref1.shape) - assert_equal(res1.dtype, ref1.dtype) - - isinstance(res2, type(xp.asarray([]))) - xp_assert_equal(res2, ref2) - assert_equal(res2.shape, ref2.shape) - assert_equal(res2.dtype, ref2.dtype) + ref3 = ref3.reshape(result_shape) + + xp_assert_close(res1, ref1, rtol=2e-16, allow_0d=True) + xp_assert_equal(res2, ref2, allow_0d=True) + if not is_array_api_strict(xp): + xp_assert_equal(res3, ref3, allow_0d=True) diff --git a/scipy/_lib/tests/test_array_api.py b/scipy/_lib/tests/test_array_api.py index 3ca0ae36f030..85bedb9a18a9 100644 --- a/scipy/_lib/tests/test_array_api.py +++ b/scipy/_lib/tests/test_array_api.py @@ -101,7 +101,8 @@ def test_strict_checks(self, xp, dtype, shape): xp_assert_equal(x, y, **options) else: with pytest.raises(AssertionError, match="Shapes do not match."): - xp_assert_equal(x, y, **options) + xp_assert_equal(x, xp.asarray(y), allow_0d=True, **options) + @array_api_compatible def test_check_scalar(self, xp): @@ -109,6 +110,20 @@ def test_check_scalar(self, xp): pytest.skip("Scalars only exist in NumPy") if is_numpy(xp): - with pytest.raises(AssertionError, match="Types do not match."): + # Check default convention: 0d arrays are not allowed + message = "Result is a NumPy 0d array. Many SciPy functions..." + with pytest.raises(AssertionError, match=message): xp_assert_equal(xp.asarray(0.), xp.float64(0)) + with pytest.raises(AssertionError, match=message): + xp_assert_equal(xp.asarray(0.), xp.asarray(0.)) xp_assert_equal(xp.float64(0), xp.asarray(0.)) + xp_assert_equal(xp.float64(0), xp.float64(0)) + + # Check `allow_0d` + message = "Types do not match:\n..." + with pytest.raises(AssertionError, match=message): + xp_assert_equal(xp.asarray(0.), xp.float64(0), allow_0d=True) + with pytest.raises(AssertionError, match=message): + xp_assert_equal(xp.float64(0), xp.asarray(0.), allow_0d=True) + xp_assert_equal(xp.float64(0), xp.float64(0), allow_0d=True) + xp_assert_equal(xp.asarray(0.), xp.asarray(0.), allow_0d=True) diff --git a/scipy/constants/tests/test_constants.py b/scipy/constants/tests/test_constants.py index 9f5c241b13f7..6693ad482e8b 100644 --- a/scipy/constants/tests/test_constants.py +++ b/scipy/constants/tests/test_constants.py @@ -3,6 +3,7 @@ import scipy.constants as sc from scipy.conftest import array_api_compatible from scipy._lib._array_api import xp_assert_equal, xp_assert_close +from numpy.testing import assert_allclose pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends")] @@ -53,7 +54,7 @@ def test_convert_temperature(self, xp): @skip_xp_backends(np_only=True, reasons=['Python list input uses NumPy backend']) def test_convert_temperature_array_like(self): - xp_assert_close(sc.convert_temperature([491.67, 0.], 'rankine', 'kelvin'), + assert_allclose(sc.convert_temperature([491.67, 0.], 'rankine', 'kelvin'), [273.15, 0.], rtol=0., atol=1e-13) @@ -73,7 +74,7 @@ def test_lambda_to_nu(self, xp): @skip_xp_backends(np_only=True, reasons=['Python list input uses NumPy backend']) def test_lambda_to_nu_array_like(self, xp): - xp_assert_equal(sc.lambda2nu([sc.speed_of_light, 1]), + assert_allclose(sc.lambda2nu([sc.speed_of_light, 1]), [1, sc.speed_of_light]) @@ -84,6 +85,6 @@ def test_nu_to_lambda(self, xp): @skip_xp_backends(np_only=True, reasons=['Python list input uses NumPy backend']) def test_nu_to_lambda_array_like(self, xp): - xp_assert_equal(sc.nu2lambda([sc.speed_of_light, 1]), + assert_allclose(sc.nu2lambda([sc.speed_of_light, 1]), [1, sc.speed_of_light]) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 9d920ab63917..1d50ed4a7e6b 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -78,6 +78,10 @@ class TestTrimmedStats: dprec = np.finfo(np.float64).precision @array_api_compatible + @skip_xp_backends('array_api_strict', + reasons=["`array_api_strict.where` `fillvalue` doesn't " + "accept Python floats. See data-apis/array-api#807."]) + @pytest.mark.usefixtures("skip_xp_backends") def test_tmean(self, xp): x = xp.asarray(X) From 6594ebed26a04dfdb67de45a0da24ab517a296ce Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Mon, 17 Jun 2024 21:46:25 -0600 Subject: [PATCH 429/500] CI, MAINT: test_plot_iv NumPy 2 shim * Remove the guard inside `test_plot_iv()` now that `matplotlib` provides a NumPy 2 compatible version. * Similarly, reactivate `matplotlib` prerelease CI testing for one case where it was disabled for a similar reason. [skip circle] [skip cirrus] --- .github/workflows/linux.yml | 5 +---- scipy/stats/tests/test_fit.py | 10 +++------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index df8d9b42310c..e30f3c0ef646 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -317,10 +317,7 @@ jobs: - name: Install Python packages run: | python -m pip install cython pythran ninja meson-python pybind11 click rich_click pydevtool - # Note: matplotlib not installed here to avoid issues around numpy 2.0 - # (it can be put back after matplotlib has made a 2.0-compatible - # release on PyPI. - python -m pip install --pre --upgrade pytest pytest-cov pytest-xdist mpmath gmpy2 threadpoolctl pooch hypothesis + python -m pip install --pre --upgrade pytest pytest-cov pytest-xdist mpmath gmpy2 threadpoolctl pooch hypothesis matplotlib python -m pip install -r requirements/openblas.txt # Install numpy last, to ensure we get nightly (avoid possible <2.0 constraints). python -m pip install --pre --upgrade --timeout=60 -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy diff --git a/scipy/stats/tests/test_fit.py b/scipy/stats/tests/test_fit.py index 5fae9aa4cb66..f3264b945596 100644 --- a/scipy/stats/tests/test_fit.py +++ b/scipy/stats/tests/test_fit.py @@ -1030,10 +1030,6 @@ def optimizer(*args, **kwargs): with pytest.raises(ValueError, match=message): res.plot(plot_type='llama') except (ModuleNotFoundError, ImportError): - # Avoid trying to call MPL with numpy 2.0-dev, because that fails - # too often due to ABI mismatches and is hard to avoid. This test - # will work fine again once MPL has done a 2.0-compatible release. - if not np.__version__.startswith('2.0.0.dev0'): - message = r"matplotlib must be installed to use method `plot`." - with pytest.raises(ModuleNotFoundError, match=message): - res.plot(plot_type='llama') + message = r"matplotlib must be installed to use method `plot`." + with pytest.raises(ModuleNotFoundError, match=message): + res.plot(plot_type='llama') From 16fbfa9917e2f4c7cb8f263061786ab10e48b2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 18 Jun 2024 02:53:37 -0500 Subject: [PATCH 430/500] CI: Add macOS to free-threaded wheel release CI (#20951) --- .github/workflows/free_threaded_wheels.yml | 74 +++++++++++++++++++--- tools/wheels/cibw_before_build_macos.sh | 10 +++ 2 files changed, 76 insertions(+), 8 deletions(-) diff --git a/.github/workflows/free_threaded_wheels.yml b/.github/workflows/free_threaded_wheels.yml index ecb0800dc8ad..454cd5b34758 100644 --- a/.github/workflows/free_threaded_wheels.yml +++ b/.github/workflows/free_threaded_wheels.yml @@ -82,6 +82,12 @@ jobs: buildplat: - [ubuntu-22.04, manylinux, x86_64, "", ""] - [ubuntu-22.04, musllinux, x86_64, "", ""] + - [macos-12, macosx, x86_64, openblas, "10.9"] + - [macos-13, macosx, x86_64, accelerate, "14.0"] + # OpenBLAS builds with deployment target set to 12 are failing due + # to relocation issues of gfortran dylibs + # - [macos-14, macosx, arm64, openblas, "12.0"] + - [macos-14, macosx, arm64, accelerate, "14.0"] # TODO: build scipy and set up Windows and MacOS # cibuildwheel does not yet support Mac for free-threaded python # windows is supported but numpy doesn't build on the image yet @@ -103,8 +109,56 @@ jobs: with: python-version: "3.x" + - name: Setup macOS + if: startsWith( matrix.buildplat[0], 'macos-' ) + run: | + if [[ ${{ matrix.buildplat[3] }} == 'accelerate' ]]; then + echo CIBW_CONFIG_SETTINGS=\"setup-args=-Dblas=accelerate\" >> "$GITHUB_ENV" + # Always use preinstalled gfortran for Accelerate builds + ln -s $(which gfortran-13) gfortran + export PATH=$PWD:$PATH + echo "PATH=$PATH" >> "$GITHUB_ENV" + LIB_PATH=$(dirname $(gfortran --print-file-name libgfortran.dylib)) + fi + if [[ ${{ matrix.buildplat[4] }} == '10.9' ]]; then + # Newest version of Xcode that supports macOS 10.9 + XCODE_VER='13.4.1' + else + XCODE_VER='15.2' + fi + CIBW="sudo xcode-select -s /Applications/Xcode_${XCODE_VER}.app" + echo "CIBW_BEFORE_ALL=$CIBW" >> $GITHUB_ENV + # setting SDKROOT necessary when using the gfortran compiler + # installed in cibw_before_build_macos.sh + sudo xcode-select -s /Applications/Xcode_${XCODE_VER}.app + CIBW="MACOSX_DEPLOYMENT_TARGET=${{ matrix.buildplat[4] }}\ + SDKROOT=$(xcrun --sdk macosx --show-sdk-path)\ + PKG_CONFIG_PATH=${{ github.workspace }}" + echo "CIBW_ENVIRONMENT_MACOS=$CIBW" >> "$GITHUB_ENV" + + echo "REPAIR_PATH=$LIB_PATH" >> "$GITHUB_ENV" + + PREFIX=DYLD_LIBRARY_PATH="\$(dirname \$(gfortran --print-file-name libgfortran.dylib))" + # remove libgfortran from location used for linking (if any), to + # check wheel has bundled things correctly and all tests pass without + # needing installed gfortran + POSTFIX=" sudo rm -rf /opt/gfortran-darwin-x86_64-native &&\ + sudo rm -rf /usr/local/gfortran/lib" + CIBW="$PREFIX delocate-listdeps -d {wheel} && echo "-----------" &&\ + $PREFIX delocate-wheel -v $EXCLUDE --require-archs \ + {delocate_archs} -w {dest_dir} {wheel} && echo "-----------" &&\ + delocate-listdeps -d {dest_dir}/*.whl && echo "-----------" &&\ + $POSTFIX" + + # Rename x86 Accelerate wheel to test on macOS 13 runner + if [[ ${{ matrix.buildplat[0] }} == 'macos-13' && ${{ matrix.buildplat[4] }} == '14.0' ]]; then + CIBW+=" && mv {dest_dir}/\$(basename {wheel}) \ + {dest_dir}/\$(echo \$(basename {wheel}) | sed 's/14_0/13_0/')" + fi + echo "CIBW_REPAIR_WHEEL_COMMAND_MACOS=$CIBW" >> "$GITHUB_ENV" + - name: Build wheels - uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 env: CIBW_PRERELEASE_PYTHONS: True CIBW_FREE_THREADED_SUPPORT: True @@ -114,6 +168,12 @@ jobs: # cibw_before_build.sh when a released cython can build numpy CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" + - name: Rename after test (macOS x86 Accelerate only) + # Rename x86 Accelerate wheel back so it targets macOS >= 14 + if: matrix.buildplat[0] == 'macos-13' && matrix.buildplat[4] == '14.0' + run: | + mv ./wheelhouse/*.whl $(find ./wheelhouse -type f -name '*.whl' | sed 's/13_0/14_0/') + - uses: actions/upload-artifact@v4 with: path: ./wheelhouse/*.whl @@ -121,18 +181,16 @@ jobs: ${{ matrix.buildplat[2] }} ${{ matrix.buildplat[3] }} ${{ matrix.buildplat[4] }} - - uses: mamba-org/setup-micromamba@422500192359a097648154e8db4e39bdb6c6eed7 + - uses: conda-incubator/setup-miniconda@v3 with: # for installation of anaconda-client, required for upload to # anaconda.org + # default (and activated) environment name is test # Note that this step is *after* specific pythons have been used to # build and test the wheel - # for installation of anaconda-client, for upload to anaconda.org - # environment will be activated after creation, and in future bash steps - init-shell: bash - environment-name: upload-env - create-args: >- - anaconda-client + auto-update-conda: true + python-version: "3.10" + miniconda-version: "latest" - name: Upload wheels if: success() diff --git a/tools/wheels/cibw_before_build_macos.sh b/tools/wheels/cibw_before_build_macos.sh index 945ff19ba935..1e057360a215 100644 --- a/tools/wheels/cibw_before_build_macos.sh +++ b/tools/wheels/cibw_before_build_macos.sh @@ -59,6 +59,16 @@ if [[ $PLATFORM == "arm64" ]]; then type -p gfortran fi +# TODO: delete along with enabling build isolation by unsetting +# CIBW_BUILD_FRONTEND when scipy is buildable under free-threaded +# python with a released version of cython +FREE_THREADED_BUILD="$(python -c"import sysconfig; print(bool(sysconfig.get_config_var('Py_GIL_DISABLED')))")" +if [[ $FREE_THREADED_BUILD == "True" ]]; then + python -m pip install -U --pre pip + python -m pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy cython + # python -m pip install git+https://github.com/serge-sans-paille/pythran + python -m pip install ninja meson-python pybind11 pythran +fi # Install Openblas python -m pip install -r requirements/openblas.txt From c22b657faf9e8cf19167a82b3bfe65a90a2c5afb Mon Sep 17 00:00:00 2001 From: Nick ODell Date: Tue, 18 Jun 2024 05:21:13 -0600 Subject: [PATCH 431/500] DOC: Add more information about how to use Accelerate (#20912) [docs only] --------- Co-authored-by: Ralf Gommers --- doc/source/building/blas_lapack.rst | 15 ++++++++++++++- doc/source/building/index.rst | 16 ++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/doc/source/building/blas_lapack.rst b/doc/source/building/blas_lapack.rst index fe7429b8c7bf..284c3672bb1e 100644 --- a/doc/source/building/blas_lapack.rst +++ b/doc/source/building/blas_lapack.rst @@ -26,7 +26,20 @@ implementations on conda-forge), use:: $ python -m pip -Csetup-args=-Dblas=blas -Csetup-args=-Dlapack=lapack Other options that should work (as long as they're installed with -``pkg-config`` or CMake support) include ``mkl``, ``atlas`` and ``blis``. +``pkg-config`` or CMake support) include ``mkl``, ``atlas``, ``blis`` and +``accelerate``. + +Note that both Accelerate and ``scipy-openblas`` have flags in ``dev.py`` +that are easier to remember, since they're commonly used for development:: + + $ python dev.py build --with-accelerate + $ python dev.py build --with-scipy-openblas + +The ``-Dlapack`` flag isn't needed for Accelerate, MKL or ``scipy-openblas``, +since we can be sure that BLAS and LAPACK are the same for those options. +E.g., to create a wheel with Accelerate (on macOS >=13.3 only), use:: + + $ python -m build -Csetup-args=-Dblas=accelerate Using pkg-config to detect libraries in a nonstandard location diff --git a/doc/source/building/index.rst b/doc/source/building/index.rst index c34139073788..4661da9b3f5c 100644 --- a/doc/source/building/index.rst +++ b/doc/source/building/index.rst @@ -125,18 +125,18 @@ your system. brew install gfortran openblas pkg-config - .. note:: + To allow the build tools to find OpenBLAS, you must run:: + + brew info openblas | grep PKG_CONFIG_PATH - ``export PKG_CONFIG_PATH="/opt/homebrew/opt/openblas/lib/pkgconfig"`` - may need to be used in order for the build system to detect OpenBlas. + This will give you a command starting with ``export PKG_CONFIG_PATH=``, which + you must run. .. note:: - As of SciPy >=1.2.0, we do not support compiling against the system - Accelerate library for BLAS and LAPACK. It does not support a sufficiently - recent LAPACK interface. This is planned to change in 2023, because macOS - 13.3 introduced a major upgrade to Accelerate which resolved all known - issues. + As of SciPy 1.14.0, we have added support for the Accelerate library + for BLAS and LAPACK. It requires macOS 13.3 or greater. To build with + Accelerate instead of OpenBLAS, see :ref:`blas-lapack-selection`. .. tab-item:: Windows :sync: windows From 9e90db479e720a7ec5ea148f059fae37be3e74cb Mon Sep 17 00:00:00 2001 From: lucascolley Date: Tue, 18 Jun 2024 22:09:54 +0100 Subject: [PATCH 432/500] DOC/DEV: update commit message guidance [docs only] --- .../dev/contributor/development_workflow.rst | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/doc/source/dev/contributor/development_workflow.rst b/doc/source/dev/contributor/development_workflow.rst index 4e7da3421277..93494daa4a42 100644 --- a/doc/source/dev/contributor/development_workflow.rst +++ b/doc/source/dev/contributor/development_workflow.rst @@ -214,15 +214,29 @@ been added to ``upstream`` that affect your work. In this case, follow the Writing the commit message -------------------------- -Commit messages should be clear and follow a few basic rules. Example:: +Commit messages should be clear and follow a few basic rules. - ENH: add functionality X to SciPy.. +Example:: + + MAINT/TST: fft: remove xp backend skips, test `fftfreq` `device` The first line of the commit message starts with a capitalized acronym - (options listed below) indicating what type of commit this is. Then a blank - line, then more text if needed. Lines shouldn't be longer than 72 - characters. If the commit is related to a ticket, indicate that with - "See #3456", "See ticket 3456", "Closes #3456", or similar. + (or multiple, options listed below) indicating what type of commit this is. + Then a blank line, then more text if needed. + References to code names should be enclosed in backticks. + If changes are limited to certain submodules or functions, they should be + included after the acronym(s) - backticks are not needed here unless the code names + contain an underscore. + +Example:: + + BUG:sparse.linalg.gmres: add early exit when `x0` already solves problem + + Lines shouldn't be longer than 72 characters. If the commit is related to an issue, + indicate that with "See gh-3456", "Closes gh-3456", or similar, + in the extended description. + However, if you are pushing many commits to a PR, you should avoid including + this in every commit message as it will clutter the linked issue. Describing the motivation for a change, the nature of a bug for bug fixes or some details on what an enhancement does are also good to include in a commit From 6a0eaa99389fc61c0972cd0d12cfb69100250c3a Mon Sep 17 00:00:00 2001 From: lucascolley Date: Tue, 18 Jun 2024 22:20:54 +0100 Subject: [PATCH 433/500] DEV: update PR template [docs only] --- .github/PULL_REQUEST_TEMPLATE.md | 4 +++- doc/source/dev/contributor/development_workflow.rst | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 11bfeea6a15e..895c27dad85c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -5,7 +5,9 @@ http://scipy.github.io/devdocs/dev/contributor/development_workflow.html#checkli Also, please name and describe your PR as you would write a commit message: -http://scipy.github.io/devdocs/dev/contributor/development_workflow.html#writing-the-commit-message +http://scipy.github.io/devdocs/dev/contributor/development_workflow.html#writing-the-commit-message. +However, please only include an issue number in the description, not the title, +and please ensure that any code names containing underscores are enclosed in backticks. Depending on your changes, you can skip CI operations and save time and energy: http://scipy.github.io/devdocs/dev/contributor/continuous_integration.html#skipping diff --git a/doc/source/dev/contributor/development_workflow.rst b/doc/source/dev/contributor/development_workflow.rst index 93494daa4a42..82d41c40c03c 100644 --- a/doc/source/dev/contributor/development_workflow.rst +++ b/doc/source/dev/contributor/development_workflow.rst @@ -225,8 +225,7 @@ Example:: Then a blank line, then more text if needed. References to code names should be enclosed in backticks. If changes are limited to certain submodules or functions, they should be - included after the acronym(s) - backticks are not needed here unless the code names - contain an underscore. + included after the acronym(s) - backticks are not needed here. Example:: From 1d59ab9813f052d442f5ae460f016b05aaad20a2 Mon Sep 17 00:00:00 2001 From: m-maggi <124086916+m-maggi@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:43:58 +0200 Subject: [PATCH 434/500] DOC: `integrate.quad_vec`: Add example when using `workers` (#20915) Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/integrate/_quad_vec.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scipy/integrate/_quad_vec.py b/scipy/integrate/_quad_vec.py index 19218d196eb3..74f2ba44cb1f 100644 --- a/scipy/integrate/_quad_vec.py +++ b/scipy/integrate/_quad_vec.py @@ -221,6 +221,23 @@ def quad_vec(f, a, b, epsabs=1e-200, epsrel=1e-8, norm='2', cache_size=100e6, >>> plt.ylabel(r"$\int_{0}^{2} x^\alpha dx$") >>> plt.show() + When using the argument `workers`, one should ensure + that the main module is import-safe, for instance + by rewriting the example above as: + + .. code-block:: python + + from scipy.integrate import quad_vec + import numpy as np + import matplotlib.pyplot as plt + + alpha = np.linspace(0.0, 2.0, num=30) + x0, x1 = 0, 2 + def f(x): + return x**alpha + + if __name__ == "__main__": + y, err = quad_vec(f, x0, x1, workers=2) """ a = float(a) b = float(b) From 40a9aba7ce13dcbac015cf149fe902171d04c4d1 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Thu, 20 Jun 2024 09:41:32 +1000 Subject: [PATCH 435/500] CI: test cp313-dev --- .github/workflows/linux.yml | 7 ++++--- scipy/special/_ellip_harm.pxd | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e30f3c0ef646..38aea317b740 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -26,7 +26,7 @@ jobs: uses: ./.github/workflows/commit_message.yml test_meson: - name: mypy (py3.10) & dev deps (py3.12), fast, dev.py + name: mypy (py3.10) & dev deps (py3.13), fast, dev.py needs: get_commit_message # If using act to run CI locally the github object does not exist and # the usual skipping should not be enforced @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python-version: ['3.10', '3.12'] # this run will use python dev versions when available + python-version: ['3.10', '3.13-dev'] # this run will use python dev versions when available maintenance-branch: - ${{ contains(github.ref, 'maintenance/') || contains(github.base_ref, 'maintenance/') }} exclude: @@ -54,6 +54,7 @@ jobs: python-version: ${{ matrix.python-version }} cache: 'pip' cache-dependency-path: 'environment.yml' + allow-prereleases: true - name: Install Ubuntu dependencies run: | @@ -67,7 +68,7 @@ jobs: python -m pip install numpy cython pytest pytest-xdist pytest-timeout pybind11 mpmath gmpy2 pythran ninja meson click rich-click doit pydevtool pooch hypothesis - name: Install Python packages from repositories - if: matrix.python-version == '3.12' # this run will use python dev versions when available + if: matrix.python-version == '3.13-dev' # this run will use python dev versions when available run: | python -m pip install git+https://github.com/numpy/numpy.git python -m pip install ninja cython pytest pybind11 pytest-xdist pytest-timeout click rich-click doit pydevtool pooch hypothesis "setuptools<67.3" diff --git a/scipy/special/_ellip_harm.pxd b/scipy/special/_ellip_harm.pxd index 5e4172dea688..e5a93e232673 100644 --- a/scipy/special/_ellip_harm.pxd +++ b/scipy/special/_ellip_harm.pxd @@ -69,7 +69,7 @@ cdef inline double* lame_coefficients(double h2, double k2, int n, int p, cdef double s2, alpha, beta, gamma, lamba_romain, pp, psi, t1, tol, vl, vu cdef CBLAS_INT r, tp, j, size, i, info, lwork, liwork, c, iu - cdef Py_UNICODE t + cdef Py_UCS4 t r = n/2 alpha = h2 From 74c9b175e40f6351730470720ea8fb0d59a19c07 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 20 Jun 2024 01:31:01 -0700 Subject: [PATCH 436/500] TST: special: use `standard_normal` to gen args for xp tests (#20971) Co-authored-by: Lucas Colley --- scipy/_lib/_array_api.py | 17 ++--- scipy/_lib/_util.py | 14 ++-- scipy/_lib/tests/test__util.py | 3 +- .../special/_support_alternative_backends.py | 23 +++---- .../test_support_alternative_backends.py | 64 +++++++++---------- scipy/stats/_stats_py.py | 3 + scipy/stats/tests/common_tests.py | 5 +- scipy/stats/tests/test_morestats.py | 2 +- scipy/stats/tests/test_stats.py | 19 +++--- 9 files changed, 79 insertions(+), 71 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 2e483e754d33..540c5e613a4b 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -453,15 +453,14 @@ def get_xp_devices(xp: ModuleType) -> list[str] | list[None]: return [None] -def scipy_namespace_for(xp: ModuleType) -> ModuleType: - """ - Return the `scipy` namespace for alternative backends, where it exists, - such as `cupyx.scipy` and `jax.scipy`. Useful for ad hoc dispatching. +def scipy_namespace_for(xp: ModuleType) -> ModuleType | None: + """Return the `scipy`-like namespace of a non-NumPy backend - Default: return `scipy` (this package). + That is, return the namespace corresponding with backend `xp` that contains + `scipy` sub-namespaces like `linalg` and `special`. If no such namespace + exists, return ``None``. Useful for dispatching. """ - if is_cupy(xp): import cupyx # type: ignore[import-not-found,import-untyped] return cupyx.scipy @@ -470,8 +469,10 @@ def scipy_namespace_for(xp: ModuleType) -> ModuleType: import jax # type: ignore[import-not-found] return jax.scipy - import scipy - return scipy + if is_torch(xp): + return xp + + return None # temporary substitute for xp.minimum, which is not yet in all backends diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py index 9e89841437cb..9043cd83cbfd 100644 --- a/scipy/_lib/_util.py +++ b/scipy/_lib/_util.py @@ -143,11 +143,15 @@ def _lazywhere(cond, arrays, f, fillvalue=None, f2=None): if f2 is None: # If `fillvalue` is a Python scalar and we convert to `xp.asarray`, it gets the # default `int` or `float` type of `xp`, so `result_type` could be wrong. - # `result_type` should/will handle mixed array/Python scalars - # (data-apis/array-api#805) but doesn't yet. So in the meantime, this fails - # for array-api-strict. - dtype = xp.result_type(temp1.dtype, fillvalue) - out = xp.full(cond.shape, fill_value=xp.asarray(fillvalue), dtype=dtype) + # `result_type` should/will handle mixed array/Python scalars; + # remove this special logic when it does. + if type(fillvalue) in {bool, int, float, complex}: + with np.errstate(invalid='ignore'): + dtype = (temp1 * fillvalue).dtype + else: + dtype = xp.result_type(temp1.dtype, fillvalue) + out = xp.full(cond.shape, dtype=dtype, + fill_value=xp.asarray(fillvalue, dtype=dtype)) else: ncond = ~cond temp2 = xp.asarray(f2(*(arr[ncond] for arr in arrays))) diff --git a/scipy/_lib/tests/test__util.py b/scipy/_lib/tests/test__util.py index 663945f276d7..c6f231b89149 100644 --- a/scipy/_lib/tests/test__util.py +++ b/scipy/_lib/tests/test__util.py @@ -429,6 +429,7 @@ def f2(*args): res3 = _lazywhere(cond, arrays, f, float_fillvalue) # Ensure arrays are at least 1d to follow sane type promotion rules. + # This can be removed when minimum supported NumPy is 2.0 if xp == np: cond, fillvalue, *arrays = np.atleast_1d(cond, fillvalue, *arrays) @@ -439,7 +440,7 @@ def f2(*args): # Python scalar. When it does, test can be run with array_api_strict, too. ref3 = xp.where(cond, f(*arrays), float_fillvalue) - if xp == np: + if xp == np: # because we ensured arrays are at least 1d ref1 = ref1.reshape(result_shape) ref2 = ref2.reshape(result_shape) ref3 = ref3.reshape(result_shape) diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index dd5ee1fc755f..e9de025eac70 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -3,9 +3,8 @@ import functools import numpy as np -import scipy from scipy._lib._array_api import ( - array_namespace, scipy_namespace_for, is_numpy, is_torch + array_namespace, scipy_namespace_for, is_numpy ) from . import _ufuncs # These don't really need to be imported, but otherwise IDEs might not realize @@ -25,9 +24,7 @@ def get_array_special_func(f_name, xp, n_array_args): f = None if is_numpy(xp): f = getattr(_ufuncs, f_name, None) - elif is_torch(xp): - f = getattr(xp.special, f_name, None) - elif spx is not scipy: + elif spx is not None: f = getattr(spx.special, f_name, None) if f is not None: @@ -35,7 +32,7 @@ def get_array_special_func(f_name, xp, n_array_args): # if generic array-API implementation is available, use that; # otherwise, fall back to NumPy/SciPy - if f_name in _generic_implementations: + if f_name in _generic_implementations and spx is not None: _f = _generic_implementations[f_name](xp=xp, spx=spx) if _f is not None: return _f @@ -86,7 +83,7 @@ def _chdtr(xp, spx): # 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 = getattr(spx, 'gammainc', None) # noqa: F811 + gammainc = getattr(spx.special, 'gammainc', None) # noqa: F811 if gammainc is None and hasattr(xp, 'special'): gammainc = getattr(xp.special, 'gammainc', None) if gammainc is None: @@ -105,7 +102,7 @@ def _chdtrc(xp, spx): # 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 = getattr(spx, 'gammaincc', None) # noqa: F811 + gammaincc = getattr(spx.special, 'gammaincc', None) # noqa: F811 if gammaincc is None and hasattr(xp, 'special'): gammaincc = getattr(xp.special, 'gammaincc', None) if gammaincc is None: @@ -113,14 +110,14 @@ def _chdtrc(xp, spx): 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) + 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, spx): - betainc = getattr(spx, 'betainc', None) # noqa: F811 + betainc = getattr(spx.special, 'betainc', None) # noqa: F811 if betainc is None and hasattr(xp, 'special'): betainc = getattr(xp.special, 'betainc', None) if betainc is None: @@ -133,7 +130,7 @@ def __betaincc(a, b, x): def _stdtr(xp, spx): - betainc = getattr(spx, 'betainc', None) # noqa: F811 + betainc = getattr(spx.special, 'betainc', None) # noqa: F811 if betainc is None and hasattr(xp, 'special'): betainc = getattr(xp.special, 'betainc', None) if betainc is None: @@ -141,8 +138,8 @@ def _stdtr(xp, spx): def __stdtr(df, t): x = df / (t ** 2 + df) - tail = betainc(df / 2, xp.asarray(0.5), x) / 2 - return xp.where(x < 0, tail, 1 - tail) + tail = betainc(df / 2, 0.5, x) / 2 + return xp.where(t < 0, tail, 1 - tail) return __stdtr diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index 8485826f6668..aea483c5cbf7 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -1,6 +1,4 @@ import pytest -from hypothesis import given, strategies, reproduce_failure # noqa: F401 -import hypothesis.extra.numpy as npst from scipy.special._support_alternative_backends import (get_array_special_func, array_special_func_map) @@ -49,48 +47,46 @@ def test_rel_entr_generic(dtype): @pytest.mark.fail_slow(5) @array_api_compatible -@given(data=strategies.data()) +# @pytest.mark.skip_xp_backends('numpy', reasons=['skip while debugging']) +# @pytest.mark.usefixtures("skip_xp_backends") # `reversed` is for developer convenience: test new function first = less waiting @pytest.mark.parametrize('f_name_n_args', reversed(array_special_func_map.items())) -def test_support_alternative_backends(xp, data, f_name_n_args): +@pytest.mark.parametrize('dtype', ['float32', 'float64']) +@pytest.mark.parametrize('shapes', [[(0,)]*4, [tuple()]*4, [(10,)]*4, + [(10,), (11, 1), (12, 1, 1), (13, 1, 1, 1)]]) +def test_support_alternative_backends(xp, f_name_n_args, dtype, shapes): f_name, n_args = f_name_n_args - - if is_jax(xp): - if f_name in ['gammainc', 'gammaincc']: - pytest.skip("google/jax#20507") - if f_name == 'rel_entr': - pytest.skip("google/jax#21265") - + shapes = shapes[:n_args] f = getattr(special, f_name) - mbs = npst.mutually_broadcastable_shapes(num_shapes=n_args) - shapes, final_shape = data.draw(mbs) - - dtype = data.draw(strategies.sampled_from(['float32', 'float64'])) dtype_np = getattr(np, dtype) dtype_xp = getattr(xp, dtype) - elements = dict(min_value=dtype_np(-10), max_value=dtype_np(10), - allow_subnormal=False) - args_np = [np.asarray(data.draw(npst.arrays(dtype_np, shape, elements=elements))) - for shape in shapes] + # # To test the robustness of the alternative backend's implementation, + # # use Hypothesis to generate arguments + # from hypothesis import given, strategies, reproduce_failure, assume + # import hypothesis.extra.numpy as npst + # @given(data=strategies.data()) + # mbs = npst.mutually_broadcastable_shapes(num_shapes=n_args) + # shapes, final_shape = data.draw(mbs) + # elements = dict(allow_subnormal=False) # consider min_value, max_value + # args_np = [np.asarray(data.draw(npst.arrays(dtype_np, shape, elements=elements)), + # dtype=dtype_np) + # for shape in shapes] + + # For CI, be a little more forgiving; just generate normally distributed arguments + rng = np.random.default_rng(984254252920492019) + args_np = [rng.standard_normal(size=shape, dtype=dtype_np) for shape in shapes] + + if (is_jax(xp) and f_name == 'gammaincc' # google/jax#20699 + or f_name == 'chdtrc'): # gh-20972 + args_np[0] = np.abs(args_np[0]) + args_np[1] = np.abs(args_np[1]) - # `torch.asarray(np.asarray(1.))` produces - # TypeError: can't convert np.ndarray of type numpy.object_. - # So we extract the scalar from 0d arrays. args_xp = [xp.asarray(arg[()], dtype=dtype_xp) for arg in args_np] - ref = np.asarray(f(*args_np)) res = f(*args_xp) + ref = xp.asarray(f(*args_np), dtype=dtype_xp) - eps = np.finfo(dtype).eps - # PyTorch seems to struggle with precision near the poles of `gammaln`, - # so the tolerance needs to be quite loose (eps**0.2) - see gh-19935. - # To compensate, we also check that the root-mean-square error is - # less than eps**0.5. - ref = xp.asarray(ref, dtype=dtype_xp) - xp_assert_close(res, ref, rtol=eps**0.2, atol=eps*20, - check_namespace=True, check_shape=True, check_dtype=True,) - xp_assert_close(xp.sqrt(xp.mean(res**2)), xp.sqrt(xp.mean(ref**2)), - rtol=eps**0.5, atol=eps*20, - check_namespace=False, check_shape=False, check_dtype=False,) + eps = np.finfo(dtype_np).eps + xp_assert_close(res, ref, atol=10*eps) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index b96d0a587dc4..cefd9dcb1245 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -6476,6 +6476,9 @@ def _t_confidence_interval(df, t, confidence_level, alternative, dtype=None, xp= dtype = t.dtype if dtype is None else dtype xp = array_namespace(t) if xp is None else xp + # stdtrit not dispatched yet; use NumPy + df, t = np.asarray(df), np.asarray(t) + if confidence_level < 0 or confidence_level > 1: message = "`confidence_level` must be a number between 0 and 1." raise ValueError(message) diff --git a/scipy/stats/tests/common_tests.py b/scipy/stats/tests/common_tests.py index 4260eb97f2a2..cd943a7c2c21 100644 --- a/scipy/stats/tests/common_tests.py +++ b/scipy/stats/tests/common_tests.py @@ -10,13 +10,16 @@ from scipy._lib._util import ( getfullargspec_no_self as _getfullargspec, np_long ) +from scipy._lib._array_api import xp_assert_equal from scipy import stats -def check_named_results(res, attributes, ma=False): +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)) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index f2f19cd40df1..a05999ab4487 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -755,7 +755,7 @@ def test_result_attributes(self, xp): args = [xp.asarray(arg) for arg in args] res = stats.bartlett(*args) attributes = ('statistic', 'pvalue') - check_named_results(res, attributes) + check_named_results(res, attributes, xp=xp) @pytest.mark.skip_xp_backends( "jax.numpy", cpu_only=True, diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index af05c3d97077..9658e3220ac4 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -78,9 +78,11 @@ class TestTrimmedStats: dprec = np.finfo(np.float64).precision @array_api_compatible - @skip_xp_backends('array_api_strict', + @skip_xp_backends('array_api_strict', 'jax.numpy', reasons=["`array_api_strict.where` `fillvalue` doesn't " - "accept Python floats. See data-apis/array-api#807."]) + "accept Python floats. See data-apis/array-api#807.", + "JAX doesn't allow item assignment, and this " + "function uses _lazywhere."]) @pytest.mark.usefixtures("skip_xp_backends") def test_tmean(self, xp): x = xp.asarray(X) @@ -3774,7 +3776,7 @@ def test_onesample(self, xp): res = stats.ttest_1samp(xp.asarray(self.X1), 0.) attributes = ('statistic', 'pvalue') - check_named_results(res, attributes) + check_named_results(res, attributes, xp=xp) t, p = stats.ttest_1samp(xp.asarray(self.X2), 0.) @@ -4194,7 +4196,7 @@ def test_power_divergence_result_attributes(self, xp): res = stats.power_divergence(f_obs=f_obs, f_exp=f_exp, ddof=ddof, axis=axis, lambda_="pearson") attributes = ('statistic', 'pvalue') - check_named_results(res, attributes) + check_named_results(res, attributes, xp=xp) def test_power_divergence_gh_12282(self, xp): # The sums of observed and expected frequencies must match @@ -6294,9 +6296,10 @@ def test_describe_nan_policy_other(self, xp): with pytest.raises(ValueError, match=message): stats.describe(x, nan_policy='foobar') - @array_api_compatible - def test_describe_result_attributes(self, xp): - actual = stats.describe(xp.arange(5.)) + 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) @@ -6395,7 +6398,7 @@ def test_against_R(self, alternative, xp): 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')) + check_named_results(res, ('statistic', 'pvalue'), xp=xp) def test_nan(self, xp): # nan in input -> nan output (default nan_policy='propagate') From 00850e1e4fc679d9bc32c9da398f9995f9d791ca Mon Sep 17 00:00:00 2001 From: Futuer <57895730+futuer-szd@users.noreply.github.com> Date: Thu, 20 Jun 2024 10:51:06 +0100 Subject: [PATCH 437/500] MAINT: signal: fix code comment typo (#20998) --- scipy/signal/_spectral_py.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/signal/_spectral_py.py b/scipy/signal/_spectral_py.py index ef7fd2e6e8b7..6117ad421164 100644 --- a/scipy/signal/_spectral_py.py +++ b/scipy/signal/_spectral_py.py @@ -1865,7 +1865,7 @@ def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, if padded: # Pad to integer number of windowed segments - # I.e make x.shape[-1] = nperseg + (nseg-1)*nstep, with integer nseg + # 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) From 75f2d3850434911c81bd92a55561c049ccb89224 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Thu, 20 Jun 2024 15:03:57 +0200 Subject: [PATCH 438/500] MAINT: odr: fix a refcounting issue in `__odrpack.c` Looks like a minor issue, but was reported as a potential use-after-free bug found by a static analysis tool. Closes gh-18014 --- scipy/odr/__odrpack.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/scipy/odr/__odrpack.c b/scipy/odr/__odrpack.c index c806e33fbfe4..bbfc1070c64d 100644 --- a/scipy/odr/__odrpack.c +++ b/scipy/odr/__odrpack.c @@ -50,8 +50,6 @@ void fcn_callback(F_INT *n, F_INT *m, F_INT *np, F_INT *nq, F_INT *ldn, F_INT *l PyArrayObject *pyXplusD; void *beta_dst; - arg01 = PyTuple_New(2); - if (*m != 1) { npy_intp dim2[2]; @@ -68,21 +66,18 @@ void fcn_callback(F_INT *n, F_INT *m, F_INT *np, F_INT *nq, F_INT *ldn, F_INT *l memcpy(PyArray_DATA(pyXplusD), (void *)xplusd, (*n) * sizeof(double)); } - PyTuple_SetItem(arg01, 0, odr_global.pyBeta); - Py_INCREF(odr_global.pyBeta); - PyTuple_SetItem(arg01, 1, (PyObject *) pyXplusD); - Py_INCREF((PyObject *) pyXplusD); + arg01 = PyTuple_Pack(2, odr_global.pyBeta, (PyObject *) pyXplusD); + Py_DECREF(pyXplusD); + if (arg01 == NULL) { + return; + } if (odr_global.extra_args != NULL) - { arglist = PySequence_Concat(arg01, odr_global.extra_args); - } else - { arglist = PySequence_Tuple(arg01); /* make a copy */ - } - Py_DECREF(arg01); + *istop = 0; beta_dst = (PyArray_DATA((PyArrayObject *) odr_global.pyBeta)); @@ -255,14 +250,12 @@ void fcn_callback(F_INT *n, F_INT *m, F_INT *np, F_INT *nq, F_INT *ldn, F_INT *l Py_DECREF(result); Py_DECREF(arglist); - Py_DECREF(pyXplusD); return; fail: Py_XDECREF(result); Py_XDECREF(arglist); - Py_XDECREF(pyXplusD); *istop = -1; return; } From 1985c72edc1ee9808f2ff9f1736ccfc53b3a7a65 Mon Sep 17 00:00:00 2001 From: lucascolley Date: Thu, 20 Jun 2024 15:08:26 +0100 Subject: [PATCH 439/500] CI: one invocation for all tests in array API job [skip cirrus] [skip circle] --- .github/workflows/array_api.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 75289af89abc..340cb72f05e8 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -15,6 +15,14 @@ permissions: env: CCACHE_DIR: "${{ github.workspace }}/.ccache" INSTALLDIR: "build-install" + XP_TESTS: >- + -t scipy.cluster + -t scipy.constants + -t scipy.fft + -t scipy.special.tests.test_support_alternative_backends + -t scipy._lib.tests.test_array_api + -t scipy._lib.tests.test__util + -t scipy.stats concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -93,11 +101,5 @@ jobs: - name: Test SciPy run: | export OMP_NUM_THREADS=2 - # expand as new modules are added - python dev.py --no-build test -b all -s cluster -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -s constants -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -s fft -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -t scipy.special.tests.test_support_alternative_backends -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -t scipy._lib.tests.test_array_api -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -t scipy._lib.tests.test__util -- --durations 3 --timeout=60 - python dev.py --no-build test -b all -s stats -- --durations 3 --timeout=60 + # expand as more modules are supported by adding to `XP_TESTS` above + python dev.py --no-build test -b all $XP_TESTS -- --durations 3 --timeout=60 From ecaca3d4841cd1e297d3e803806d8ebae5c2c26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Thu, 20 Jun 2024 15:28:39 -0300 Subject: [PATCH 440/500] DOC: `stats.chisquare`: convert API example to tutorial notebook (#21004) * DOC: `stats.chisquare`: convert API example to tutorial notebook --- .../tutorial/stats/hypothesis_chisquare.md | 67 +++++++++++++++++++ .../tutorial/stats/hypothesis_tests.rst | 11 ++- scipy/stats/_stats_py.py | 52 ++------------ 3 files changed, 83 insertions(+), 47 deletions(-) create mode 100644 doc/source/tutorial/stats/hypothesis_chisquare.md diff --git a/doc/source/tutorial/stats/hypothesis_chisquare.md b/doc/source/tutorial/stats/hypothesis_chisquare.md new file mode 100644 index 000000000000..0bf3ed7949bb --- /dev/null +++ b/doc/source/tutorial/stats/hypothesis_chisquare.md @@ -0,0 +1,67 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +```{eval-rst} +.. jupyterlite:: ../../_contents/hypothesis_chisquare.ipynb + :new_tab: False +``` + +(hypothesis_chisquare)= +# Chi-square test + +The {class}`chi-square test ` tests the null hypothesis +that the categorical data has the given frequencies. + +In [^1], bird foraging behavior was investigated in an old-growth forest of +Oregon. In the forest, 44% of the canopy volume was Douglas fir, 24% was +ponderosa pine, 29% was grand fir, and 3% was western larch. The authors +observed the behavior of several species of birds, one of which was the +red-breasted nuthatch. They made 189 observations of this species foraging, +recording 43 ("23%") of observations in Douglas fir, 52 ("28%") in ponderosa +pine, 54 ("29%") in grand fir, and 40 ("21%") in western larch. + +Using a chi-square test, we can test the null hypothesis that the proportions of +foraging events are equal to the proportions of canopy volume. The authors of +the paper considered a p-value less than 1% to be significant. + +Using the above proportions of canopy volume and observed events, we can infer +expected frequencies. + +```{code-cell} ipython3 +import numpy as np +f_exp = np.array([44, 24, 29, 3]) / 100 * 189 +``` + +The observed frequencies of foraging were: + +```{code-cell} ipython3 +f_obs = np.array([43, 52, 54, 40]) +``` + +We can now compare the observed frequencies with the expected frequencies. + +```{code-cell} ipython3 +from scipy.stats import chisquare +chisquare(f_obs=f_obs, f_exp=f_exp) +``` + +The p-value is well below the chosen significance level. Hence, the authors +considered the difference to be significant and concluded that the relative +proportions of foraging events were not the same as the relative proportions of +tree canopy volume. + +## References + +[^1]: Mannan, R. William and E. Charles. Meslow. "Bird populations and vegetation + characteristics in managed and old-growth forests, northeastern Oregon." + Journal of Wildlife Management 48, 1219-1238, :doi:`10.2307/3801783`, 1984. diff --git a/doc/source/tutorial/stats/hypothesis_tests.rst b/doc/source/tutorial/stats/hypothesis_tests.rst index 7ff2c0bdd51d..c5c35d6bbb6c 100644 --- a/doc/source/tutorial/stats/hypothesis_tests.rst +++ b/doc/source/tutorial/stats/hypothesis_tests.rst @@ -10,5 +10,12 @@ Sample statistics and hypothesis tests Statistical hypothesis tests are used to decide whether data sufficiently support a particular hypothesis. SciPy defines a number of hypothesis tests, -listed in :ref:`hypotests`. You can find examples to each test in the -corresponding docstring. +listed in :ref:`hypotests`. + +You can find simple examples to each test in the corresponding docstring. For +more detailed examples, see the following sections. + +.. toctree:: + :maxdepth: 1 + + hypothesis_chisquare.md diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index cefd9dcb1245..a49fbb5f12a7 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -7706,6 +7706,7 @@ def chisquare(f_obs, f_exp=None, ddof=0, axis=0): 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` Notes ----- @@ -7738,59 +7739,19 @@ def chisquare(f_obs, f_exp=None, ddof=0, axis=0): 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. - .. [4] Mannan, R. William and E. Charles. Meslow. "Bird populations and - vegetation characteristics in managed and old-growth forests, - northeastern Oregon." Journal of Wildlife Management - 48, 1219-1238, :doi:`10.2307/3801783`, 1984. Examples -------- - In [4]_, bird foraging behavior was investigated in an old-growth forest - of Oregon. - In the forest, 44% of the canopy volume was Douglas fir, - 24% was ponderosa pine, 29% was grand fir, and 3% was western larch. - The authors observed the behavior of several species of birds, one of - which was the red-breasted nuthatch. They made 189 observations of this - species foraging, recording 43 ("23%") of observations in Douglas fir, - 52 ("28%") in ponderosa pine, 54 ("29%") in grand fir, and 40 ("21%") in - western larch. - - Using a chi-square test, we can test the null hypothesis that the - proportions of foraging events are equal to the proportions of canopy - volume. The authors of the paper considered a p-value less than 1% to be - significant. - - Using the above proportions of canopy volume and observed events, we can - infer expected frequencies. + 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 - >>> f_exp = np.array([44, 24, 29, 3]) / 100 * 189 - - The observed frequencies of foraging were: - - >>> f_obs = np.array([43, 52, 54, 40]) - - We can now compare the observed frequencies with the expected frequencies. - >>> from scipy.stats import chisquare - >>> chisquare(f_obs=f_obs, f_exp=f_exp) - Power_divergenceResult(statistic=228.23515947653874, pvalue=3.3295585338846486e-49) - - The p-value is well below the chosen significance level. Hence, the - authors considered the difference to be significant and concluded - that the relative proportions of foraging events were not the same - as the relative proportions of tree canopy volume. - - Following are other generic examples to demonstrate how the other - parameters can be used. - - When just `f_obs` is given, it is assumed that the expected frequencies - are uniform and given by the mean of the observed frequencies. - >>> chisquare([16, 18, 16, 14, 12, 12]) Power_divergenceResult(statistic=2.0, pvalue=0.84914503608460956) - With `f_exp` the expected frequencies can be given. + 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) @@ -7819,7 +7780,7 @@ def chisquare(f_obs, f_exp=None, ddof=0, axis=0): 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]) + >>> 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 @@ -7832,6 +7793,7 @@ def chisquare(f_obs, f_exp=None, ddof=0, axis=0): ... 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") From 66ec33370c889e9c9acba320354b624ee258ee22 Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Thu, 20 Jun 2024 19:53:13 -0400 Subject: [PATCH 441/500] ENH: sparse: Add indexing for 1D arrays (#20120) * add csr 1d and test_arithmetic1d * clean up after rebase of _mul to _matmul namechange * change matmul to produce 0d with vec @ vec. Adjust tests. clean up comments * Add tests for 1d@1d. Fix 1d on the right of @. Slow to convert to csc * update _index.py for 1d, refactor _validate_indices, add tests. Move TestGetSet1d to indexing1d.py. Simplify helper functions. * update _index.py: None indexes, validate returns index and new_shape, fix fancy This should set us up for nD indexing when the time comes. Note: ndindex could work for much of this -- but not for sparse array boolean In the future we could maybe implement sparse integer array indexing. We should think about whether 0D return should be sparse array or ndarray. Currently ndarray. This doesn't make reduction functions return sparse arrays yet -- only indexing * convert assert tests to np.testing assert_equal and assert_allclose * lint fixes * lint fixes * fix error msg issue due to lint fix * add DOK support for 1D indexing. * add csr 1d and test_arithmetic1d * clean up after rebase of _mul to _matmul namechange * change matmul to produce 0d with vec @ vec. Adjust tests. clean up comments * Add tests for 1d@1d. Fix 1d on the right of @. Slow to convert to csc * fixup _coo_to_compressed from setdiag fix to work for 1d * fix _coo.py review comments; add copy flag to 1D tocsr * fix other review comments * rewrite fin new_shape logic. make copy of coords if asked. * revert dispatch logic to 1D and 1D explicit treatment * Adjust after merge * linting * fix test * try handling Nones with scalar * reorder getitem/setitem * remove override of get/setitem in csr * handle style changes * update _get_int and friends in compressed * Update test_common1d.py * adopt review suggestions * implement suggestions * address review remove superfluous check of idx add multple comments to clarify logic add clarifying `return None` at end of _compatible_boolean_index --------- Co-authored-by: CJ Carey --- scipy/sparse/_base.py | 11 +- scipy/sparse/_compressed.py | 55 +-- scipy/sparse/_csr.py | 75 ++-- scipy/sparse/_dok.py | 74 ++-- scipy/sparse/_index.py | 414 +++++++++++---------- scipy/sparse/tests/meson.build | 1 + scipy/sparse/tests/test_array_api.py | 35 +- scipy/sparse/tests/test_base.py | 19 +- scipy/sparse/tests/test_common1d.py | 43 +-- scipy/sparse/tests/test_dok.py | 6 +- scipy/sparse/tests/test_indexing1d.py | 503 ++++++++++++++++++++++++++ 11 files changed, 827 insertions(+), 409 deletions(-) create mode 100644 scipy/sparse/tests/test_indexing1d.py diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 3f6531e6f4b1..5f7debb1a456 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -252,8 +252,9 @@ def _asfptype(self): if self.dtype <= np.dtype(fp_type): return self.astype(fp_type) - raise TypeError('cannot upcast [%s] to a floating ' - 'point format' % self.dtype.name) + raise TypeError( + f'cannot upcast [{self.dtype.name}] to a floating point format' + ) def __iter__(self): for r in range(self.shape[0]): @@ -333,8 +334,8 @@ def _getnnz(self, axis=None): -------- count_nonzero : Number of non-zero entries """ - raise NotImplementedError("getnnz not implemented for %s." % - self.__class__.__name__) + clsname = self.__class__.__name__ + raise NotImplementedError(f"getnnz not implemented for {clsname}.") @property def nnz(self) -> int: @@ -886,7 +887,7 @@ def nonzero(self): # convert to COOrdinate format A = self.tocoo() nz_mask = A.data != 0 - return (A.row[nz_mask], A.col[nz_mask]) + 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 diff --git a/scipy/sparse/_compressed.py b/scipy/sparse/_compressed.py index c471250f81bf..f35cf3c0e8ba 100644 --- a/scipy/sparse/_compressed.py +++ b/scipy/sparse/_compressed.py @@ -545,7 +545,7 @@ def _matmul_sparse(self, other): 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 + K2, N = other._shape if other.ndim == 2 else (other.shape[0], 1) # find new_shape: (M, N), (M,), (N,) or () new_shape = () @@ -702,43 +702,6 @@ def _minor_reduce(self, ufunc, data=None): # Getting and Setting # ####################### - def _get_int(self, idx): - if 0 <= idx <= self.shape[0]: - spot = np.flatnonzero(self.indices == idx) - if spot.size: - return self.data[spot[0]] - return self.data.dtype.type(0) - raise IndexError(f'index ({idx}) out of range') - -# For now, 1d only has integer indexing. Soon we will add get_slice/array -# def _get_slice(self, idx): -# if idx == slice(None): -# return self.copy() -# if idx.step in (1, None): -# major, minor = self._swap((0, idx)) -# ret = self._get_submatrix(major, minor, copy=True) -# return ret.reshape(ret.shape[-1]) -# -# _slice = self._swap((self._minor_slice, self._major_slice))[0] -# return _slice(idx) -# -# def _get_array(self, idx): -# idx = np.asarray(idx) -# idx_dtype = self.indices.dtype -# M, N = self._swap((1, self.shape[0])) -# row = np.zeros_like(idx, dtype=idx_dtype) -# major, minor = self._swap((row, idx)) -# major = np.asarray(major, dtype=idx_dtype) -# minor = np.asarray(minor, dtype=idx_dtype) -# if minor.size == 0: -# return self.__class__([], dtype=self.dtype) -# new_shape = minor.shape if minor.shape[0] > 1 else (minor.shape[-1],) -# -# 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) -# return self.__class__(val.reshape(new_shape)) - def _get_intXint(self, row, col): M, N = self._swap(self.shape) major, minor = self._swap((row, col)) @@ -786,8 +749,7 @@ def _major_index_fancy(self, idx): return self.__class__(new_shape, dtype=self.dtype) row_nnz = (self.indptr[indices + 1] - self.indptr[indices]).astype(idx_dtype) - - res_indptr = np.zeros(M+1, dtype=idx_dtype) + res_indptr = np.zeros(M + 1, dtype=idx_dtype) np.cumsum(row_nnz, out=res_indptr[1:]) nnz = res_indptr[-1] @@ -922,17 +884,6 @@ def _get_submatrix(self, major=None, minor=None, copy=False): return self.__class__((data, indices, indptr), shape=shape, dtype=self.dtype, copy=False) - def _set_int(self, idx, x): - major, minor = self._swap((0, idx)) - self._set_many(major, minor, x) - - def _set_array(self, idx, x): - major, minor = self._swap((np.zeros_like(idx), idx)) - broadcast = x.shape[-1] == 1 and minor.shape[-1] != 1 - if broadcast: - x = np.repeat(x.data, idx.shape[-1]) - self._set_many(major, minor, x) - def _set_intXint(self, row, col, x): i, j = self._swap((row, col)) self._set_many(i, j, x) @@ -1460,7 +1411,7 @@ 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. + 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 diff --git a/scipy/sparse/_csr.py b/scipy/sparse/_csr.py index ffa2aa7e7822..16badac62621 100644 --- a/scipy/sparse/_csr.py +++ b/scipy/sparse/_csr.py @@ -9,7 +9,7 @@ from ._matrix import spmatrix from ._base import _spbase, sparray from ._sparsetools import (csr_tocsc, csr_tobsr, csr_count_blocks, - get_csr_submatrix) + get_csr_submatrix, csr_sample_values) from ._sputils import upcast from ._compressed import _cs_matrix @@ -18,40 +18,6 @@ class _csr_base(_cs_matrix): _format = 'csr' - # override IndexMixin.__getitem__ for 1d case until fully implemented - def __getitem__(self, key): - if self.ndim == 2: - return super().__getitem__(key) - - if isinstance(key, tuple) and len(key) == 1: - key = key[0] - INT_TYPES = (int, np.integer) - if isinstance(key, INT_TYPES): - if key < 0: - key += self.shape[-1] - if key < 0 or key >= self.shape[-1]: - raise IndexError('index value out of bounds') - return self._get_int(key) - else: - raise IndexError('array/slice index for 1d csr_array not yet supported') - - # override IndexMixin.__setitem__ for 1d case until fully implemented - def __setitem__(self, key, value): - if self.ndim == 2: - return super().__setitem__(key, value) - - if isinstance(key, tuple) and len(key) == 1: - key = key[0] - INT_TYPES = (int, np.integer) - if isinstance(key, INT_TYPES): - if key < 0: - key += self.shape[-1] - if key < 0 or key >= self.shape[-1]: - raise IndexError('index value out of bounds') - return self._set_int(key, value) - else: - raise IndexError('array index for 1d csr_array not yet provided') - def transpose(self, axes=None, copy=False): if axes is not None and axes != (1, 0): raise ValueError("Sparse arrays/matrices do not support " @@ -133,7 +99,7 @@ def tobsr(self, blocksize=None, copy=True): M,N = self.shape if R < 1 or C < 1 or M % R != 0 or N % C != 0: - raise ValueError('invalid blocksize %s' % blocksize) + raise ValueError(f'invalid blocksize {blocksize}') blks = csr_count_blocks(M,N,R,C,self.indptr,self.indices) @@ -223,6 +189,36 @@ def _getcol(self, i): 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) @@ -276,6 +272,13 @@ def _get_arrayXslice(self, row, col): 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? diff --git a/scipy/sparse/_dok.py b/scipy/sparse/_dok.py index da4554209ce4..ed4ecb5f12d9 100644 --- a/scipy/sparse/_dok.py +++ b/scipy/sparse/_dok.py @@ -53,7 +53,7 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False, *, maxprint=None): self._dict = {i: v for i, v in enumerate(arg1) if v != 0} self.dtype = getdtype(arg1.dtype) else: - d = self._coo_container(arg1, dtype=dtype).todok() + 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_1d=is_array) @@ -144,27 +144,34 @@ def get(self, key, default=0.0): key = key[0] return self._dict.get(key, default) - # override IndexMixin.__getitem__ for 1d case until fully implemented - def __getitem__(self, key): - if self.ndim == 2: - return super().__getitem__(key) - - if isinstance(key, tuple) and len(key) == 1: - key = key[0] - INT_TYPES = (int, np.integer) - if isinstance(key, INT_TYPES): - if key < 0: - key += self.shape[-1] - if key < 0 or key >= self.shape[-1]: - raise IndexError('index value out of bounds') - return self._get_int(key) - else: - raise IndexError('array/slice index for 1d dok_array not yet supported') - # 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)) @@ -236,23 +243,6 @@ def _get_arrayXarray(self, row, col): newdok._dict[key] = v return newdok - # override IndexMixin.__setitem__ for 1d case until fully implemented - def __setitem__(self, key, value): - if self.ndim == 2: - return super().__setitem__(key, value) - - if isinstance(key, tuple) and len(key) == 1: - key = key[0] - INT_TYPES = (int, np.integer) - if isinstance(key, INT_TYPES): - if key < 0: - key += self.shape[-1] - if key < 0 or key >= self.shape[-1]: - raise IndexError('index value out of bounds') - return self._set_int(key, value) - else: - raise IndexError('array index for 1d dok_array not yet provided') - # 1D set methods def _set_int(self, idx, x): if x: @@ -260,6 +250,20 @@ def _set_int(self, 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) diff --git a/scipy/sparse/_index.py b/scipy/sparse/_index.py index c0fc3d01b0eb..8bd18f5b9b73 100644 --- a/scipy/sparse/_index.py +++ b/scipy/sparse/_index.py @@ -1,14 +1,8 @@ """Indexing mixin for sparse array/matrix classes. """ -from __future__ import annotations - -from typing import TYPE_CHECKING - import numpy as np from ._sputils import isintlike - -if TYPE_CHECKING: - import numpy.typing as npt +from ._base import sparray, issparse INT_TYPES = (int, np.integer) @@ -32,74 +26,127 @@ class IndexMixin: """ This class provides common dispatching and validation logic for indexing. """ - def _raise_on_1d_array_slice(self): - """We do not currently support 1D sparse arrays. - - This function is called each time that a 1D array would - result, raising an error instead. - - Once 1D sparse arrays are implemented, it should be removed. - """ - from scipy.sparse import sparray - - if isinstance(self, sparray): - raise NotImplementedError( - 'We have not yet implemented 1D sparse slices; ' - 'please index using explicit indices, e.g. `x[:, [0]]`' - ) - def __getitem__(self, key): - row, col = self._validate_indices(key) + index, new_shape = self._validate_indices(key) + + # 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): - return self._get_intXint(row, col) + res = self._get_intXint(row, col) elif isinstance(col, slice): - self._raise_on_1d_array_slice() - return self._get_intXslice(row, col) + res = self._get_intXslice(row, col) elif col.ndim == 1: - self._raise_on_1d_array_slice() - return self._get_intXarray(row, col) + res = self._get_intXarray(row, col) elif col.ndim == 2: - return self._get_intXarray(row, col) - raise IndexError('index results in >2 dimensions') + res = self._get_intXarray(row, col) + else: + raise IndexError('index results in >2 dimensions') elif isinstance(row, slice): if isinstance(col, INT_TYPES): - self._raise_on_1d_array_slice() - return self._get_sliceXint(row, col) + res = self._get_sliceXint(row, col) elif isinstance(col, slice): if row == slice(None) and row == col: - return self.copy() - return self._get_sliceXslice(row, col) + res = self.copy() + else: + res = self._get_sliceXslice(row, col) elif col.ndim == 1: - return self._get_sliceXarray(row, col) - raise IndexError('index results in >2 dimensions') - elif row.ndim == 1: - if isinstance(col, INT_TYPES): - self._raise_on_1d_array_slice() - return self._get_arrayXint(row, col) - elif isinstance(col, slice): - return self._get_arrayXslice(row, col) - else: # row.ndim == 2 + res = self._get_sliceXarray(row, col) + else: + raise IndexError('index results in >2 dimensions') + else: if isinstance(col, INT_TYPES): - return self._get_arrayXint(row, col) + res = self._get_arrayXint(row, col) elif isinstance(col, slice): - raise IndexError('index results in >2 dimensions') - elif row.shape[1] == 1 and (col.ndim == 1 or col.shape[0] == 1): - # special case for outer indexing - return self._get_columnXarray(row[:,0], col.ravel()) - - # The only remaining case is inner (fancy) 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: - return self.__class__(np.atleast_2d(row).shape, dtype=self.dtype) - return self._get_arrayXarray(row, col) + 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.ravel()) + 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) + + # package the result and return + if isinstance(self, sparray) and res.shape != new_shape: + # handle formats that support indexing but not 1D (lil for now) + if self.format == "lil" and len(new_shape) != 2: + return res.tocoo().reshape(new_shape) + return res.reshape(new_shape) + return res def __setitem__(self, key, x): - row, col = self._validate_indices(key) + index, _ = self._validate_indices(key) + + # 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): x = np.asarray(x, dtype=self.dtype) @@ -124,7 +171,6 @@ def __setitem__(self, key, x): if i.shape != j.shape: raise IndexError('number of row and column indices differ') - from ._base import issparse if issparse(x): if i.ndim == 1: # Inner indexing, so treat them like row vectors. @@ -151,52 +197,84 @@ def __setitem__(self, key, x): self._set_arrayXarray(i, j, x) def _validate_indices(self, key): - # First, check if indexing with single boolean matrix. - from ._base import _spbase - if (isinstance(key, (_spbase, np.ndarray)) and - key.ndim == 2 and key.dtype.kind == 'b'): - if key.shape != self.shape: - raise IndexError('boolean index shape does not match array shape') - row, col = key.nonzero() - else: - row, col = _unpack_index(key) - M, N = self.shape - - def _validate_bool_idx( - idx: npt.NDArray[np.bool_], - axis_size: int, - axis_name: str - ) -> npt.NDArray[np.int_]: - if len(idx) != axis_size: + """Returns two tuples: (index tuple, requested shape tuple)""" + # single ellipsis + if key is Ellipsis: + return (slice(None),) * self.ndim, self.shape + + if not isinstance(key, tuple): + key = [key] + + ellps_pos = None + idx_shape = [] + index = [] + index_ndim = 0 + array_indices = [] + 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: + idx_shape.append(1) + elif isinstance(idx, slice): + index.append(idx) + len_slice = len(range(*idx.indices(self._shape[index_ndim]))) + 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) + index_ndim += 1 + elif (ix := _compatible_boolean_index(idx, self.ndim)) is not None: + tmp_ndim = index_ndim + ix.ndim + mid_shape = self._shape[index_ndim:tmp_ndim] + if ix.shape != mid_shape: + raise IndexError( + f"bool index {i} has shape {mid_shape} instead of {ix.shape}" + ) + index.extend(ix.nonzero()) + array_indices.extend(range(index_ndim, tmp_ndim)) + index_ndim = tmp_ndim + elif issparse(idx): + # TODO: make sparse matrix indexing work for sparray raise IndexError( - f"boolean {axis_name} index has incorrect length: {len(idx)} " - f"instead of {axis_size}" - ) - return _boolean_index_to_array(idx) - - if isintlike(row): - row = int(row) - if row < -M or row >= M: - raise IndexError('row index (%d) out of range' % row) - if row < 0: - row += M - elif (bool_row := _compatible_boolean_index(row)) is not None: - row = _validate_bool_idx(bool_row, M, "row") - elif not isinstance(row, slice): - row = self._asindices(row, M) - - if isintlike(col): - col = int(col) - if col < -N or col >= N: - raise IndexError('column index (%d) out of range' % col) - if col < 0: - col += N - elif (bool_col := _compatible_boolean_index(col)) is not None: - col = _validate_bool_idx(bool_col, N, "column") - elif not isinstance(col, slice): - col = self._asindices(col, N) - - return row, col + 'Indexing with sparse matrices is not supported ' + 'except boolean indexing where matrix and index ' + 'are equal shapes.') + else: # dense array + N = self._shape[index_ndim] + idx = np.array(idx) + idx = self._asindices(idx, N) + index.append(idx) + array_indices.append(index_ndim) + index_ndim += 1 + if index_ndim > self.ndim: + raise IndexError( + f'invalid index ndim. Array is {self.ndim}D. Index needs {index_ndim}D' + ) + if len(array_indices) > 1: + idx_arrays = _broadcast_arrays(*(index[i] for i in array_indices)) + if any(idx_arrays[0].shape != ix.shape for ix in idx_arrays[1:]): + raise IndexError('array indices after broadcast differ in shape') + + # add slice(None) (which is colon) to fill out full index + nslice = self.ndim - index_ndim + if nslice > 0: + if ellps_pos is None: + ellps_pos = index_ndim + index = index[:ellps_pos] + [slice(None)] * nslice + index[ellps_pos:] + mid_shape = list(self.shape[ellps_pos : ellps_pos + nslice]) + idx_shape = idx_shape[:ellps_pos] + mid_shape + idx_shape[ellps_pos:] + + if array_indices: + idx_shape = list(index[array_indices[0]].shape) + idx_shape + if (ndim := len(idx_shape)) > 2: + raise IndexError(f'Only 1D or 2D arrays allowed. Index makes {ndim}D') + return tuple(index), tuple(idx_shape) def _asindices(self, idx, length): """Convert `idx` to a valid index for an axis with a given length. @@ -250,6 +328,15 @@ def _getcol(self, i): 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() @@ -280,6 +367,12 @@ def _get_columnXarray(self, row, col): 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() @@ -293,100 +386,25 @@ def _set_arrayXarray_sparse(self, row, col, x): self._set_arrayXarray(row, col, x) -def _unpack_index(index) -> tuple[ - int | slice | npt.NDArray[np.bool_ | np.int_], - int | slice | npt.NDArray[np.bool_ | np.int_] -]: - """ Parse index. Always return a tuple of the form (row, col). - Valid type for row/col is integer, slice, array of bool, or array of integers. - """ - # Parse any ellipses. - index = _check_ellipsis(index) - - # Next, parse the tuple or object - if isinstance(index, tuple): - if len(index) == 2: - row, col = index - elif len(index) == 1: - row, col = index[0], slice(None) - else: - raise IndexError('invalid number of indices') - else: - idx = _compatible_boolean_index(index) - if idx is None: - row, col = index, slice(None) - elif idx.ndim < 2: - return idx, slice(None) - elif idx.ndim == 2: - return idx.nonzero() - # Next, check for validity and transform the index as needed. - from ._base import issparse - if issparse(row) or issparse(col): - # Supporting sparse boolean indexing with both row and col does - # not work because spmatrix.ndim is always 2. - raise IndexError( - 'Indexing with sparse matrices is not supported ' - 'except boolean indexing where matrix and index ' - 'are equal shapes.') - return row, col - - -def _check_ellipsis(index): - """Process indices with Ellipsis. Returns modified index.""" - if index is Ellipsis: - return (slice(None), slice(None)) - - if not isinstance(index, tuple): - return index - - # Find any Ellipsis objects. - ellipsis_indices = [i for i, v in enumerate(index) if v is Ellipsis] - if not ellipsis_indices: - return index - if len(ellipsis_indices) > 1: - raise IndexError("an index can only have a single ellipsis ('...')") - - # Replace the Ellipsis object with 0, 1, or 2 null-slices as needed. - i, = ellipsis_indices - num_slices = max(0, 3 - len(index)) - return index[:i] + (slice(None),) * num_slices + index[i + 1:] - - -def _maybe_bool_ndarray(idx): - """Returns a compatible array if elements are boolean. - """ - idx = np.asanyarray(idx) +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 - - -def _first_element_bool(idx, max_dim=2): - """Returns True if first element of the incompatible - array type is boolean. - """ - if max_dim < 1: - return None - try: - first = next(iter(idx), None) - except TypeError: - return None - if isinstance(first, bool): - return True - return _first_element_bool(first, max_dim-1) - - -def _compatible_boolean_index(idx): - """Returns a boolean index array that can be converted to - integer array. Returns None if no such array exists. - """ - # Presence of attribute `ndim` indicates a compatible array type. - if hasattr(idx, 'ndim') or _first_element_bool(idx): - return _maybe_bool_ndarray(idx) - return None - - -def _boolean_index_to_array(idx): - if idx.ndim > 1: - raise IndexError('invalid index shape') - return np.where(idx)[0] diff --git a/scipy/sparse/tests/meson.build b/scipy/sparse/tests/meson.build index 06d9353cdc43..e4cab49f5179 100644 --- a/scipy/sparse/tests/meson.build +++ b/scipy/sparse/tests/meson.build @@ -10,6 +10,7 @@ python_sources = [ 'test_csr.py', 'test_dok.py', 'test_extract.py', + 'test_indexing1d.py', 'test_matrix_io.py', 'test_minmax1d.py', 'test_sparsetools.py', diff --git a/scipy/sparse/tests/test_array_api.py b/scipy/sparse/tests/test_array_api.py index a3be7b868b1a..b01997e9ddf5 100644 --- a/scipy/sparse/tests/test_array_api.py +++ b/scipy/sparse/tests/test_array_api.py @@ -87,26 +87,21 @@ def test_indexing(A): if A.__class__.__name__[:3] in ('dia', 'coo', 'bsr'): return - with pytest.raises(NotImplementedError): - A[1, :] - - with pytest.raises(NotImplementedError): - A[:, 1] - - with pytest.raises(NotImplementedError): - A[1, [1, 2]] - - with pytest.raises(NotImplementedError): - A[[1, 2], 1] - - assert isinstance(A[[0]], scipy.sparse.sparray), \ - "Expected sparse array, got sparse matrix" - assert isinstance(A[1, [[1, 2]]], scipy.sparse.sparray), \ - "Expected ndarray, got sparse array" - assert isinstance(A[[[1, 2]], 1], scipy.sparse.sparray), \ - "Expected ndarray, got sparse array" - assert isinstance(A[:, [1, 2]], scipy.sparse.sparray), \ - "Expected sparse array, got something else" + 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 diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index 4cffa3feab35..616fc84cc6ce 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -3043,27 +3043,14 @@ def test_missized_masking(self): col_long = np.ones(N + 2, dtype=bool) col_short = np.ones(N - 2, dtype=bool) - with pytest.raises( - IndexError, - match=rf"boolean row index has incorrect length: {M + 1} instead of {M}" - ): - _ = A[row_long, :] - with pytest.raises( - IndexError, - match=rf"boolean row index has incorrect length: {M - 1} instead of {M}" - ): - _ = A[row_short, :] - + 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=r"boolean \w+ index has incorrect length" - ): + with pytest.raises(IndexError, match=match): _ = A[i, j] def test_fancy_indexing_boolean(self): @@ -4386,7 +4373,7 @@ def test_inplace_ops(self): for op, (other, expected) in data.items(): result = A.copy() - getattr(result, '__i%s__' % op)(other) + getattr(result, f'__i{op}__')(other) assert_array_equal(result.toarray(), expected.toarray()) diff --git a/scipy/sparse/tests/test_common1d.py b/scipy/sparse/tests/test_common1d.py index 1d6ca6041858..b5aa79cf5500 100644 --- a/scipy/sparse/tests/test_common1d.py +++ b/scipy/sparse/tests/test_common1d.py @@ -7,7 +7,7 @@ from scipy.sparse import ( bsr_array, csc_array, dia_array, lil_array, - coo_array, csr_array, dok_array, SparseEfficiencyWarning, + coo_array, csr_array, dok_array, ) from scipy.sparse._sputils import supported_dtypes, matrix from scipy._lib._util import ComplexWarning @@ -407,44 +407,3 @@ def test_resize(self, spcreator): assert_equal(S.toarray(), [1, 0, 3]) S.resize((5,)) assert_equal(S.toarray(), [1, 0, 3, 0, 0]) - - -@pytest.mark.parametrize("spcreator", [csr_array, dok_array]) -class TestGetSet1D: - 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, TypeError), match='index value out of bounds' - ): - A.__getitem__(ij) - - # single element tuples unwrapped - assert A[(0,)] == 4 - - with pytest.raises(IndexError, match='index value out of bounds'): - A.__getitem__((4,)) - - def test_setelement(self, spcreator): - dtype = np.float64 - A = spcreator((12,), dtype=dtype) - with np.testing.suppress_warnings() as sup: - sup.filter(SparseEfficiencyWarning, "Changing the sparsity structure") - A[0] = dtype(0) - A[1] = dtype(3) - A[8] = dtype(9.0) - A[-2] = dtype(7) - A[5] = 9 - - A[-9,] = dtype(8) - A[1,] = dtype(5) # overwrite using 1-tuple index - - for ij in [13, -14, (13,), (14,)]: - with pytest.raises(IndexError, match='index value out of bounds'): - A.__setitem__(ij, 123.0) diff --git a/scipy/sparse/tests/test_dok.py b/scipy/sparse/tests/test_dok.py index 8823ce8a2dbc..564bde5dc2a6 100644 --- a/scipy/sparse/tests/test_dok.py +++ b/scipy/sparse/tests/test_dok.py @@ -203,8 +203,4 @@ def test_dunder_ge(A, Asp): # Note: iter dunder follows np.array not dict def test_dunder_iter(A, Asp): - if isinstance(Asp, dok_array): - with pytest.raises(NotImplementedError): - [a.toarray() for a in Asp] - else: - assert all((a == asp).all() for a, asp in zip(A, Asp)) + assert all((a == asp).all() for a, asp in zip(A, Asp)) diff --git a/scipy/sparse/tests/test_indexing1d.py b/scipy/sparse/tests/test_indexing1d.py new file mode 100644 index 000000000000..caa03289bc8d --- /dev/null +++ b/scipy/sparse/tests/test_indexing1d.py @@ -0,0 +1,503 @@ +import contextlib +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) + + with pytest.raises(IndexError, match='Only 1D or 2D arrays'): + A[None, 2, 1, None, None] + with pytest.raises(IndexError, match='Only 1D or 2D arrays'): + A[None, 0:2, None, 1] + with pytest.raises(IndexError, match='Only 1D or 2D arrays'): + A[0:1, 1:, None] + with pytest.raises(IndexError, match='Only 1D or 2D arrays'): + A[1:, 1, None, None] + + 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,)) + + def test_setelement(self, spcreator): + dtype = np.float64 + A = spcreator((12,), dtype=dtype) + with np.testing.suppress_warnings() as sup: + sup.filter( + SparseEfficiencyWarning, + "Changing the sparsity structure of .* is expensive", + ) + A[0] = dtype(0) + A[1] = dtype(3) + A[8] = dtype(9.0) + A[-2] = dtype(7) + A[5] = 9 + + A[-9,] = dtype(8) + A[1,] = 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 np.testing.suppress_warnings() as sup: + sup.filter( + SparseEfficiencyWarning, + "Changing the sparsity structure of .* is expensive", + ) + 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 np.testing.suppress_warnings() as sup: + sup.filter( + SparseEfficiencyWarning, + "Changing the sparsity structure of .* is expensive", + ) + 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 np.testing.suppress_warnings() as sup: + sup.filter( + SparseEfficiencyWarning, + "Changing the sparsity structure of .* is expensive", + ) + 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 np.testing.suppress_warnings() as sup: + sup.filter( + SparseEfficiencyWarning, + "Changing the sparsity structure of .* is expensive", + ) + 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 np.testing.suppress_warnings() as sup: + sup.filter( + SparseEfficiencyWarning, + "Changing the sparsity structure of .* is expensive", + ) + 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 np.testing.suppress_warnings() as sup: + sup.filter( + SparseEfficiencyWarning, + "Changing the sparsity structure of .* is expensive", + ) + 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, ValueError, TypeError), + match='Index dimension must be 1 or 2|only integers', + ): + A.__getitem__("foo") + with pytest.raises( + (IndexError, ValueError, TypeError), + match='tuple index out of range|only integers', + ): + A.__getitem__((2, "foo")) + + 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 np.testing.suppress_warnings() as sup: + sup.filter( + SparseEfficiencyWarning, + "Changing the sparsity structure of .* is expensive", + ) + 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 np.testing.suppress_warnings() as sup: + sup.filter( + SparseEfficiencyWarning, + "Changing the sparsity structure of .* is expensive", + ) + 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) From 628a6b3bf3e1a3149b8a6390322ae976bba930fa Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Fri, 21 Jun 2024 22:27:48 +1000 Subject: [PATCH 442/500] BLD: make cp313 wheels [wheel build] (#21000) --- .github/workflows/wheels.yml | 65 +++++++++++++++++++++------ ci/cirrus_wheels.yml | 2 +- tools/wheels/cibw_before_build_win.sh | 2 +- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 691c0a3963c8..2d1f4b1cbe5b 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -81,13 +81,12 @@ jobs: # so easier to separate out here. - [ubuntu-22.04, manylinux, x86_64, "", ""] - [ubuntu-22.04, musllinux, x86_64, "", ""] - - [macos-12, macosx, x86_64, openblas, "10.9"] + - [macos-12, macosx, x86_64, openblas, "10.13"] - [macos-13, macosx, x86_64, accelerate, "14.0"] - - [macos-14, macosx, arm64, openblas, "12.0"] + - [macos-14, macosx, arm64, openblas, "12.3"] - [macos-14, macosx, arm64, accelerate, "14.0"] - [windows-2019, win, AMD64, "", ""] - - python: [["cp310", "3.10"], ["cp311", "3.11"], ["cp312", "3.12"]] + python: [["cp310", "3.10"], ["cp311", "3.11"], ["cp312", "3.12"], ["cp313", "3.13"]] # python[0] is used to specify the python versions made by cibuildwheel env: @@ -116,13 +115,13 @@ jobs: if: ${{ runner.os == 'Windows' && env.IS_32_BIT == 'false' }} - name: windows - set PKG_CONFIG_PATH + if: ${{ runner.os == 'Windows' }} run: | $env:CIBW = "${{ github.workspace }}" # It seems somewhere in the env passing, `\` is not # passed through, so convert it to '/' $env:CIBW=$env:CIBW.replace("\","/") - echo "CIBW_ENVIRONMENT_WINDOWS=PKG_CONFIG_PATH=$env:CIBW" >> $env:GITHUB_ENV - if: ${{ runner.os == 'Windows' }} + echo "CIBW_ENVIRONMENT=PKG_CONFIG_PATH=$env:CIBW" >> $env:GITHUB_ENV - name: Setup macOS if: startsWith( matrix.buildplat[0], 'macos-' ) @@ -135,8 +134,8 @@ jobs: echo "PATH=$PATH" >> "$GITHUB_ENV" LIB_PATH=$(dirname $(gfortran --print-file-name libgfortran.dylib)) fi - if [[ ${{ matrix.buildplat[4] }} == '10.9' ]]; then - # Newest version of Xcode that supports macOS 10.9 + if [[ ${{ matrix.buildplat[4] }} == '10.13' ]]; then + # 20240621 macos-12 images span Xcode 13.1-->14.2 XCODE_VER='13.4.1' else XCODE_VER='15.2' @@ -149,7 +148,7 @@ jobs: CIBW="MACOSX_DEPLOYMENT_TARGET=${{ matrix.buildplat[4] }}\ SDKROOT=$(xcrun --sdk macosx --show-sdk-path)\ PKG_CONFIG_PATH=${{ github.workspace }}" - echo "CIBW_ENVIRONMENT_MACOS=$CIBW" >> "$GITHUB_ENV" + echo "CIBW_ENVIRONMENT=$CIBW" >> "$GITHUB_ENV" echo "REPAIR_PATH=$LIB_PATH" >> "$GITHUB_ENV" @@ -170,20 +169,58 @@ jobs: CIBW+=" && mv {dest_dir}/\$(basename {wheel}) \ {dest_dir}/\$(echo \$(basename {wheel}) | sed 's/14_0/13_0/')" fi + + # macos-arm64-openblas wheels that target macos-12 need a + # MACOS_DEPLOYMENT_TARGET of 12.3 otherwise delocate complains. + # Unclear of cause, possibly build tool related. + # This results in wheels that have 12_3 in their name. Since Python + # has no concept of minor OS versions in packaging land rename the + # wheel back to 12. + if [[ ${{ matrix.buildplat[0] }} == 'macos-14' && ${{ matrix.buildplat[4] }} == '12.3' ]]; then + CIBW+=" && echo \$(ls {dest_dir}) && \ + mv {dest_dir}/*.whl \$(find {dest_dir} -type f -name '*.whl' | sed 's/12_3/12_0/')" + fi echo "CIBW_REPAIR_WHEEL_COMMAND_MACOS=$CIBW" >> "$GITHUB_ENV" + - name: Inject environment variable for python dev version + if: matrix.python[1] == '3.13' + shell: bash + run: | + # For dev versions of python need to use wheels from scientific-python-nightly-wheels + # When the python version is released please comment out this section, but do not remove + # (there will soon be another dev version to target). + DEPS0="pip install --pre -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy" + DEPS1="pip install ninja meson-python pybind11 pythran cython" + + CIBW="$DEPS0;$DEPS1;bash {project}/tools/wheels/cibw_before_build_linux.sh {project}" + echo "CIBW_BEFORE_BUILD_LINUX=$CIBW" >> "$GITHUB_ENV" + + CIBW="$DEPS0 && $DEPS1 && bash {project}/tools/wheels/cibw_before_build_win.sh {project}" + echo "CIBW_BEFORE_BUILD_WINDOWS=$CIBW" >> "$GITHUB_ENV" + + CIBW="$DEPS0;$DEPS1;bash {project}/tools/wheels/cibw_before_build_macos.sh {project}" + echo "CIBW_BEFORE_BUILD_MACOS=$CIBW" >> "$GITHUB_ENV" + + echo "CIBW_BEFORE_TEST=$DEPS0" >> "$GITHUB_ENV" + + CIBW="pip; args: --no-build-isolation" + echo "CIBW_BUILD_FRONTEND=$CIBW" >> "$GITHUB_ENV" + - name: Build wheels - uses: pypa/cibuildwheel@v2.17.0 + uses: pypa/cibuildwheel@v2.19.1 env: CIBW_BUILD: ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }}* CIBW_ARCHS: ${{ matrix.buildplat[2] }} CIBW_PRERELEASE_PYTHONS: True - - name: Rename after test (macOS x86 Accelerate only) - # Rename x86 Accelerate wheel back so it targets macOS >= 14 - if: matrix.buildplat[0] == 'macos-13' && matrix.buildplat[4] == '14.0' + - name: Rename macOS wheels + if: startsWith( matrix.buildplat[0], 'macos-' ) run: | - mv ./wheelhouse/*.whl $(find ./wheelhouse -type f -name '*.whl' | sed 's/13_0/14_0/') + # macos-x86_64-accelerate wheels targeting macos-14 were renamed to 13 + # so they could be tested. Shift wheel name back to targeting 14. + if [[ ${{ matrix.buildplat[0] }} == 'macos-13' && ${{ matrix.buildplat[4] }} == '14.0' ]]; then + mv ./wheelhouse/*.whl $(find ./wheelhouse -type f -name '*.whl' | sed 's/13_0/14_0/') + fi - uses: actions/upload-artifact@v4 with: diff --git a/ci/cirrus_wheels.yml b/ci/cirrus_wheels.yml index 1dc44e153433..310cd0d74f68 100644 --- a/ci/cirrus_wheels.yml +++ b/ci/cirrus_wheels.yml @@ -1,6 +1,6 @@ build_and_store_wheels: &BUILD_AND_STORE_WHEELS install_cibuildwheel_script: - - python -m pip install cibuildwheel==2.17.0 + - python -m pip install cibuildwheel==2.19.1 cibuildwheel_script: - cibuildwheel wheels_artifacts: diff --git a/tools/wheels/cibw_before_build_win.sh b/tools/wheels/cibw_before_build_win.sh index 69b4e7984906..f8e8e3082bba 100644 --- a/tools/wheels/cibw_before_build_win.sh +++ b/tools/wheels/cibw_before_build_win.sh @@ -11,4 +11,4 @@ python -m pip install -r requirements/openblas.txt python -c "import scipy_openblas32; print(scipy_openblas32.get_pkg_config())" > $PROJECT_DIR/scipy-openblas.pc # delvewheel is the equivalent of delocate/auditwheel for windows. -python -m pip install delvewheel +python -m pip install delvewheel wheel From d3d4b7b5990a306bb059e7959d83bb1f27fa2010 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 21 Jun 2024 21:14:19 +0300 Subject: [PATCH 443/500] MAINT: smoke-docs: add special/_precompute to --ignore list Also move the ignore list from _testutils.py to conftest.py for viz --- scipy/_lib/_testutils.py | 11 +---------- scipy/conftest.py | 9 +++++++++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scipy/_lib/_testutils.py b/scipy/_lib/_testutils.py index 4a830edcdf0c..8c318c5453a0 100644 --- a/scipy/_lib/_testutils.py +++ b/scipy/_lib/_testutils.py @@ -95,16 +95,7 @@ def __call__(self, label="fast", verbose=1, extra_argv=None, doctests=False, pytest_args = ['--showlocals', '--tb=short'] if doctests: - pytest_args += [ - "--doctest-modules", - "--ignore=scipy/interpolate/_interpnd_info.py", - "--ignore=scipy/_lib/array_api_compat", - "--ignore=scipy/_lib/highs", - "--ignore=scipy/_lib/unuran", - "--ignore=scipy/_lib/_gcutils.py", - "--ignore=scipy/_lib/doccer.py", - "--ignore=scipy/_lib/_uarray", - ] + pytest_args += ["--doctest-modules",] if extra_argv: pytest_args += list(extra_argv) diff --git a/scipy/conftest.py b/scipy/conftest.py index 428a9e118b03..6c4b11b2f2d6 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -389,6 +389,15 @@ def warnings_errors_and_rng(test=None): "scipy.optimize.cython_optimize", "scipy.test", "scipy.show_config", + # equivalent to "pytest --ignore=path/to/file" + "scipy/special/_precompute", + "scipy/interpolate/_interpnd_info.py", + "scipy/_lib/array_api_compat", + "scipy/_lib/highs", + "scipy/_lib/unuran", + "scipy/_lib/_gcutils.py", + "scipy/_lib/doccer.py", + "scipy/_lib/_uarray", ] dt_config.pytest_extra_xfail = { From ec4feed289ad160ab1fa735f464ca983658ce9b0 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 21 Jun 2024 14:22:00 -0400 Subject: [PATCH 444/500] ENH: Better error message on sparse matmul mismatch --- scipy/sparse/_base.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 5f7debb1a456..41bd755efcbb 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -620,9 +620,13 @@ def _matmul_dispatch(self, other): # scalar value return self._mul_scalar(other) + err_prefix = "matmul: dimension mismatch with signature" if issparse(other): - if self.shape[-1] != other.shape[0]: - raise ValueError('dimension mismatch') + if N != other.shape[0]: + raise ValueError( + f"{err_prefix} (n,k),(k,m)->(n,m) " + f"(size {N} is different from {other.shape[0]})" + ) return self._matmul_sparse(other) # If it's a list or whatever, treat it like an array @@ -640,8 +644,11 @@ def _matmul_dispatch(self, other): if other.ndim == 1 or other.ndim == 2 and other.shape[1] == 1: # dense row or column vector - if other.shape != (N,) and other.shape != (N, 1): - raise ValueError('dimension mismatch') + if other.shape[0] != N: # other.shape == (N,) or (N, 1) + raise ValueError( + f"{err_prefix} (n,k),(k,1?)->(n,1?) " + f"(size {N} is different from {other.shape[0]})" + ) result = self._matmul_vector(np.ravel(other)) @@ -659,7 +666,10 @@ def _matmul_dispatch(self, other): # dense 2D array or matrix ("multivector") if other.shape[0] != N: - raise ValueError('dimension mismatch') + raise ValueError( + f"{err_prefix} (n,k),(k,m)->(n,m) " + f"(size {N} is different from {other.shape[0]})" + ) result = self._matmul_multivector(np.asarray(other)) From 6fba513e354584b05887a9c21701ef4c81da6b59 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 21 Jun 2024 14:40:31 -0400 Subject: [PATCH 445/500] DOC: Better comment --- scipy/sparse/_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 41bd755efcbb..47626a618e4e 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -644,7 +644,7 @@ def _matmul_dispatch(self, other): if other.ndim == 1 or other.ndim == 2 and other.shape[1] == 1: # dense row or column vector - if other.shape[0] != N: # other.shape == (N,) or (N, 1) + if other.shape[0] != N: raise ValueError( f"{err_prefix} (n,k),(k,1?)->(n,1?) " f"(size {N} is different from {other.shape[0]})" From a399affe067fe1506bc5ba3a4c65e9e5da41f689 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 21 Jun 2024 14:35:10 -0700 Subject: [PATCH 446/500] ENH: stats.tmin: add array API support --- scipy/stats/_stats_py.py | 17 ++++++++---- scipy/stats/tests/test_stats.py | 46 +++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index a49fbb5f12a7..11efa1121cb3 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -711,12 +711,19 @@ def tmin(a, lowerlimit=None, axis=0, inclusive=True, nan_policy='propagate'): """ dtype = a.dtype - a, _ = _put_val_to_limits(a, (lowerlimit, None), (inclusive, None)) - res = np.nanmin(a, axis=axis) - if not np.any(np.isnan(res)): + + xp = array_namespace(a) + a, mask = _put_val_to_limits(a, (lowerlimit, None), (inclusive, None), + val=xp.inf, xp=xp) + min = xp.min(a, axis=axis) + n = xp.sum(xp.asarray(~mask, dtype=a.dtype), axis=axis) + min = xp.where(n != 0, min, xp.nan) + + if not xp.any(xp.isnan(min)): # needed if input is of integer dtype - return res.astype(dtype, copy=False) - return res + min = xp.astype(min, dtype, copy=False) + + return min[()] if min.ndim == 0 else min @_axis_nan_policy_factory( diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 9658e3220ac4..935376dd2ea3 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -169,24 +169,40 @@ def test_tstd(self): y = stats.tstd(X, limits=None) assert_approx_equal(y, X.std(ddof=1), significant=self.dprec) - def test_tmin(self): - assert_equal(stats.tmin(4), 4) + @array_api_compatible + @skip_xp_backends('array_api_strict', 'jax.numpy', + reasons=["`array_api_strict.where` `fillvalue` doesn't " + "accept Python floats. See data-apis/array-api#807.", + "JAX doesn't allow item assignment."]) + @pytest.mark.usefixtures("skip_xp_backends") + 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 = np.arange(10) - assert_equal(stats.tmin(x), 0) - assert_equal(stats.tmin(x, lowerlimit=0), 0) - assert_equal(stats.tmin(x, lowerlimit=0, inclusive=False), 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 = x.reshape((5, 2)) - assert_equal(stats.tmin(x, lowerlimit=0, inclusive=False), [2, 1]) - assert_equal(stats.tmin(x, axis=1), [0, 2, 4, 6, 8]) - assert_equal(stats.tmin(x, axis=None), 0) + x = xp.arange(10.) + x[9] = 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.arange(16).reshape(4, 4) + res = stats.tmin(x, lowerlimit=4, axis=1) + xp_assert_equal(res, xp.asarray([np.nan, 4, 8, 12])) + + def test_tmin_scalar_and_nanpolicy(self): + assert_equal(stats.tmin(4), 4) x = np.arange(10.) x[9] = np.nan with suppress_warnings() as sup: sup.record(RuntimeWarning, "invalid value*") - assert_equal(stats.tmin(x), np.nan) assert_equal(stats.tmin(x, nan_policy='omit'), 0.) assert_raises(ValueError, stats.tmin, x, nan_policy='raise') assert_raises(ValueError, stats.tmin, x, nan_policy='foobar') @@ -194,14 +210,6 @@ def test_tmin(self): with assert_raises(ValueError, match=msg): stats.tmin(x, nan_policy='foo') - # check that that if a full slice is masked, the output returns a - # nan instead of a garbage value. - with suppress_warnings() as sup: - sup.filter(RuntimeWarning, "All-NaN slice encountered") - x = np.arange(16).reshape(4, 4) - res = stats.tmin(x, lowerlimit=4, axis=1) - assert_equal(res, [np.nan, 4, 8, 12]) - def test_tmax(self): assert_equal(stats.tmax(4), 4) From d7e5a907ff9ac05263e54eb009b0ace106310665 Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Sat, 22 Jun 2024 23:25:49 +0100 Subject: [PATCH 447/500] DEV: lint: enforce newlines at end of files (#21023) --- tools/lint.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/lint.toml b/tools/lint.toml index bcc9f31b9135..a2cb5a611681 100644 --- a/tools/lint.toml +++ b/tools/lint.toml @@ -15,8 +15,9 @@ target-version = "py39" # Also, `PGH004` which checks for blanket (non-specific) `noqa`s # and `B028` which checks that warnings include the `stacklevel` keyword. # `B028` added in gh-19623. -# `ICN001` added in gh-20382 to enforce common conventions for imports -select = ["E", "F", "PGH004", "UP", "B028", "ICN001"] +# `ICN001` added in gh-20382 to enforce common conventions for import. +# `W292` added in gh-21023 to enforce newlines at end of files +select = ["E", "F", "PGH004", "UP", "B028", "ICN001", "W292"] # UP031 should be enabled once someone fixes the errors. ignore = ["E741", "UP031", "UP032"] From 665830073f3581e510f2dbd27b352ad8748c05f2 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 22 Jun 2024 12:38:03 +0300 Subject: [PATCH 448/500] MAINT: improve error message if scipy-doctest is not available --- dev.py | 4 ++++ scipy/conftest.py | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dev.py b/dev.py index 3cd5b7afb7e5..d3e3d28d1324 100644 --- a/dev.py +++ b/dev.py @@ -811,6 +811,10 @@ def scipy_tests(cls, args, pytest_args): dirs.add_sys_path() print(f"SciPy from development installed path at: {dirs.site}") + # prevent obscure error later; cf https://github.com/numpy/numpy/pull/26691/ + if not importlib.util.find_spec("scipy_doctest"): + raise ModuleNotFoundError("Please install scipy-doctest") + # FIXME: support pos-args with doit extra_argv = list(pytest_args[:]) if pytest_args else [] if extra_argv and extra_argv[0] == '--': diff --git a/scipy/conftest.py b/scipy/conftest.py index 6c4b11b2f2d6..4e7576660b5b 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -347,7 +347,6 @@ def warnings_errors_and_rng(test=None): warnings.simplefilter('error', Warning) yield - dt_config.user_context_mgr = warnings_errors_and_rng dt_config.skiplist = set([ 'scipy.linalg.LinAlgError', # comes from numpy From 223e201055f11e340d38e74691a5b5ef64985a8d Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 22 Jun 2024 13:38:39 +0300 Subject: [PATCH 449/500] MAINT: move smoke-docs pytest arguments to dev.py from _lib/testutils --- dev.py | 3 ++- scipy/_lib/_testutils.py | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dev.py b/dev.py index d3e3d28d1324..d70220efc981 100644 --- a/dev.py +++ b/dev.py @@ -831,7 +831,8 @@ def scipy_tests(cls, args, pytest_args): else: tests = None - # use strategy=api unless -t path/to/specific/file + # Request doctesting; use strategy=api unless -t path/to/specific/file + extra_argv += ["--doctest-modules",] if not args.tests: extra_argv += ['--doctest-collect=api'] diff --git a/scipy/_lib/_testutils.py b/scipy/_lib/_testutils.py index 8c318c5453a0..798f2fe223c3 100644 --- a/scipy/_lib/_testutils.py +++ b/scipy/_lib/_testutils.py @@ -94,9 +94,6 @@ def __call__(self, label="fast", verbose=1, extra_argv=None, doctests=False, pytest_args = ['--showlocals', '--tb=short'] - if doctests: - pytest_args += ["--doctest-modules",] - if extra_argv: pytest_args += list(extra_argv) From 160540981702671ab14dd5b07b59d15150d4f985 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 22 Jun 2024 17:45:00 +0300 Subject: [PATCH 450/500] BUG: smoke-docs: ask pytest to not rewrite assertions for doctests There are no assertions to rewrite in scipy-doctest, and the output on error is fixed anyway. This patch follows the suggestion at the very bottom of https://docs.pytest.org/en/7.1.x/how-to/assert.html Unless explicitly asked to exclude scipy-doctest from assertion rewriting, pytest issues an obscure warning on reimport. The warning is converted into a hard failure in dev.py. --- dev.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev.py b/dev.py index d70220efc981..b3768aab89f2 100644 --- a/dev.py +++ b/dev.py @@ -832,7 +832,8 @@ def scipy_tests(cls, args, pytest_args): tests = None # Request doctesting; use strategy=api unless -t path/to/specific/file - extra_argv += ["--doctest-modules",] + # also switch off assertion rewriting: not useful for doctests + extra_argv += ["--doctest-modules", "--assert=plain"] if not args.tests: extra_argv += ['--doctest-collect=api'] From 765906ec02ff22023e0127e493ce6262c527745c Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 23 Jun 2024 13:09:56 -0700 Subject: [PATCH 451/500] ENH: stats.tmax: add array API support --- scipy/stats/_stats_py.py | 33 ++++++++++------ scipy/stats/tests/test_stats.py | 67 +++++++++++++++++++++------------ 2 files changed, 64 insertions(+), 36 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 11efa1121cb3..50675608d17a 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -710,20 +710,22 @@ def tmin(a, lowerlimit=None, axis=0, inclusive=True, nan_policy='propagate'): 14 """ - dtype = a.dtype - xp = array_namespace(a) + + # remember original dtype; _put_val_to_limits might need to change it + dtype = a.dtype a, mask = _put_val_to_limits(a, (lowerlimit, None), (inclusive, None), val=xp.inf, xp=xp) + min = xp.min(a, axis=axis) n = xp.sum(xp.asarray(~mask, dtype=a.dtype), axis=axis) - min = xp.where(n != 0, min, xp.nan) + res = xp.where(n != 0, min, xp.nan) - if not xp.any(xp.isnan(min)): + if not xp.any(xp.isnan(res)): # needed if input is of integer dtype - min = xp.astype(min, dtype, copy=False) + res = xp.astype(res, dtype, copy=False) - return min[()] if min.ndim == 0 else min + return res[()] if res.ndim == 0 else res @_axis_nan_policy_factory( @@ -770,13 +772,22 @@ def tmax(a, upperlimit=None, axis=0, inclusive=True, nan_policy='propagate'): 12 """ + xp = array_namespace(a) + + # remember original dtype; _put_val_to_limits might need to change it dtype = a.dtype - a, _ = _put_val_to_limits(a, (None, upperlimit), (None, inclusive)) - res = np.nanmax(a, axis=axis) - if not np.any(np.isnan(res)): + a, mask = _put_val_to_limits(a, (None, upperlimit), (None, inclusive), + val=-xp.inf, xp=xp) + + max = xp.max(a, axis=axis) + n = xp.sum(xp.asarray(~mask, dtype=a.dtype), axis=axis) + res = xp.where(n != 0, max, xp.nan) + + if not xp.any(xp.isnan(res)): # needed if input is of integer dtype - return res.astype(dtype, copy=False) - return res + res = xp.astype(res, dtype, copy=False) + + return res[()] if res.ndim == 0 else res @_axis_nan_policy_factory( diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 935376dd2ea3..2ea2042aa8d6 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -182,7 +182,8 @@ def test_tmin(self, xp): 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, 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)) @@ -204,41 +205,57 @@ def test_tmin_scalar_and_nanpolicy(self): with suppress_warnings() as sup: sup.record(RuntimeWarning, "invalid value*") assert_equal(stats.tmin(x, nan_policy='omit'), 0.) - assert_raises(ValueError, stats.tmin, x, nan_policy='raise') - assert_raises(ValueError, stats.tmin, x, nan_policy='foobar') + msg = "The input contains nan values" + with assert_raises(ValueError, match=msg): + stats.tmin(x, nan_policy='raise') msg = "nan_policy must be one of..." with assert_raises(ValueError, match=msg): - stats.tmin(x, nan_policy='foo') + stats.tmin(x, nan_policy='foobar') - def test_tmax(self): - assert_equal(stats.tmax(4), 4) + @array_api_compatible + @skip_xp_backends('array_api_strict', 'jax.numpy', + reasons=["`array_api_strict.where` `fillvalue` doesn't " + "accept Python floats. See data-apis/array-api#807.", + "JAX doesn't allow item assignment."]) + @pytest.mark.usefixtures("skip_xp_backends") + 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 = xp.arange(10.) + x[9] = xp.nan + xp_assert_equal(stats.tmax(x), xp.asarray(xp.nan)) - x = np.arange(10) - assert_equal(stats.tmax(x), 9) - assert_equal(stats.tmax(x, upperlimit=9), 9) - assert_equal(stats.tmax(x, upperlimit=9, inclusive=False), 8) + # check that if a full slice is masked, the output returns a + # nan instead of a garbage value. + with suppress_warnings() as sup: + sup.filter(RuntimeWarning, "All-NaN slice encountered") + 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])) - x = x.reshape((5, 2)) - assert_equal(stats.tmax(x, upperlimit=9, inclusive=False), [8, 7]) - assert_equal(stats.tmax(x, axis=1), [1, 3, 5, 7, 9]) - assert_equal(stats.tmax(x, axis=None), 9) + def test_tax_scalar_and_nanpolicy(self): + assert_equal(stats.tmax(4), 4) x = np.arange(10.) x[6] = np.nan with suppress_warnings() as sup: sup.record(RuntimeWarning, "invalid value*") - assert_equal(stats.tmax(x), np.nan) assert_equal(stats.tmax(x, nan_policy='omit'), 9.) - assert_raises(ValueError, stats.tmax, x, nan_policy='raise') - assert_raises(ValueError, stats.tmax, x, nan_policy='foobar') - - # check that that if a full slice is masked, the output returns a - # nan instead of a garbage value. - with suppress_warnings() as sup: - sup.filter(RuntimeWarning, "All-NaN slice encountered") - x = np.arange(16).reshape(4, 4) - res = stats.tmax(x, upperlimit=11, axis=1) - assert_equal(res, [3, 7, 11, np.nan]) + msg = "The input contains nan values" + with assert_raises(ValueError, match=msg): + stats.tmax(x, nan_policy='raise') + msg = "nan_policy must be one of..." + with assert_raises(ValueError, match=msg): + stats.tmax(x, nan_policy='foobar') def test_tsem(self): y = stats.tsem(X, limits=(3, 8), inclusive=(False, True)) From 4f08704dc1a713a0ce5f0ff9a07f992adc0ef17e Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Sun, 23 Jun 2024 23:58:43 +0100 Subject: [PATCH 452/500] DEP: special.perm: deprecate non-integer `N` and `k` with `exact=True` (#20909) Co-authored-by: Lucas Colley Co-authored-by: h-vetinari Co-authored-by: Matt Haberland --- scipy/special/_basic.py | 21 ++++++++++++++++++--- scipy/special/tests/test_basic.py | 21 ++++++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/scipy/special/_basic.py b/scipy/special/_basic.py index c003bb423cc1..5f79f85395ac 100644 --- a/scipy/special/_basic.py +++ b/scipy/special/_basic.py @@ -2765,8 +2765,9 @@ def perm(N, k, exact=False): k : int, ndarray Number of elements taken. exact : bool, optional - If `exact` is False, then floating point precision is used, otherwise - exact long integer is computed. + 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 ------- @@ -2791,10 +2792,24 @@ def perm(N, k, exact=False): """ 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 scalar integers be with `exact=True`.") + + floor_N, floor_k = int(N), int(k) + non_integral = not (floor_N == N and floor_k == k) if (k > N) or (N < 0) or (k < 0): + if non_integral: + msg = ("Non-integer `N` and `k` with `exact=True` is deprecated and " + "will raise an error in SciPy 1.16.0.") + warnings.warn(msg, DeprecationWarning, stacklevel=2) return 0 + if non_integral: + raise ValueError("Non-integer `N` and `k` with `exact=True` is not " + "supported.") val = 1 - for i in range(N - k + 1, N + 1): + for i in range(floor_N - floor_k + 1, floor_N + 1): val *= i return val else: diff --git a/scipy/special/tests/test_basic.py b/scipy/special/tests/test_basic.py index 179d9842d976..e3027536d2f2 100644 --- a/scipy/special/tests/test_basic.py +++ b/scipy/special/tests/test_basic.py @@ -1459,7 +1459,7 @@ def test_comb_zeros(self): 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_dep(self): msg = "`exact=True`" with pytest.deprecated_call(match=msg): @@ -1477,6 +1477,25 @@ def test_perm_zeros(self): 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) + + # Non-integral scalars with N < k, or N,k < 0 used to return 0, this is now + # deprecated and will raise an error in SciPy 1.16.0 + with pytest.deprecated_call(match="Non-integer"): + special.perm(4.6, 6, exact=True) + with pytest.deprecated_call(match="Non-integer"): + special.perm(-4.6, 3, exact=True) + with pytest.deprecated_call(match="Non-integer"): + special.perm(4, -3.9, exact=True) + + # Non-integral scalars which aren't included in the cases above an raise an + # error directly without deprecation as this code never worked + with pytest.raises(ValueError, match="Non-integer"): + special.perm(6.0, 4.6, exact=True) + class TestTrigonometric: def test_cbrt(self): From 1acfce951a1cba8143784543dbd2ec49c9d5b840 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 23 Jun 2024 17:02:42 -0700 Subject: [PATCH 453/500] DOC/MAINT: stats.xmean: treat invalid input consistently (#20962) --- scipy/stats/_stats_py.py | 47 +++++++++++++++++++++++++++------ scipy/stats/tests/test_stats.py | 38 +++++++++++++++----------- 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index a49fbb5f12a7..0d6d19ca3d61 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -184,6 +184,14 @@ def gmean(a, axis=0, dtype=None, weights=None): 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*, @@ -266,10 +274,16 @@ def hmean(a, axis=0, dtype=None, *, weights=None): 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*, @@ -293,9 +307,13 @@ def hmean(a, axis=0, dtype=None, *, weights=None): if weights is not None: weights = np.asarray(weights, dtype=dtype) - if not np.all(a >= 0): - message = ("The harmonic mean is only defined if all elements are greater " - "than or equal to zero; otherwise, the result is NaN.") + negative_mask = a < 0 + if np.any(negative_mask): + # `where` avoids having to be careful about dtypes and will work with + # JAX. This is the exceptional case, so it's OK to be a little slower. + a = np.where(negative_mask, np.nan, a) + 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'): @@ -365,6 +383,9 @@ def pmean(a, p, *, axis=0, dtype=None, weights=None): 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 @@ -412,9 +433,13 @@ def pmean(a, p, *, axis=0, dtype=None, weights=None): if weights is not None: weights = np.asanyarray(weights, dtype=dtype) - if not np.all(a >= 0): - message = ("The power mean is only defined if all elements are greater " - "than or equal to zero; otherwise, the result is NaN.") + negative_mask = a < 0 + if np.any(negative_mask): + # `where` avoids having to be careful about dtypes and will work with + # JAX. This is the exceptional case, so it's OK to be a little slower. + a = np.where(negative_mask, np.nan, a) + message = ("The power 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', invalid='ignore'): @@ -3223,7 +3248,7 @@ def gstd(a, axis=0, ddof=1): 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 only for positive reals. + 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)))``. @@ -3271,8 +3296,14 @@ def gstd(a, axis=0, ddof=1): log = np.log with np.errstate(invalid='ignore', divide='ignore'): - return np.exp(np.std(log(a), axis=axis, ddof=ddof)) + res = np.exp(np.std(log(a), axis=axis, ddof=ddof)) + + if (a <= 0).any(): + 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 diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 9658e3220ac4..89a52f87ac22 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6785,7 +6785,7 @@ def check_equal_pmean(array_like, exp, desired, axis=None, dtype=None, assert_equal(x.dtype, dtype) -class TestHarMean: +class TestHMean: def test_0(self): a = [1, 0, 2] desired = 0 @@ -6816,7 +6816,8 @@ def test_1d_array_with_negative_value(self): a = np.array([1, 0, -1]) message = "The harmonic mean is only defined..." with pytest.warns(RuntimeWarning, match=message): - stats.hmean(a) + res = stats.hmean(a) + np.testing.assert_equal(res, np.nan) # Note the next tests use axis=None as default, not axis=0 def test_2d_list(self): @@ -6887,7 +6888,7 @@ def test_weights_masked_1d_array(self): @array_api_compatible -class TestGeoMean: +class TestGMean: def test_0(self, xp): a = [1, 0, 2] desired = 0 @@ -7011,7 +7012,7 @@ def test_weights_masked_1d_array(self, xp): dtype=np.float64, xp=xp) -class TestPowMean: +class TestPMean: def pmean_reference(a, p): return (np.sum(a**p) / a.size)**(1/p) @@ -7027,7 +7028,7 @@ def test_bad_exponent(self): def test_1d_list(self): a, p = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], 3.5 - desired = TestPowMean.pmean_reference(np.array(a), p) + desired = TestPMean.pmean_reference(np.array(a), p) check_equal_pmean(a, p, desired) a, p = [1, 2, 3, 4], 2 @@ -7036,7 +7037,7 @@ def test_1d_list(self): def test_1d_array(self): a, p = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]), -2.5 - desired = TestPowMean.pmean_reference(a, p) + desired = TestPMean.pmean_reference(a, p) check_equal_pmean(a, p, desired) def test_1d_array_with_zero(self): @@ -7048,7 +7049,8 @@ def test_1d_array_with_negative_value(self): a, p = np.array([1, 0, -1]), 1.23 message = "The power mean is only defined..." with pytest.warns(RuntimeWarning, match=message): - stats.pmean(a, p) + res = stats.pmean(a, p) + np.testing.assert_equal(res, np.nan) @pytest.mark.parametrize( ("a", "p"), @@ -7056,7 +7058,7 @@ def test_1d_array_with_negative_value(self): (np.array([[10, 20], [50, 60], [90, 100]]), 0.5)] ) def test_2d_axisnone(self, a, p): - desired = TestPowMean.pmean_reference(np.array(a), p) + desired = TestPMean.pmean_reference(np.array(a), p) check_equal_pmean(a, p, desired) @pytest.mark.parametrize( @@ -7066,7 +7068,7 @@ def test_2d_axisnone(self, a, p): ) def test_2d_list_axis0(self, a, p): desired = [ - TestPowMean.pmean_reference( + TestPMean.pmean_reference( np.array([a[i][j] for i in range(len(a))]), p ) for j in range(len(a[0])) @@ -7079,13 +7081,13 @@ def test_2d_list_axis0(self, a, p): ([[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]], 0.5)] ) def test_2d_list_axis1(self, a, p): - desired = [TestPowMean.pmean_reference(np.array(a_), p) for a_ in a] + desired = [TestPMean.pmean_reference(np.array(a_), p) for a_ in a] check_equal_pmean(a, p, desired, axis=1) def test_weights_1d_list(self): a, p = [2, 10, 6], -1.23456789 weights = [10, 5, 3] - desired = TestPowMean.wpmean_reference(np.array(a), p, weights) + desired = TestPMean.wpmean_reference(np.array(a), p, weights) check_equal_pmean(a, p, desired, weights=weights, rtol=1e-5) def test_weights_masked_1d_array(self): @@ -7103,7 +7105,7 @@ def test_weights_masked_1d_array(self): def test_weights_2d_array(self, axis, fun_name, p): if fun_name == 'wpmean_reference': def fun(a, axis, weights): - return TestPowMean.wpmean_reference(a, p, weights) + return TestPMean.wpmean_reference(a, p, weights) else: fun = getattr(stats, fun_name) a = np.array([[2, 5], [10, 5], [6, 5]]) @@ -7112,7 +7114,7 @@ def fun(a, axis, weights): check_equal_pmean(a, p, desired, axis=axis, weights=weights, rtol=1e-5) -class TestGeometricStandardDeviation: +class TestGSTD: # must add 1 as `gstd` is only defined for positive values array_1d = np.arange(2 * 3 * 4) + 1 gstd_array_1d = 2.294407613602 @@ -7131,10 +7133,16 @@ def test_raises_value_error_non_numeric_input(self): with pytest.raises(TypeError, match="ufunc 'log' not supported"): stats.gstd('You cannot take the logarithm of a string.') - @pytest.mark.parametrize('bad_value', (0, -1, np.inf)) + @pytest.mark.parametrize('bad_value', (0, -1, np.inf, np.nan)) def test_returns_nan_invalid_value(self, bad_value): x = np.append(self.array_1d, [bad_value]) - assert_equal(stats.gstd(x), np.nan) + if np.isfinite(bad_value): + message = "The geometric standard deviation is only defined..." + with pytest.warns(RuntimeWarning, match=message): + res = stats.gstd(x) + else: + res = stats.gstd(x) + assert_equal(res, np.nan) def test_propagates_nan_values(self): a = array([[1, 1, 1, 16], [np.nan, 1, 2, 3]]) From b2fdae37219941f144978f9d5439c71c30a9fa29 Mon Sep 17 00:00:00 2001 From: lucascolley Date: Tue, 18 Jun 2024 21:01:50 +0100 Subject: [PATCH 454/500] DEV: `gh_lists`: single -> double backtics [skip ci] --- .gitignore | 4 ++++ tools/gh_lists.py | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 5f9c22fbfca9..989f5ac282e4 100644 --- a/.gitignore +++ b/.gitignore @@ -107,6 +107,10 @@ doc/source/.jupyterlite.doit.db .cache/ .pytest_cache/ +# github cache # +################ +gh_cache.json + # mypy cache # ############## .mypy_cache/ diff --git a/tools/gh_lists.py b/tools/gh_lists.py index 8f3ac68a8f5e..8c428deab1a7 100644 --- a/tools/gh_lists.py +++ b/tools/gh_lists.py @@ -46,17 +46,31 @@ def print_list(title, items): print("-"*len(title)) print() + def backtick_repl(matchobj): + if matchobj.group(2) != ' ': + post = '\ ' + matchobj.group(2) + else: + post = matchobj.group(2) + return '``' + matchobj.group(1) + '``' + post + for issue in items: msg = "* `#{0} <{1}>`__: {2}" # sanitize whitespace, `, and * title = re.sub("\\s+", " ", issue.title.strip()) - title = title.replace('`', '\\`').replace('*', '\\*') + title = re.sub("([^`]|^)`([^`]|$)", "\g<1>``\g<2>", title) + title = re.sub("``(.*?)``(.)", backtick_repl, title) + title = title.replace('*', '\\*') if len(title) > 60: remainder = re.sub("\\s.*$", "...", title[60:]) if len(remainder) > 20: - remainder = title[:80] + "..." + # this was previously bugged, + # assigning to `remainder` rather than `title` + title = title[:80] + "..." else: title = title[:60] + remainder + if title.count('`') % 4 != 0: + # ellipses have cut in the middle of a code block + title = title[:-3] + '``...' msg = msg.format(issue.id, issue.url, title) print(msg) print() From ba2aa9feb18a6e15a87bbd317caf957fff6658a5 Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Mon, 24 Jun 2024 15:14:49 +0100 Subject: [PATCH 455/500] DOC/DEV: update vendored-code page (#21032) --- doc/source/dev/core-dev/vendored-code.rst.inc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/source/dev/core-dev/vendored-code.rst.inc b/doc/source/dev/core-dev/vendored-code.rst.inc index ac316109b292..39d2b5476396 100644 --- a/doc/source/dev/core-dev/vendored-code.rst.inc +++ b/doc/source/dev/core-dev/vendored-code.rst.inc @@ -14,9 +14,10 @@ Maintainers should be careful to *not* accept contributions upstream. Instead, they should direct contributors to the upstream repo. Currently, this includes the following parts of the codebase: +- DIRECT_, at ``scipy/optimize/_direct`` - ARPACK_, at ``scipy/sparse/linalg/_eigen/arpack/ARPACK`` - SuperLU_, at ``scipy/sparse/linalg/_dsolve/SuperLU`` -- QHull_, at ``scipy/spatial/qhull`` +- QHull_, at ``scipy/spatial/qhull_src`` - trlib_, at ``scipy/optimize/_trlib`` - UNU.RAN_, at ``scipy/stats/_unuran`` @@ -25,3 +26,4 @@ Currently, this includes the following parts of the codebase: .. _QHull: https://github.com/qhull/qhull .. _trlib: https://github.com/felixlen/trlib .. _UNU.RAN: https://statmath.wu.ac.at/unuran/ +.. _DIRECT: https://github.com/stevengj/nlopt/tree/master/src/algs/direct From e36e728081475466d2faae65e1dfecfa2314c857 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Mon, 24 Jun 2024 17:48:45 +0300 Subject: [PATCH 456/500] DOC: interpolate: discuss linear interpolation with extrapolation (#21033) --- doc/source/tutorial/interpolate/1D.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/doc/source/tutorial/interpolate/1D.rst b/doc/source/tutorial/interpolate/1D.rst index f30e755775bc..2ea5f9a8ebcd 100644 --- a/doc/source/tutorial/interpolate/1D.rst +++ b/doc/source/tutorial/interpolate/1D.rst @@ -35,6 +35,9 @@ and ``y``, and a third array, ``xnew``, of points to evaluate the interpolation .. :caption: One-dimensional interpolation using `numpy.interp` +One limitation of `numpy.interp` is that it does not allow controlling the +extrapolation. See the :ref:`interpolation with B-Splines section ` +section for alternative routines which provide this kind of functionality. Cubic splines ============= @@ -109,6 +112,8 @@ data with an outlier: >>> plt.show() +.. _tutorial-interpolate_bsplines: + Interpolation with B-splines ============================ @@ -157,6 +162,25 @@ constructor. By default, both use the 'not-a-knot' boundary condition. +Non-cubic splines +----------------- + +One use of ``make_interp_spline`` is constructing a linear interpolant with +linear extrapolation since ``make_interp_spline`` extrapolates by default. Consider + + >>> from scipy.interpolate import make_interp_spline + >>> x = np.linspace(0, 5, 11) + >>> y = 2*x + >>> spl = make_interp_spline(x, y, k=1) # k=1: linear + >>> spl([-1, 6]) + [-2, 12] + >>> np.interp([-1, 6], x, y) + [0, 10] + +See :ref:`the extrapolation section ` for more details +and discussion. + + .. _tutorial-interpolate_parametric: Parametric spline curves From 6d5ab4c62912c01da7e3d701635b165cb8840d3a Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Mon, 24 Jun 2024 14:11:00 -0400 Subject: [PATCH 457/500] BUG: sparse: Fix advanced indexing using both slice and array (#21022) * copy spmatrix tests of fancy indexing for 2d arrays * fix fancy indexing with single array and slice * add elif * Update scipy/sparse/tests/test_indexing1d.py Co-authored-by: CJ Carey --------- Co-authored-by: CJ Carey --- scipy/sparse/_index.py | 7 +- scipy/sparse/tests/test_indexing1d.py | 93 +++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/scipy/sparse/_index.py b/scipy/sparse/_index.py index 8bd18f5b9b73..5bfcaf468305 100644 --- a/scipy/sparse/_index.py +++ b/scipy/sparse/_index.py @@ -260,6 +260,11 @@ def _validate_indices(self, key): idx_arrays = _broadcast_arrays(*(index[i] for i in array_indices)) if any(idx_arrays[0].shape != ix.shape for ix in idx_arrays[1:]): raise IndexError('array indices after broadcast differ in shape') + idx_shape = list(idx_arrays[0].shape) + idx_shape + elif len(array_indices) == 1: + arr_index = array_indices[0] + arr_shape = list(index[arr_index].shape) + idx_shape = idx_shape[:arr_index] + arr_shape + idx_shape[arr_index:] # add slice(None) (which is colon) to fill out full index nslice = self.ndim - index_ndim @@ -270,8 +275,6 @@ def _validate_indices(self, key): mid_shape = list(self.shape[ellps_pos : ellps_pos + nslice]) idx_shape = idx_shape[:ellps_pos] + mid_shape + idx_shape[ellps_pos:] - if array_indices: - idx_shape = list(index[array_indices[0]].shape) + idx_shape if (ndim := len(idx_shape)) > 2: raise IndexError(f'Only 1D or 2D arrays allowed. Index makes {ndim}D') return tuple(index), tuple(idx_shape) diff --git a/scipy/sparse/tests/test_indexing1d.py b/scipy/sparse/tests/test_indexing1d.py index caa03289bc8d..6646f6222709 100644 --- a/scipy/sparse/tests/test_indexing1d.py +++ b/scipy/sparse/tests/test_indexing1d.py @@ -330,6 +330,99 @@ def test_bad_index(self, spcreator): ): 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) From b47b977ed6bf3e34c03daac572c18eb69c209a2a Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 24 Jun 2024 14:29:17 -0400 Subject: [PATCH 458/500] Apply suggestions from code review Co-authored-by: CJ Carey --- scipy/sparse/_base.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 47626a618e4e..ab88d41315b3 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -624,8 +624,7 @@ def _matmul_dispatch(self, other): if issparse(other): if N != other.shape[0]: raise ValueError( - f"{err_prefix} (n,k),(k,m)->(n,m) " - f"(size {N} is different from {other.shape[0]})" + f"{err_prefix} (n,k={N}),(k={other.shape[0]},m)->(n,m)" ) return self._matmul_sparse(other) @@ -646,8 +645,7 @@ def _matmul_dispatch(self, other): # dense row or column vector if other.shape[0] != N: raise ValueError( - f"{err_prefix} (n,k),(k,1?)->(n,1?) " - f"(size {N} is different from {other.shape[0]})" + f"{err_prefix} (n,k={N}),(k={other.shape[0]},1?)->(n,1?)" ) result = self._matmul_vector(np.ravel(other)) @@ -667,8 +665,7 @@ def _matmul_dispatch(self, other): if other.shape[0] != N: raise ValueError( - f"{err_prefix} (n,k),(k,m)->(n,m) " - f"(size {N} is different from {other.shape[0]})" + f"{err_prefix} (n,k={N}),(k={other.shape[0]},m)->(n,m)" ) result = self._matmul_multivector(np.asarray(other)) From 663d9ca315f20230153f637cbdc3ea8089c475ad Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 24 Jun 2024 11:29:59 -0700 Subject: [PATCH 459/500] TST: stats._xp_var: skip JAX tests [skip circle] [skip cirrus] --- scipy/stats/_stats_py.py | 3 ++- scipy/stats/tests/test_stats.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 0b95d7f8f76b..311d5477a6c9 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -11104,7 +11104,8 @@ def _xp_mean(x, /, *, axis=None, weights=None, keepdims=False, nan_policy='propa 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 + # 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 = xp.asarray(x) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index fd9154b6b2b6..dd3b4128d6d1 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -9219,6 +9219,8 @@ def test_integer(self, xp): @array_api_compatible +@pytest.mark.usefixtures("skip_xp_backends") +@skip_xp_backends('jax.numpy', reasons=['JAX arrays do not support item assignment']) class TestXP_Var: @pytest.mark.parametrize('axis', [None, 1, -1, (-2, 2)]) @pytest.mark.parametrize('keepdims', [False, True]) From 3ea39ced6ca29968f925ca30c6c033507f30f529 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Mon, 24 Jun 2024 15:42:26 -0600 Subject: [PATCH 460/500] MAINT: forward port 1.14.0 relnotes * Forward port the SciPy `1.14.0` release notes following the release this afternoon. Also, update the docs version switcher. [docs only] --- doc/source/_static/version_switcher.json | 9 ++- doc/source/release/1.14.0-notes.rst | 98 +++++++++++++++++++----- 2 files changed, 84 insertions(+), 23 deletions(-) diff --git a/doc/source/_static/version_switcher.json b/doc/source/_static/version_switcher.json index ead99397ee43..ddc79cd33641 100644 --- a/doc/source/_static/version_switcher.json +++ b/doc/source/_static/version_switcher.json @@ -5,9 +5,14 @@ "url": "https://scipy.github.io/devdocs/" }, { - "name": "1.13.1 (stable)", - "version":"1.13.1", + "name": "1.14.0 (stable)", + "version":"1.14.0", "preferred": true, + "url": "https://docs.scipy.org/doc/scipy-1.14.0/" + }, + { + "name": "1.13.1", + "version":"1.13.1", "url": "https://docs.scipy.org/doc/scipy-1.13.1/" }, { diff --git a/doc/source/release/1.14.0-notes.rst b/doc/source/release/1.14.0-notes.rst index 27ec106ef125..971f7eb35eb1 100644 --- a/doc/source/release/1.14.0-notes.rst +++ b/doc/source/release/1.14.0-notes.rst @@ -2,8 +2,6 @@ SciPy 1.14.0 Release Notes ========================== -.. note:: SciPy 1.14.0 is not released yet! - .. contents:: SciPy 1.14.0 is the culmination of 3 months of hard work. It contains @@ -84,6 +82,13 @@ New features `scipy.sparse` improvements =========================== +- Sparse arrays now support 1D shapes in COO, DOK and CSR formats. + These are all the formats we currently intend to support 1D shapes. + Other sparse array formats raise an exception for 1D input. +- Sparse array methods min/nanmin/argmin and max analogs now return 1D arrays. + Results are still COO format sparse arrays for min/nanmin and + dense ``np.ndarray`` for argmin. +- Sparse matrix and array objects improve their ``repr`` and ``str`` output. - A special case has been added to handle multiplying a ``dia_array`` by a scalar, which avoids a potentially costly conversion to CSR format. - `scipy.sparse.csgraph.yen` has been added, allowing usage of Yen's K-Shortest @@ -198,7 +203,7 @@ Deprecated features - The option ``quadrature="trapz"`` in `scipy.integrate.quad_vec` has been deprecated in favour of ``quadrature="trapezoid"`` and will be removed in SciPy 1.16.0. -- `scipy.special.comb` has deprecated support for use of ``exact=True`` in +- ``scipy.special.{comb,perm}`` have deprecated support for use of ``exact=True`` in conjunction with non-integral ``N`` and/or ``k``. @@ -277,22 +282,23 @@ Other changes Authors ******* * Name (commits) -* h-vetinari (30) +* h-vetinari (34) * Steven Adams (1) + * Max Aehle (1) + * Ataf Fazledin Ahamed (2) + +* Luiz Eduardo Amaral (1) + * Trinh Quoc Anh (1) + * Miguel A. Batalla (7) + * Tim Beyer (1) + * Andrea Blengino (1) + * boatwrong (1) -* Jake Bowhay (47) +* Jake Bowhay (51) * Dietrich Brunn (2) -* Evgeni Burovski (174) +* Evgeni Burovski (177) * Tim Butters (7) + * CJ Carey (5) * Sean Cheah (46) -* Lucas Colley (72) +* Lucas Colley (73) * Giuseppe "Peppe" Dilillo (1) + * DWesl (2) * Pieter Eendebak (5) @@ -301,11 +307,11 @@ Authors * fancidev (2) * Anthony Frazier (1) + * Ilan Gold (1) + -* Ralf Gommers (122) +* Ralf Gommers (125) * Rohit Goswami (28) * Ben Greiner (1) + * Lorenzo Gualniera (1) + -* Matt Haberland (250) +* Matt Haberland (260) * Shawn Hsu (1) + * Budjen Jovan (3) + * Jozsef Kutas (1) @@ -317,37 +323,39 @@ Authors * marinelay (2) + * Nikolay Mayorov (1) * Nicholas McKibben (2) -* Melissa Weber Mendonça (6) +* Melissa Weber Mendonça (7) * João Mendes (1) + +* Samuel Le Meur-Diebolt (1) + * Tomiță Militaru (2) + -* Andrew Nelson (32) +* Andrew Nelson (35) * Lysandros Nikolaou (1) * Nick ODell (5) + * Jacob Ogle (1) + * Pearu Peterson (1) -* Matti Picus (4) -* Ilhan Polat (8) +* Matti Picus (5) +* Ilhan Polat (9) * pwcnorthrop (3) + * Bharat Raghunathan (1) * Tom M. Ragonneau (2) + -* Tyler Reddy (47) -* Pamphile Roy (17) +* Tyler Reddy (101) +* Pamphile Roy (18) * Atsushi Sakai (9) * Daniel Schmitz (5) * Julien Schueller (2) + -* Dan Schult (12) +* Dan Schult (13) * Tomer Sery (7) * Scott Shambaugh (4) * Tuhin Sharma (1) + * Sheila-nk (4) * Skylake (1) + -* Albert Steppi (214) +* Albert Steppi (215) * Kai Striega (6) * Zhibing Sun (2) + * Nimish Telang (1) + * toofooboo (1) + * tpl2go (1) + * Edgar Andrés Margffoy Tuay (44) +* Andrew Valentine (1) * Valerix (1) + * Christian Veenhuis (1) * void (2) + @@ -357,9 +365,10 @@ Authors * Xiao Yuan (1) * Irwin Zaid (35) * Elmar Zander (1) + -* ਗਗਨਦੀਪ ਸਿੰਘ (Gagandeep Singh) (2) + +* Zaikun ZHANG (1) +* ਗਗਨਦੀਪ ਸਿੰਘ (Gagandeep Singh) (4) + -A total of 81 people contributed to this release. +A total of 85 people contributed to this release. People with a "+" by their names contributed a patch for the first time. This list of names is automatically generated, and may not be fully complete. @@ -374,7 +383,9 @@ Issues closed for 1.14.0 * `#8056 `__: cho_factor and cho_solve don't support (0,0)-shape matrices * `#8083 `__: special.hyp2f1 returns the wrong values when c-a-b is an integer... * `#8510 `__: ValueError: failed to create intent(cache|hide)|optional array--... +* `#8848 `__: \`integrate.solve_ivp\` try to evaluate the function with much... * `#8856 `__: LinearNDInterpolator not thread safe +* `#9198 `__: \`solve_ivp\` RK45 can evaluate the function at times later than... * `#9307 `__: feature request: make \`scipy.stats.pearsonr\` accept 2-D arrays * `#9459 `__: BUG: linalg: lu and decompositions don't support (0, 1) or (0,... * `#12515 `__: scipy.linalg.pinvh gives incorrect results @@ -387,6 +398,7 @@ Issues closed for 1.14.0 * `#16748 `__: None of the \`cython_\*\` APIs have any tests using Cython * `#16926 `__: TEST/BUG: Tolerance violation in test_solvers::test_solve_discrete_are * `#17084 `__: ENH: Exporting the removed component of detrend() +* `#17341 `__: BUG: \`solve_ivp\` evaluates outside the requested interval for... * `#17559 `__: ENH: _mannwhitneyu.py computation of exact MWU statistics may... * `#17658 `__: Inconsistent support for empty matrices in linalg * `#19322 `__: BUG: \`rv_discrete.expect\` fails when duplicate positions @@ -404,10 +416,12 @@ Issues closed for 1.14.0 * `#20128 `__: BUG: \`csr_array(int())\` errors * `#20208 `__: BUG: Test failures due to \`invalid value encountered in _beta_ppf\`... * `#20247 `__: ENH: Akima1DInterpolator Extrapolation +* `#20256 `__: MAINT, BLD: symbol visibility warnings on MacOS ARM static lib... * `#20277 `__: Very noisy doc builds after jupyterlite-sphinx integration * `#20296 `__: CI: jupyterlite-shpinx pin breaks recent doc builds * `#20324 `__: MAINT, BUG (?): pearsonr statistic return type change * `#20357 `__: BUG: Memory usage in griddata function in version 1.12 +* `#20358 `__: TST, MAINT: failure in TestGroupDelay::test_singular against... * `#20377 `__: ENH: sparse: Update str dunder to handle 1D (and 2D better) * `#20378 `__: ENH: sparse: Update repr dunder to handle 1D (and maybe 2D better) * `#20385 `__: MAINT: special version hex cleanup @@ -421,6 +435,8 @@ Issues closed for 1.14.0 * `#20458 `__: MAINT: more potential cleanups related to version bumps * `#20461 `__: DOC: some likely changes to release process docs * `#20466 `__: BUG: scipy.linalg.bandwidth returns incorrect upper bandwidth +* `#20470 `__: BUG: \`TestNNLS.test_nnls_inner_loop_case1\` fails with MKL +* `#20486 `__: DEP: deprecate and remove remaining usages of slur-adjacent "trapz" * `#20488 `__: BUG: When given invalid bounds, \`_minimize_neldermead\` raises... * `#20492 `__: DOC: linalg.solve_discrete_lyapunov: dead reference link * `#20502 `__: BUG: special.hyp2f1: local test failure @@ -432,7 +448,9 @@ Issues closed for 1.14.0 * `#20562 `__: BUG: Invalid default bracket selection in _bracket_minimum. * `#20564 `__: TST: stats array API failure for test_skew_constant_value[torch]... * `#20584 `__: BUG: \`optimize.linprog\` fails with \`list\` type \`integrality\`... +* `#20587 `__: BLD: warning from \`scipy/special/special/gamma.h\` * `#20598 `__: ENH: special: add log of wright_bessel +* `#20603 `__: DOC: document switch from mailing list to discourse * `#20614 `__: DOC: dual_annealing optimizer does not pass bounds to minimizer... * `#20618 `__: BUG: scipy 'minimize' with method='trust-constr' with equality... * `#20620 `__: DOC: Suggested improvement to interp2d transition guide @@ -443,12 +461,20 @@ Issues closed for 1.14.0 * `#20683 `__: DOC: A typo in ValueError raised by signal.iirdesign * `#20691 `__: ENH: Reintroduce Apple Accelerate support * `#20697 `__: BUG: special: algorithmic Error in \`ratevl\` in \`cephes/polevl.h\` -* `#20740 `__: BLD/DEV: special: build warnings * `#20755 `__: BUG: stats: Two new test failures * `#20768 `__: BUG: optimize.minimize: garbage collection in \`lbfgs\` * `#20783 `__: BUG: Build failure on PyPy3.10 7.3.16: \`error: ‘Py_Initialize’... * `#20797 `__: BUG: special.hyp1f1: broken for complex argument * `#20802 `__: MAINT, TST: pytest-fail-slow and local concurrent runs/variability +* `#20840 `__: BUG: first shared library in scipy fails to be consumed by MSVC +* `#20850 `__: DOC: stats.bootstrap: improve documentation multidimensional... +* `#20852 `__: BUG: Library not loaded: @rpath/libgfortran.5.dylib for scipy... +* `#20860 `__: BUG/BLD: scipy-1.13.1 fails to build with msvc +* `#20901 `__: BUG: \`zsh: abort python\` after \`scipy.linalg.sqrtm\` on empty... +* `#20911 `__: TST: TestEig.test_singular failing tolerance with generic BLAS... +* `#20921 `__: DOC: stats: wrong docstrings of \`\*Result\` classes +* `#20938 `__: TST: tolerance violations with SciPy 1.14.0rc1 on linux-{aarch64,ppc64le} +* `#20943 `__: TST: test failures on windows with SciPy 1.14.0rc1 ************************ Pull requests for 1.14.0 @@ -456,6 +482,7 @@ Pull requests for 1.14.0 * `#13534 `__: ENH: Add more initialization methods for HessianUpdateStrategy * `#15321 `__: ENH: fft: Add \`prev_fast_len\` to complement \`next_fast_len\` +* `#17348 `__: BUG: integrate: make \`select_initial_step\` aware of integration... * `#17924 `__: ENH: sparse.linalg: speed up \`spsolve_triangular\` * `#18926 `__: ENH: Move symiirorder1/2, cspline2d, qspline2d and spline_filter... * `#19561 `__: ENH: stats.power: add function to simulate hypothesis test power @@ -599,7 +626,7 @@ Pull requests for 1.14.0 * `#20588 `__: DEP: special: remove legacy kwarg from special.comb and switch... * `#20590 `__: Revert "ENH: Use \`highspy\` in \`linprog\`" * `#20593 `__: ENH: constants: add array api support -* `#20595 `__: ENH: stats.circ___: add array-API support +* `#20595 `__: ENH: \`stats.circ___\`: add array-API support * `#20597 `__: ENH: stats.skewtest: add array-API support * `#20600 `__: TYP: update supported Mypy version from 1.0.0 to 1.10.0 * `#20604 `__: ENH: stats.monte_carlo_test: add array API support @@ -650,6 +677,7 @@ Pull requests for 1.14.0 * `#20726 `__: DOC: stats.{circmean, circvar, circstd}: improve accuracy/clarity * `#20730 `__: BUG: special: fix algorithmic error in \`ratevl\` in \`cephes/polevl.h\` * `#20732 `__: BUG: interpolate: do not segfault on bad boundary conditions +* `#20734 `__: BUG: stats.ttest_1samp: fix use of \`keepdims\` * `#20736 `__: ENH: stats.normaltest/jarque_bera: add array-API support * `#20737 `__: TST, MAINT: run optimize array API tests and fix \`chandrupatla\` * `#20738 `__: DOC: sparse.csgraph.dijkstra: add warning for \`directed=False\`... @@ -672,6 +700,7 @@ Pull requests for 1.14.0 * `#20780 `__: DEP: special.comb: deprecate \`exact=True\` for non-integer intputs * `#20781 `__: TST: stats: remove overhead of array_namespace in calls to _get_pvalue * `#20782 `__: ENH: stats: end-to-end array-API support for NHSTs with chi-squared... +* `#20784 `__: DOC: SciPy 1.14.0 relnotes * `#20787 `__: DOC: interpolate: mention default kinds in interp2d transition... * `#20788 `__: ENH: optimize: improve \`cobyqa\` performance by reducing overhead... * `#20789 `__: DEP: stats.linregress: deprecate one-arg use @@ -688,3 +717,30 @@ Pull requests for 1.14.0 * `#20812 `__: DOC: extend "building reproducible binaries" page * `#20815 `__: DOC: integrate: odeint user functions must not modify y. * `#20819 `__: REV: revert accidental \`cobyqa\` update in gh-17924 +* `#20820 `__: BLD: Warning fix from \`\`scipy/special/special/gamma.h\`\` +* `#20828 `__: DEP: deprecate trapz alias of \`stats.trapezoid\` distribution +* `#20831 `__: MAINT: version pins/prep for 1.14.0rc1 +* `#20838 `__: DOC: sparse: 1.14.0 release notes additions +* `#20839 `__: REL: set 1.14.0rc2 unreleased +* `#20841 `__: DOC: add cobyqa website reference +* `#20851 `__: DOC: add cobyqa website reference (#20841) +* `#20858 `__: MAINT: \`stats.bootstrap\`: emit \`FutureWarning\` about broadcasting +* `#20870 `__: BLD: test delocate works by removing original lib [wheel build] +* `#20881 `__: DOC: mailing list to forum +* `#20890 `__: DOC: Write API reference titles in monospace font +* `#20909 `__: DEP: special.perm: deprecate non-integer \`N\` and \`k\` with... +* `#20914 `__: TST: linalg: bump tolerance in \`TestEig::test_singular\` +* `#20919 `__: BLD: optimize: use hidden visibility for static HiGHS libraries +* `#20920 `__: MAINT: special: fix msvc build by using \`new\` and \`delete\`... +* `#20923 `__: DOC: update doctests to satisfy scipy-doctests==1.2.0 +* `#20927 `__: MAINT: adapt to a scipy-doctests change +* `#20933 `__: MAINT: 1.14.0rc2 backports +* `#20936 `__: DOC: \`array_api.rst\`: update 1.14 functions with array API... +* `#20937 `__: BUG/BLD: special: Ensure symbols in \`sf_error_state\` shared... +* `#20945 `__: TST: address tolerance violations with SciPy 1.14.0rc1 on linux-{aarch64,ppc64le} +* `#20952 `__: TST: loosen tolerance in test_x0_working to pass with alternate... +* `#20953 `__: TST: loosen tolerance in test_krandinit slightly to pass with... +* `#20961 `__: TST: robustify test_nnls_inner_loop_case1 +* `#20970 `__: REL: set 1.14.0 rc3 unreleased +* `#20973 `__: TST:sparse.linalg: Skip test due to sensitivity to numerical... +* `#20979 `__: STY: \`_lib._util\`: address new mypy complaint in main From f7e0d1a0d83741ea8efebf33f5ccbda12d1d8503 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 24 Jun 2024 15:46:36 -0700 Subject: [PATCH 461/500] ENH: stats.hmean/pmean: add array API support (#21035) * ENH: stats.hmean/pmean: add array API support * TST: stats.gmean/hmean/pmean: remove redundant tests/test_stats * MAINT: fix typo in comment --------- Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/stats/_stats_py.py | 20 +-- scipy/stats/tests/test_stats.py | 248 +++++++++++++++++--------------- 2 files changed, 145 insertions(+), 123 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 1eb94bbbfe07..daa53533d292 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -302,16 +302,18 @@ def hmean(a, axis=0, dtype=None, *, weights=None): 1.9029126213592233 """ - a = np.asarray(a, dtype=dtype) + xp = array_namespace(a, weights) + a = xp.asarray(a, dtype=dtype) if weights is not None: - weights = np.asarray(weights, dtype=dtype) + weights = xp.asarray(weights, dtype=dtype) negative_mask = a < 0 - if np.any(negative_mask): + if xp.any(negative_mask): # `where` avoids having to be careful about dtypes and will work with # JAX. This is the exceptional case, so it's OK to be a little slower. - a = np.where(negative_mask, np.nan, a) + # Won't work for array_api_strict for now, but see data-apis/array-api#807 + a = xp.where(negative_mask, xp.nan, a) message = ("The harmonic mean is only defined if all elements are " "non-negative; otherwise, the result is NaN.") warnings.warn(message, RuntimeWarning, stacklevel=2) @@ -428,16 +430,18 @@ def pmean(a, p, *, axis=0, dtype=None, weights=None): if p == 0: return gmean(a, axis=axis, dtype=dtype, weights=weights) - a = np.asarray(a, dtype=dtype) + xp = array_namespace(a, weights) + a = xp.asarray(a, dtype=dtype) if weights is not None: - weights = np.asanyarray(weights, dtype=dtype) + weights = xp.asarray(weights, dtype=dtype) negative_mask = a < 0 - if np.any(negative_mask): + if xp.any(negative_mask): # `where` avoids having to be careful about dtypes and will work with # JAX. This is the exceptional case, so it's OK to be a little slower. - a = np.where(negative_mask, np.nan, a) + # Won't work for array_api_strict for now, but see data-apis/array-api#807 + a = xp.where(negative_mask, np.nan, a) message = ("The power mean is only defined if all elements are " "non-negative; otherwise, the result is NaN.") warnings.warn(message, RuntimeWarning, stacklevel=2) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 834cea712a21..b53cdca8f0d0 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6785,131 +6785,147 @@ def test_obrientransform(): assert_array_almost_equal(result[0], expected, decimal=4) -def check_equal_gmean(array_like, desired, *, xp, axis=None, dtype=None, rtol=1e-7, - weights=None): +def check_equal_xmean(*args, xp, mean_fun, axis=None, dtype=None, + rtol=1e-7, 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) desired = xp.asarray(desired, dtype=dtype) weights = xp.asarray(weights, dtype=dtype) if weights is not None else weights - x = stats.gmean(array_like, axis=axis, dtype=dtype, weights=weights) + args = (array_like,) if len(args) == 2 else (array_like, p) + x = mean_fun(*args, axis=axis, dtype=dtype, weights=weights) xp_assert_close(x, desired, rtol=rtol) -def check_equal_hmean(array_like, desired, axis=None, dtype=None, rtol=1e-7, - weights=None): - x = stats.hmean(array_like, axis=axis, dtype=dtype, weights=weights) - assert_allclose(x, desired, rtol=rtol) - assert_equal(x.dtype, dtype) +def check_equal_gmean(*args, **kwargs): + return check_equal_xmean(*args, mean_fun=stats.gmean, **kwargs) -def check_equal_pmean(array_like, exp, desired, axis=None, dtype=None, - rtol=1e-7, weights=None): - x = stats.pmean(array_like, exp, axis=axis, dtype=dtype, weights=weights) - assert_allclose(x, desired, rtol=rtol) - assert_equal(x.dtype, dtype) +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) + +@array_api_compatible class TestHMean: - def test_0(self): + def test_0(self, xp): a = [1, 0, 2] desired = 0 - check_equal_hmean(a, desired) + check_equal_hmean(a, desired, xp=xp) - def test_1d_list(self): - # Test a 1d list + def test_1d(self, xp): + # Test a 1d case a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] desired = 34.1417152147 - check_equal_hmean(a, desired) + check_equal_hmean(a, desired, xp=xp) a = [1, 2, 3, 4] desired = 4. / (1. / 1 + 1. / 2 + 1. / 3 + 1. / 4) - check_equal_hmean(a, desired) - - def test_1d_array(self): - # Test a 1d array - a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) - desired = 34.1417152147 - check_equal_hmean(a, desired) + check_equal_hmean(a, desired, xp=xp) - def test_1d_array_with_zero(self): + def test_1d_with_zero(self, xp): a = np.array([1, 0]) desired = 0.0 - assert_equal(stats.hmean(a), desired) + check_equal_hmean(a, desired, xp=xp, rtol=0.0) - def test_1d_array_with_negative_value(self): + @skip_xp_backends('array_api_strict', + reasons=["`array_api_strict.where` `fillvalue` doesn't " + "accept Python scalars. See data-apis/array-api#807."]) + @pytest.mark.usefixtures("skip_xp_backends") + def test_1d_with_negative_value(self, xp): + # Won't work for array_api_strict for now, but see data-apis/array-api#807 a = np.array([1, 0, -1]) message = "The harmonic mean is only defined..." with pytest.warns(RuntimeWarning, match=message): - res = stats.hmean(a) - np.testing.assert_equal(res, np.nan) + 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_list(self): - # Test a 2d list + 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(a, desired) + check_equal_hmean(np.array(a), desired, xp=xp) - def test_2d_array(self): - # Test a 2d array - a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] - desired = 38.6696271841 - check_equal_hmean(np.array(a), desired) - - def test_2d_axis0(self): - # Test a 2d list with axis=0 + 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) + check_equal_hmean(a, desired, axis=0, xp=xp) - def test_2d_axis0_with_zero(self): + 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]) - assert_allclose(stats.hmean(a, axis=0), desired) + check_equal_hmean(a, desired, axis=0, xp=xp) - def test_2d_axis1(self): - # Test a 2d list with axis=1 + 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) + check_equal_hmean(a, desired, axis=1, xp=xp) - def test_2d_axis1_with_zero(self): + 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]) - assert_allclose(stats.hmean(a, axis=1), desired) + check_equal_hmean(a, desired, axis=1, xp=xp) - def test_weights_1d_list(self): + @pytest.mark.skip_xp_backends( + np_only=True, + reasons=['array-likes only supported for NumPy backend'], + ) + @pytest.mark.usefixtures("skip_xp_backends") + 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) + check_equal_hmean(a, desired, weights=weights, rtol=1e-5, xp=xp) - def test_weights_2d_array_axis0(self): + 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) + check_equal_hmean(a, desired, axis=0, weights=weights, rtol=1e-5, xp=xp) - def test_weights_2d_array_axis1(self): + 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) + check_equal_hmean(a, desired, axis=1, weights=weights, rtol=1e-5, xp=xp) - def test_weights_masked_1d_array(self): + @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 - check_equal_hmean(a, desired, weights=weights, rtol=1e-5) + 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) @array_api_compatible @@ -6919,8 +6935,8 @@ def test_0(self, xp): desired = 0 check_equal_gmean(a, desired, xp=xp) - def test_1d_list(self, xp): - # Test a 1d list + def test_1d(self, xp): + # Test a 1d case a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] desired = 45.2872868812 check_equal_gmean(a, desired, xp=xp) @@ -6929,31 +6945,19 @@ def test_1d_list(self, xp): desired = power(1 * 2 * 3 * 4, 1. / 4.) check_equal_gmean(a, desired, rtol=1e-14, xp=xp) - def test_1d_array(self, xp): - # Test a 1d array - a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) - desired = 45.2872868812 - check_equal_gmean(a, desired, xp=xp) - a = array([1, 2, 3, 4], float32) desired = power(1 * 2 * 3 * 4, 1. / 4.) check_equal_gmean(a, desired, dtype=xp.float32, xp=xp) # Note the next tests use axis=None as default, not axis=0 - def test_2d_list(self, xp): - # Test a 2d list + 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_array(self, xp): - # Test a 2d array - a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] - desired = 52.8885199 - check_equal_gmean(array(a), desired, xp=xp) - def test_2d_axis0(self, xp): - # Test a 2d list with axis=0 + # 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) @@ -6963,7 +6967,7 @@ def test_2d_axis0(self, xp): check_equal_gmean(a, desired, axis=0, rtol=1e-14, xp=xp) def test_2d_axis1(self, xp): - # Test a 2d list with axis=1 + # 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) @@ -6978,22 +6982,15 @@ def test_large_values(self, xp): desired = 1e200 check_equal_gmean(a, desired, rtol=1e-13, xp=xp) - def test_1d_list0(self, xp): - # Test a 1d list with zero element + 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) - def test_1d_array0(self, xp): - # Test a 1d array with zero element - a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 0]) - desired = 0.0 # due to exp(-inf)=0 - with np.errstate(divide='ignore'): - check_equal_gmean(a, desired, xp=xp) - - def test_1d_list_neg(self, xp): - # Test a 1d list with negative element + 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'): @@ -7017,7 +7014,7 @@ def test_weights_1d_list(self, xp): res = stats.gmean(a, weights=weights) xp_assert_close(res, np.asarray(desired), rtol=1e-5) - def test_weights_1d_array(self, xp): + 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]) @@ -7037,6 +7034,7 @@ def test_weights_masked_1d_array(self, xp): dtype=np.float64, xp=xp) +@array_api_compatible class TestPMean: def pmean_reference(a, p): @@ -7045,81 +7043,101 @@ def pmean_reference(a, p): def wpmean_reference(a, p, weights): return (np.sum(weights * a**p) / np.sum(weights))**(1/p) - def test_bad_exponent(self): + def test_bad_exponent(self, xp): with pytest.raises(ValueError, match='Power mean only defined for'): - stats.pmean([1, 2, 3], [0]) + stats.pmean(xp.asarray([1, 2, 3]), xp.asarray([0])) with pytest.raises(ValueError, match='Power mean only defined for'): - stats.pmean([1, 2, 3], np.array([0])) + stats.pmean(xp.asarray([1, 2, 3]), xp.asarray([0])) - def test_1d_list(self): + def test_1d(self, xp): 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) + check_equal_pmean(a, p, desired, 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, 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) + check_equal_pmean(a, p, desired, xp=xp) - def test_1d_array(self): - a, p = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]), -2.5 - desired = TestPMean.pmean_reference(a, p) - check_equal_pmean(a, p, desired) - - def test_1d_array_with_zero(self): + def test_1d_with_zero(self, xp): a, p = np.array([1, 0]), -1 desired = 0.0 - assert_equal(stats.pmean(a, p), desired) + check_equal_pmean(a, p, desired, rtol=0.0, xp=xp) - def test_1d_array_with_negative_value(self): + @skip_xp_backends('array_api_strict', + reasons=["`array_api_strict.where` `fillvalue` doesn't " + "accept Python scalars. See data-apis/array-api#807."]) + @pytest.mark.usefixtures("skip_xp_backends") + 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 pytest.warns(RuntimeWarning, match=message): - res = stats.pmean(a, p) - np.testing.assert_equal(res, np.nan) + 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): + def test_2d_axisnone(self, a, p, xp): desired = TestPMean.pmean_reference(np.array(a), p) - check_equal_pmean(a, p, desired) + 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_list_axis0(self, a, p): + 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) + 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_list_axis1(self, a, p): + 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) + check_equal_pmean(a, p, desired, axis=1, xp=xp) - def test_weights_1d_list(self): + 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) + check_equal_pmean(a, p, desired, weights=weights, rtol=1e-5, xp=xp) - def test_weights_masked_1d_array(self): + @pytest.mark.skip_xp_backends( + np_only=True, + reasons=['array-likes only supported for NumPy backend'], + ) + @pytest.mark.usefixtures("skip_xp_backends") + 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) - check_equal_pmean(a, p, desired, weights=weights, rtol=1e-5) + 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"), @@ -7127,7 +7145,7 @@ def test_weights_masked_1d_array(self): (0, "gmean", 0), (1, "hmean", -1)] ) - def test_weights_2d_array(self, axis, fun_name, p): + 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) @@ -7136,7 +7154,7 @@ def fun(a, axis, weights): 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) + check_equal_pmean(a, p, desired, axis=axis, weights=weights, rtol=1e-5, xp=xp) class TestGSTD: From 8ee57f50e27e27ec0bfee92785ba583b0a4152af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 25 Jun 2024 08:37:54 +0200 Subject: [PATCH 462/500] TST: optimize: fix exception test on PyPy3.10 Adjust the expected `float(complex())` conversion exception message to allow for the word "real" to be missing, as it is on PyPy3.10. This makes the test pass both with CPython and PyPy3, and also permits for PyPy3 to adjust the message in line with CPython in the future. Fixes #21045 --- scipy/optimize/tests/test_hessian_update_strategy.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scipy/optimize/tests/test_hessian_update_strategy.py b/scipy/optimize/tests/test_hessian_update_strategy.py index fe9d7a059b47..ce138201b24f 100644 --- a/scipy/optimize/tests/test_hessian_update_strategy.py +++ b/scipy/optimize/tests/test_hessian_update_strategy.py @@ -100,9 +100,8 @@ def test_initialize_catch_illegal(self): ndims = 3 # no complex allowed inits_msg_errtype = ((complex(3.14), - re.escape("float() argument must be a " - "string or a real number, " - "not 'complex'"), + r"float\(\) argument must be a string or a " + r"(real )?number, not 'complex'", TypeError), (np.array([3.2, 2.3, 1.2]).astype(np.complex128), From fffcaa8c58b6909d22063c29e4f9593059a40230 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 25 Jun 2024 00:55:02 -0700 Subject: [PATCH 463/500] ENH: stats.tvar/tstd/tsem: add array API support (#21036) * ENH: stats.tvar: fix array API support * ENH: stats.tstd: add array API support * ENH: stats.tsem: add array API support * TST: stats.tmean and friends: apply @array_api_compatible to TestTrimmedStats * STY: stats.tstd/tsem: correct copy-paste comment and lint * TST: stats.tvar/tstd/tsem: adjust for PyTorch --- scipy/stats/_stats_py.py | 29 ++++++--- scipy/stats/tests/test_stats.py | 106 +++++++++++++++----------------- 2 files changed, 69 insertions(+), 66 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index daa53533d292..834031047a00 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -690,9 +690,14 @@ def tvar(a, limits=None, inclusive=(True, True), axis=0, ddof=1): 20.0 """ - a, _ = _put_val_to_limits(a, limits, inclusive) - return np.nanvar(a, ddof=ddof, axis=axis) - + 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) @_axis_nan_policy_factory( lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) @@ -868,7 +873,7 @@ def tstd(a, limits=None, inclusive=(True, True), axis=0, ddof=1): 4.4721359549995796 """ - return np.sqrt(tvar(a, limits, inclusive, axis, ddof, _no_deco=True)) + return tvar(a, limits, inclusive, axis, ddof, _no_deco=True)**0.5 @_axis_nan_policy_factory( @@ -920,10 +925,18 @@ def tsem(a, limits=None, inclusive=(True, True), axis=0, ddof=1): 1.1547005383792515 """ - a, _ = _put_val_to_limits(a, limits, inclusive) - sd = np.sqrt(np.nanvar(a, ddof=ddof, axis=axis)) - n_obs = (~np.isnan(a)).sum(axis=axis) - return sd / np.sqrt(n_obs, dtype=sd.dtype) + 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 + + n_obs = xp.sum(~xp.isnan(a), axis=axis, dtype=sd.dtype) + return sd / n_obs**0.5 ##################################### diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index b53cdca8f0d0..7c7629d2a563 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -23,7 +23,7 @@ import pytest from pytest import raises as assert_raises import numpy.ma.testutils as mat -from numpy import array, arange, float32, float64, power +from numpy import array, arange, float32, power import numpy as np import scipy.stats as stats @@ -73,22 +73,21 @@ ROUND = array([0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5], float) +@array_api_compatible +@skip_xp_backends('array_api_strict', 'jax.numpy', + reasons=["`array_api_strict.where` `fillvalue` doesn't " + "accept Python floats. See data-apis/array-api#807.", + "JAX doesn't allow item assignment."]) +@pytest.mark.usefixtures("skip_xp_backends") class TestTrimmedStats: # TODO: write these tests to handle missing values properly dprec = np.finfo(np.float64).precision - @array_api_compatible - @skip_xp_backends('array_api_strict', 'jax.numpy', - reasons=["`array_api_strict.where` `fillvalue` doesn't " - "accept Python floats. See data-apis/array-api#807.", - "JAX doesn't allow item assignment, and this " - "function uses _lazywhere."]) - @pytest.mark.usefixtures("skip_xp_backends") def test_tmean(self, xp): - x = xp.asarray(X) + x = xp.asarray(X.tolist()) # use default dtype of xp y = stats.tmean(x, (2, 8), (True, True)) - xp_assert_close(y, xp.asarray(5.0, dtype=x.dtype)) + xp_assert_close(y, xp.asarray(5.0)) y1 = stats.tmean(x, limits=(2, 8), inclusive=(False, False)) y2 = stats.tmean(x, limits=None) @@ -130,51 +129,45 @@ def test_tmean(self, xp): 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)) - def test_tvar(self): - y = stats.tvar(X, limits=(2, 8), inclusive=(True, True)) - assert_approx_equal(y, 4.6666666666666661, significant=self.dprec) + def test_tvar(self, xp): + x = xp.asarray(X.tolist()) # use default dtype of xp + xp_test = array_namespace(x) # need array-api-compat var for `correction` - y = stats.tvar(X, limits=None) - assert_approx_equal(y, X.var(ddof=1), significant=self.dprec) + y = stats.tvar(x, limits=(2, 8), inclusive=(True, True)) + xp_assert_close(y, xp.asarray(4.6666666666666661)) - x_2d = arange(63, dtype=float64).reshape((9, 7)) + y = stats.tvar(x, limits=None) + xp_assert_close(y, xp_test.var(x, correction=1)) + + x_2d = xp.reshape(xp.arange(63.), (9, 7)) y = stats.tvar(x_2d, axis=None) - assert_approx_equal(y, x_2d.var(ddof=1), significant=self.dprec) + xp_assert_close(y, xp_test.var(x_2d, correction=1)) y = stats.tvar(x_2d, axis=0) - assert_array_almost_equal(y[0], np.full((1, 7), 367.50000000), decimal=8) + xp_assert_close(y, xp.full((7,), 367.5)) y = stats.tvar(x_2d, axis=1) - assert_array_almost_equal(y[0], np.full((1, 9), 4.66666667), decimal=8) + xp_assert_close(y, xp.full((9,), 4.66666667)) - y = stats.tvar(x_2d[3, :]) - assert_approx_equal(y, 4.666666666666667, significant=self.dprec) - - with suppress_warnings() as sup: - sup.record(RuntimeWarning, "Degrees of freedom <= 0 for slice.") + # 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 some values along one axis - y = stats.tvar(x_2d, limits=(1, 5), axis=1, inclusive=(True, True)) - assert_approx_equal(y[0], 2.5, significant=self.dprec) + # 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)) - # Limiting all values along one axis - y = stats.tvar(x_2d, limits=(0, 6), axis=1, inclusive=(True, True)) - assert_approx_equal(y[0], 4.666666666666667, significant=self.dprec) - assert_equal(y[1], np.nan) + def test_tstd(self, xp): + x = xp.asarray(X.tolist()) # use default dtype of xp + xp_test = array_namespace(x) # need array-api-compat std for `correction` - def test_tstd(self): - y = stats.tstd(X, (2, 8), (True, True)) - assert_approx_equal(y, 2.1602468994692865, significant=self.dprec) + y = stats.tstd(x, (2, 8), (True, True)) + xp_assert_close(y, xp.asarray(2.1602468994692865)) - y = stats.tstd(X, limits=None) - assert_approx_equal(y, X.std(ddof=1), significant=self.dprec) + y = stats.tstd(x, limits=None) + xp_assert_close(y, xp_test.std(x, correction=1)) - @array_api_compatible - @skip_xp_backends('array_api_strict', 'jax.numpy', - reasons=["`array_api_strict.where` `fillvalue` doesn't " - "accept Python floats. See data-apis/array-api#807.", - "JAX doesn't allow item assignment."]) - @pytest.mark.usefixtures("skip_xp_backends") def test_tmin(self, xp): x = xp.arange(10) xp_assert_equal(stats.tmin(x), xp.asarray(0)) @@ -197,7 +190,9 @@ def test_tmin(self, xp): res = stats.tmin(x, lowerlimit=4, axis=1) xp_assert_equal(res, xp.asarray([np.nan, 4, 8, 12])) - def test_tmin_scalar_and_nanpolicy(self): + @skip_xp_backends(np_only=True, + reasons=["Only NumPy arrays support scalar input/`nan_policy`."]) + def test_tmin_scalar_and_nanpolicy(self, xp): assert_equal(stats.tmin(4), 4) x = np.arange(10.) @@ -212,12 +207,6 @@ def test_tmin_scalar_and_nanpolicy(self): with assert_raises(ValueError, match=msg): stats.tmin(x, nan_policy='foobar') - @array_api_compatible - @skip_xp_backends('array_api_strict', 'jax.numpy', - reasons=["`array_api_strict.where` `fillvalue` doesn't " - "accept Python floats. See data-apis/array-api#807.", - "JAX doesn't allow item assignment."]) - @pytest.mark.usefixtures("skip_xp_backends") def test_tmax(self, xp): x = xp.arange(10) xp_assert_equal(stats.tmax(x), xp.asarray(9)) @@ -242,7 +231,9 @@ def test_tmax(self, xp): res = stats.tmax(x, upperlimit=11, axis=1) xp_assert_equal(res, xp.asarray([3, 7, 11, np.nan])) - def test_tax_scalar_and_nanpolicy(self): + @skip_xp_backends(np_only=True, + reasons=["Only NumPy arrays support scalar input/`nan_policy`."]) + def test_tax_scalar_and_nanpolicy(self, xp): assert_equal(stats.tmax(4), 4) x = np.arange(10.) @@ -257,15 +248,14 @@ def test_tax_scalar_and_nanpolicy(self): with assert_raises(ValueError, match=msg): stats.tmax(x, nan_policy='foobar') - def test_tsem(self): - y = stats.tsem(X, limits=(3, 8), inclusive=(False, True)) - y_ref = np.array([4, 5, 6, 7, 8]) - assert_approx_equal(y, y_ref.std(ddof=1) / np.sqrt(y_ref.size), - significant=self.dprec) + def test_tsem(self, xp): + x = xp.asarray(X.tolist()) # use default dtype of xp + xp_test = array_namespace(x) # need array-api-compat std for `correction` - assert_approx_equal(stats.tsem(X, limits=[-1, 10]), - stats.tsem(X, limits=None), - significant=self.dprec) + y = stats.tsem(x, limits=(3, 8), inclusive=(False, True)) + y_ref = xp.asarray([4., 5., 6., 7., 8.]) + xp_assert_close(y, xp_test.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: From 485b26da6b797b5b98d9441ae304388ea1dceb66 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 27 Jun 2024 10:04:09 -0600 Subject: [PATCH 464/500] MAINT: gcc-14 test_region5 tol bump (#21063) * Fixes #21059 * A small tolerance bump in `TestHyp2f1.test_region5` needed for one its test cases to pass when compiling SciPy with gcc-14 series. [ci skip] [skip ci] --- scipy/special/tests/test_hyp2f1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/special/tests/test_hyp2f1.py b/scipy/special/tests/test_hyp2f1.py index 6c2f6ec103bf..adef4e2584c8 100644 --- a/scipy/special/tests/test_hyp2f1.py +++ b/scipy/special/tests/test_hyp2f1.py @@ -2093,7 +2093,7 @@ def test_region4(self, hyp2f1_test_case): c=-15.5, z=(0.3413793103448277-0.9482758620689655j), expected=(-1.0509834850116921-1.1145522325486075j), - rtol=1e-14, + rtol=1.1e-14, ), ), pytest.param( From bd8f399b202ad8b503733eed2fd33250030b5d78 Mon Sep 17 00:00:00 2001 From: Albert Steppi Date: Thu, 27 Jun 2024 16:48:23 -0400 Subject: [PATCH 465/500] BUG: special: remove type punning (#21067) to avoid warnings in LTO builds --- scipy/special/special_wrappers.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/scipy/special/special_wrappers.cpp b/scipy/special/special_wrappers.cpp index 38b32dc15de4..5b7f450f9858 100644 --- a/scipy/special/special_wrappers.cpp +++ b/scipy/special/special_wrappers.cpp @@ -107,19 +107,11 @@ using namespace std; namespace { complex to_complex(npy_cdouble z) { - union { - npy_cdouble cvalue; - complex value; - } z_union{z}; - return z_union.value; + return {npy_creal(z), npy_cimag(z)}; } npy_cdouble to_ccomplex(complex z) { - union { - complex value; - npy_cdouble cvalue; - } z_union{z}; - return z_union.cvalue; + return {z.real(), z.imag()}; } } // namespace From b3cf42e98594904a49f65ce0c37484d7a00fa2e7 Mon Sep 17 00:00:00 2001 From: Djip007 <3705339+Djip007@users.noreply.github.com> Date: Fri, 28 Jun 2024 00:46:15 +0200 Subject: [PATCH 466/500] BUG: interpolate.LinearNDInterpolator: fix for precomputed triangulation (#21051) --- scipy/interpolate/interpnd.pyx | 7 ++++--- scipy/interpolate/tests/test_interpnd.py | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/scipy/interpolate/interpnd.pyx b/scipy/interpolate/interpnd.pyx index 61e0e716e071..706023a266a1 100644 --- a/scipy/interpolate/interpnd.pyx +++ b/scipy/interpolate/interpnd.pyx @@ -88,9 +88,10 @@ class NDInterpolatorBase: self.scale = np.ptp(points, axis=0) self.scale[~(self.scale > 0)] = 1.0 # avoid division by 0 self.points /= self.scale - - self._calculate_triangulation(self.points) - + + if self.tri is None: + self._calculate_triangulation(self.points) + if need_values or values is not None: self._set_values(values, fill_value, need_contiguous, ndim) else: diff --git a/scipy/interpolate/tests/test_interpnd.py b/scipy/interpolate/tests/test_interpnd.py index 55ef27672f45..a6c29375ffa0 100644 --- a/scipy/interpolate/tests/test_interpnd.py +++ b/scipy/interpolate/tests/test_interpnd.py @@ -59,8 +59,10 @@ def test_tri_input(self): y = y - 3j*y tri = qhull.Delaunay(x) - yi = interpnd.LinearNDInterpolator(tri, y)(x) + interpolator = interpnd.LinearNDInterpolator(tri, y) + yi = interpolator(x) assert_almost_equal(y, yi) + assert interpolator.tri is tri def test_square(self): # Test barycentric interpolation on a square against a manual From 24f4f89b80df3a987bf707248d55c031f14917d7 Mon Sep 17 00:00:00 2001 From: Albert Steppi Date: Fri, 28 Jun 2024 20:59:57 -0400 Subject: [PATCH 467/500] BUG: special: Fixes for pro_rad1 (#21062) * BUG: Fix indices in sphj * Fix sphj argument order * Fix translation error from original Fortran * TST: Add test for pro_rad1 * Bump test tolerance for linux 32bit --- scipy/special/special/specfun/specfun.h | 10 +++++----- scipy/special/tests/test_specfun.py | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/scipy/special/special/specfun/specfun.h b/scipy/special/special/specfun/specfun.h index 51c7d2bcfd78..c574e7175f9e 100644 --- a/scipy/special/special/specfun/specfun.h +++ b/scipy/special/special/specfun/specfun.h @@ -4318,7 +4318,7 @@ inline int msta2(double x, int n, int mp) { n1 = nn; f1 = f; } - return nn; + return nn + 10; } @@ -4777,7 +4777,7 @@ inline void rmn1(int m, int n, T c, T x, int kd, T *df, T *r1f, T *r1d) { cx = c * x; nm2 = 2 * nm + m; - sphj(static_cast(nm2), cx, &nm2, sj, dj); + sphj(cx, nm2, &nm2, sj, dj); a0 = pow(1.0 - kd / (x * x), 0.5 * m) / suc; *r1f = 0.0; @@ -5597,7 +5597,7 @@ void sphj(T x, int n, int *nm, T *sj, T *dj) { } sj[0] = 1.0; if (n > 0) { - dj[0] = 1.0 / 3.0; + dj[1] = 1.0 / 3.0; } return; } @@ -5621,7 +5621,7 @@ void sphj(T x, int n, int *nm, T *sj, T *dj) { f1 = 1e-100; for (k = m; k >= 0; k--) { f = (2.0*k + 3.0)*f1/x - f0; - if (k <= *nm) { sj[k - 1] = f; } + if (k <= *nm) { sj[k] = f; } f0 = f1; f1 = f; } @@ -5631,7 +5631,7 @@ void sphj(T x, int n, int *nm, T *sj, T *dj) { } } for (k = 1; k <= *nm; k++) { - dj[k] = sj[k - 1] - (k + 1.0)*sj[k - 1]/x; + dj[k] = sj[k - 1] - (k + 1.0)*sj[k]/x; } return; } diff --git a/scipy/special/tests/test_specfun.py b/scipy/special/tests/test_specfun.py index aa9c839f6c3e..d096f60dd653 100644 --- a/scipy/special/tests/test_specfun.py +++ b/scipy/special/tests/test_specfun.py @@ -29,3 +29,12 @@ def test_hygfz_branches(): """(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) From 3959e8b28a8fdf9f6803fe34bf8f0e34909d840d Mon Sep 17 00:00:00 2001 From: Yusuke Toyama <131319857+koko1928@users.noreply.github.com> Date: Sat, 29 Jun 2024 12:28:35 +0900 Subject: [PATCH 468/500] DOC: optimize: add comparison of optimizers to guide (#20040) DOC: optimize: add comparison of optimizers to guide --------- Co-authored-by: Daniel Schmitz <40656107+dschmitz89@users.noreply.github.com> Co-authored-by: Andrew Nelson Co-authored-by: Matt Haberland --- doc/source/tutorial/optimize.rst | 215 +++++++++++++++++++++++++------ 1 file changed, 175 insertions(+), 40 deletions(-) diff --git a/doc/source/tutorial/optimize.rst b/doc/source/tutorial/optimize.rst index e36fa4b36be9..4a747feffd62 100644 --- a/doc/source/tutorial/optimize.rst +++ b/doc/source/tutorial/optimize.rst @@ -15,9 +15,8 @@ The :mod:`scipy.optimize` package provides several commonly used optimization algorithms. A detailed listing is available: :mod:`scipy.optimize` (can also be found by ``help(scipy.optimize)``). - -Unconstrained minimization of multivariate scalar functions (:func:`minimize`) ------------------------------------------------------------------------------- +Local minimization of multivariate scalar functions (:func:`minimize`) +---------------------------------------------------------------------- The :func:`minimize` function provides a common interface to unconstrained and constrained minimization algorithms for multivariate scalar functions @@ -40,8 +39,11 @@ and must return a float value. The exact calling signature must be ``f(x, *args)`` where ``x`` represents a numpy array and ``args`` a tuple of additional arguments supplied to the objective function. +Unconstrained minimization +^^^^^^^^^^^^^^^^^^^^^^^^^^ + Nelder-Mead Simplex algorithm (``method='Nelder-Mead'``) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +"""""""""""""""""""""""""""""""""""""""""""""""""""""""" In the example below, the :func:`minimize` routine is used with the *Nelder-Mead* simplex algorithm (selected through the ``method`` @@ -125,7 +127,7 @@ Another alternative is to use :py:func:`functools.partial`. [1. 1. 1. 1. 0.99999999] Broyden-Fletcher-Goldfarb-Shanno algorithm (``method='BFGS'``) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" In order to converge more quickly to the solution, this routine uses the gradient of the objective function. If the gradient is not given @@ -177,8 +179,7 @@ through the ``jac`` parameter as illustrated below. >>> res.x array([1., 1., 1., 1., 1.]) -Avoiding Redundant Calculation -"""""""""""""""""""""""""""""" +**Avoiding Redundant Calculation** It is common for the objective function and its gradient to share parts of the calculation. For instance, consider the following problem. @@ -244,7 +245,7 @@ simple situations, this can be accomplished with the Newton-Conjugate-Gradient algorithm (``method='Newton-CG'``) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" Newton-Conjugate Gradient algorithm is a modified Newton's method and uses a conjugate gradient algorithm to (approximately) invert @@ -275,8 +276,7 @@ or a function to compute the product of the Hessian with an arbitrary vector. -Full Hessian example: -""""""""""""""""""""" +**Full Hessian example** The Hessian of the Rosenbrock function is @@ -324,8 +324,7 @@ the function using Newton-CG method is shown in the following example: array([1., 1., 1., 1., 1.]) -Hessian product example: -"""""""""""""""""""""""" +**Hessian product example** For larger minimization problems, storing the entire Hessian matrix can consume considerable time and memory. The Newton-CG algorithm only needs @@ -378,7 +377,7 @@ according to the authors, deals more effectively with this problematic situation and will be described next. Trust-Region Newton-Conjugate-Gradient Algorithm (``method='trust-ncg'``) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" The ``Newton-CG`` method is a line search method: it finds a direction of search minimizing a quadratic approximation of the function and then uses @@ -401,9 +400,7 @@ model with the real function. This family of methods is known as trust-region me The ``trust-ncg`` algorithm is a trust-region method that uses a conjugate gradient algorithm to solve the trust-region subproblem [NW]_. - -Full Hessian example: -""""""""""""""""""""" +**Full Hessian example** >>> res = minimize(rosen, x0, method='trust-ncg', ... jac=rosen_der, hess=rosen_hess, @@ -417,8 +414,7 @@ Full Hessian example: >>> res.x array([1., 1., 1., 1., 1.]) -Hessian product example: -"""""""""""""""""""""""" +**Hessian product example** >>> res = minimize(rosen, x0, method='trust-ncg', ... jac=rosen_der, hessp=rosen_hess_p, @@ -433,7 +429,7 @@ Hessian product example: array([1., 1., 1., 1., 1.]) Trust-Region Truncated Generalized Lanczos / Conjugate Gradient Algorithm (``method='trust-krylov'``) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" Similar to the ``trust-ncg`` method, the ``trust-krylov`` method is a method suitable for large-scale problems as it uses the hessian only as linear @@ -455,8 +451,7 @@ For indefinite problems it is usually better to use this method as it reduces the number of nonlinear iterations at the expense of few more matrix-vector products per subproblem solve in comparison to the ``trust-ncg`` method. -Full Hessian example: -""""""""""""""""""""" +**Full Hessian example** >>> res = minimize(rosen, x0, method='trust-krylov', ... jac=rosen_der, hess=rosen_hess, @@ -470,8 +465,7 @@ Full Hessian example: >>> res.x array([1., 1., 1., 1., 1.]) -Hessian product example: -"""""""""""""""""""""""" +**Hessian product example** >>> res = minimize(rosen, x0, method='trust-krylov', ... jac=rosen_der, hessp=rosen_hess_p, @@ -496,7 +490,7 @@ Hessian product example: Trust-Region Nearly Exact Algorithm (``method='trust-exact'``) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" All methods ``Newton-CG``, ``trust-ncg`` and ``trust-krylov`` are suitable for dealing with large-scale problems (problems with thousands of variables). That is because the conjugate @@ -537,15 +531,15 @@ example using the Rosenbrock function follows: .. _tutorial-sqlsp: -Constrained minimization of multivariate scalar functions (:func:`minimize`) ----------------------------------------------------------------------------- +Constrained minimization +^^^^^^^^^^^^^^^^^^^^^^^^ -The :func:`minimize` function provides algorithms for constrained minimization, +The :func:`minimize` function provides several algorithms for constrained minimization, namely ``'trust-constr'`` , ``'SLSQP'``, ``'COBYLA'``, and ``'COBYQA'``. They require the constraints to be defined using slightly different structures. The methods ``'trust-constr'`` and ``'COBYQA'`` require the constraints to be defined as a sequence of objects :func:`LinearConstraint` and :func:`NonlinearConstraint`. Methods ``'SLSQP'`` and ``'COBYLA'``, on the other hand, -require constraints to be defined as a sequence of dictionaries, with keys +require constraints to be defined as a sequence of dictionaries, with keys ``type``, ``fun`` and ``jac``. As an example let us consider the constrained minimization of the Rosenbrock function: @@ -566,7 +560,7 @@ for which only the first and fourth constraints are active. Trust-Region Constrained Algorithm (``method='trust-constr'``) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" The trust-region constrained method deals with constrained minimization problems of the form: @@ -585,9 +579,7 @@ The implementation is based on [EQSQP]_ for equality-constraint problems and on for problems with inequality constraints. Both are trust-region type algorithms suitable for large-scale problems. - -Defining Bounds Constraints: -"""""""""""""""""""""""""""" +**Defining Bounds Constraints** The bound constraints :math:`0 \leq x_0 \leq 1` and :math:`-0.5 \leq x_1 \leq 2.0` are defined using a :func:`Bounds` object. @@ -595,8 +587,7 @@ are defined using a :func:`Bounds` object. >>> from scipy.optimize import Bounds >>> bounds = Bounds([0, -0.5], [1.0, 2.0]) -Defining Linear Constraints: -"""""""""""""""""""""""""""" +**Defining Linear Constraints** The constraints :math:`x_0 + 2 x_1 \leq 1` and :math:`2 x_0 + x_1 = 1` can be written in the linear constraint standard format: @@ -614,8 +605,7 @@ and defined using a :func:`LinearConstraint` object. >>> from scipy.optimize import LinearConstraint >>> linear_constraint = LinearConstraint([[1, 2], [2, 1]], [-np.inf, 1], [1, 1]) -Defining Nonlinear Constraints: -""""""""""""""""""""""""""""""" +**Defining Nonlinear Constraints** The nonlinear constraint: .. math:: @@ -691,9 +681,7 @@ be provided by the user or defined using :class:`HessianUpdateStrategy`. >>> nonlinear_constraint = NonlinearConstraint(cons_f, -np.inf, 1, jac='2-point', hess=BFGS()) - -Solving the Optimization Problem: -""""""""""""""""""""""""""""""""" +**Solving the Optimization Problem** The optimization problem is solved using: >>> x0 = np.array([0.5, 0]) @@ -755,7 +743,7 @@ and the gradient with finite differences. optimization. SIAM Journal on Optimization 8.3: 682-706. Sequential Least SQuares Programming (SLSQP) Algorithm (``method='SLSQP'``) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" The SLSQP method deals with constrained minimization problems of the form: .. math:: @@ -801,6 +789,108 @@ And the optimization problem is solved with: Most of the options available for the method ``'trust-constr'`` are not available for ``'SLSQP'``. +Local minimization solver comparison +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Find a solver that meets your requirements using the table below. +If there are multiple candidates, try several and see which ones best +meet your needs (e.g. execution time, objective function value). + +.. list-table:: + :widths: 15 20 20 15 15 15 + :header-rows: 1 + + * - Solver + - Bounds Constraints + - Nonlinear Constraints + - Uses Gradient + - Uses Hessian + - Utilizes Sparsity + * - CG + - + - + - ✓ + - + - + * - BFGS + - + - + - ✓ + - + - + * - dogleg + - + - + - ✓ + - ✓ + - + * - trust-ncg + - + - + - ✓ + - ✓ + - + * - trust-krylov + - + - + - ✓ + - ✓ + - + * - trust-exact + - + - + - ✓ + - ✓ + - + * - Newton-CG + - + - + - ✓ + - ✓ + - ✓ + * - Nelder-Mead + - ✓ + - + - + - + - + * - Powell + - ✓ + - + - + - + - + * - L-BFGS-B + - ✓ + - + - ✓ + - + - + * - TNC + - ✓ + - + - ✓ + - + - + * - COBYLA + - ✓ + - ✓ + - + - + - + * - SLSQP + - ✓ + - ✓ + - ✓ + - + - + * - trust-constr + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + Global optimization ------------------- @@ -916,6 +1006,50 @@ We'll now plot all found minima on a heatmap of the function:: :alt: "This X-Y plot is a heatmap with the Z value denoted with the lowest points as black and the highest values as white. The image resembles a chess board rotated 45 degrees but heavily smoothed. A red dot is located at many of the minima on the grid resulting from the SHGO optimizer. SHGO shows the global minima as a red X in the top right. A local minima found with dual annealing is a white circle marker in the top left. A different local minima found with basinhopping is a yellow marker in the top center. The code is plotting the differential evolution result as a cyan circle, but it is not visible on the plot. At a glance it's not clear which of these valleys is the true global minima." :include-source: 0 +Comparison of Global Optimizers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Find a solver that meets your requirements using the table below. +If there are multiple candidates, try several and see which ones best +meet your needs (e.g. execution time, objective function value). + +.. list-table:: + :widths: 20 15 15 20 20 + :header-rows: 1 + + * - Solver + - Bounds Constraints + - Nonlinear Constraints + - Uses Gradient + - Uses Hessian + * - basinhopping + - + - + - (✓) + - (✓) + * - direct + - ✓ + - + - + - + * - dual_annealing + - ✓ + - + - (✓) + - (✓) + * - differential_evolution + - ✓ + - ✓ + - + - + * - shgo + - ✓ + - ✓ + - (✓) + - (✓) + +(✓) = Depending on the chosen local minimizer + Least-squares minimization (:func:`least_squares`) -------------------------------------------------- @@ -1106,6 +1240,7 @@ For example, to find the minimum of :math:`J_{1}\left( x \right)` near 5.33144184241 + Custom minimizers ----------------- From ab7d08c6148286059f6498ab5c3070268d13cbd9 Mon Sep 17 00:00:00 2001 From: h-vetinari Date: Sun, 30 Jun 2024 07:33:27 +1100 Subject: [PATCH 469/500] MAINT: fix typo in small_dynamic_array.h (#21069) on clang-19, this causes: ``` ../scipy/_lib/_uarray/small_dynamic_array.h(145,18): error: reference to non-static member function must be called 145 | size_ = copy.size; | ~~~~~^~~~ 1 error generated. ``` I'm not sure how previous versions (much less other compilers) dealt with this, as it seems that the `SmallDynamicArray` class has no `size` member or field at all. --- scipy/_lib/_uarray/small_dynamic_array.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/_lib/_uarray/small_dynamic_array.h b/scipy/_lib/_uarray/small_dynamic_array.h index b6c46d7c44fa..351b5d8fc653 100644 --- a/scipy/_lib/_uarray/small_dynamic_array.h +++ b/scipy/_lib/_uarray/small_dynamic_array.h @@ -142,7 +142,7 @@ class SmallDynamicArray { clear(); - size_ = copy.size; + size_ = copy.size_; try { allocate(); } catch (...) { From d5aa3e1a7a0c6545c62dc5374fcadb63153feaa2 Mon Sep 17 00:00:00 2001 From: AdityaKarumanchi Date: Sat, 29 Jun 2024 20:59:49 +0100 Subject: [PATCH 470/500] MAINT: lint: remove `UP031`, `UP032` ignores --- tools/lint.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/lint.toml b/tools/lint.toml index a2cb5a611681..c0568013fde8 100644 --- a/tools/lint.toml +++ b/tools/lint.toml @@ -16,10 +16,9 @@ target-version = "py39" # and `B028` which checks that warnings include the `stacklevel` keyword. # `B028` added in gh-19623. # `ICN001` added in gh-20382 to enforce common conventions for import. -# `W292` added in gh-21023 to enforce newlines at end of files +# `W292` added in gh-21023 to enforce newlines at end of files. select = ["E", "F", "PGH004", "UP", "B028", "ICN001", "W292"] -# UP031 should be enabled once someone fixes the errors. -ignore = ["E741", "UP031", "UP032"] +ignore = ["E741"] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" From d1b5af016e907e037136b7a38e485437165490f2 Mon Sep 17 00:00:00 2001 From: AdityaKarumanchi Date: Sat, 29 Jun 2024 21:00:14 +0100 Subject: [PATCH 471/500] MAINT/STY: fix `UP031`, `UP032` lint failures [skip cirrus] Co-authored-by: Lucas Colley --- benchmarks/benchmarks/optimize.py | 2 +- doc/source/conf.py | 2 +- scipy/_build_utils/_wrappers_common.py | 2 +- scipy/_lib/_docscrape.py | 17 +++--- scipy/_lib/_threadsafety.py | 2 +- scipy/_lib/decorator.py | 18 +++--- scipy/_lib/deprecation.py | 2 +- scipy/cluster/hierarchy.py | 61 +++++++++---------- scipy/cluster/vq.py | 8 +-- scipy/fft/_basic_backend.py | 4 +- scipy/fft/_pocketfft/tests/test_basic.py | 6 +- .../_pocketfft/tests/test_real_transforms.py | 6 +- scipy/fftpack/_helper.py | 4 +- scipy/fftpack/tests/gen_fftw_ref.py | 2 +- scipy/fftpack/tests/test_basic.py | 6 +- scipy/fftpack/tests/test_pseudo_diffs.py | 2 +- scipy/fftpack/tests/test_real_transforms.py | 6 +- scipy/integrate/_bvp.py | 9 ++- scipy/integrate/_ivp/bdf.py | 10 ++- scipy/integrate/_ivp/common.py | 4 +- scipy/integrate/_ivp/radau.py | 10 ++- scipy/integrate/_ode.py | 33 +++++----- scipy/integrate/_quad_vec.py | 6 +- scipy/integrate/_quadpack_py.py | 2 +- scipy/interpolate/_bsplines.py | 16 ++--- scipy/interpolate/_fitpack2.py | 30 ++++----- scipy/interpolate/_fitpack_impl.py | 4 +- scipy/interpolate/_fitpack_py.py | 8 +-- scipy/interpolate/_interpolate.py | 12 ++-- scipy/interpolate/_polyint.py | 6 +- scipy/interpolate/_rgi.py | 4 +- scipy/interpolate/tests/test_bsplines.py | 2 +- scipy/interpolate/tests/test_rbf.py | 4 +- scipy/io/_fast_matrix_market/__init__.py | 2 +- scipy/io/_fortran.py | 2 +- .../_harwell_boeing/_fortran_format_parser.py | 4 +- scipy/io/_harwell_boeing/hb.py | 38 ++++++------ scipy/io/_idl.py | 31 +++++----- scipy/io/_mmio.py | 13 ++-- scipy/io/_netcdf.py | 13 ++-- scipy/io/arff/_arffread.py | 18 +++--- scipy/io/matlab/_byteordercodes.py | 2 +- scipy/io/matlab/_mio.py | 2 +- scipy/io/matlab/_mio4.py | 17 +++--- scipy/io/matlab/_mio5.py | 2 +- scipy/io/matlab/_miobase.py | 3 +- scipy/io/matlab/tests/test_mio.py | 4 +- scipy/io/tests/test_fortran.py | 4 +- scipy/io/tests/test_mmio.py | 4 +- scipy/io/wavfile.py | 2 +- scipy/linalg/_basic.py | 4 +- scipy/linalg/_decomp_cossin.py | 4 +- scipy/linalg/_decomp_schur.py | 2 +- scipy/linalg/_expm_frechet.py | 2 +- scipy/linalg/_procrustes.py | 2 +- scipy/linalg/_solvers.py | 15 +++-- scipy/linalg/_special_matrices.py | 4 +- scipy/linalg/lapack.py | 5 +- scipy/linalg/tests/test_basic.py | 36 +++++------ scipy/odr/_odrpack.py | 16 ++--- scipy/optimize/_cobyla_py.py | 4 +- scipy/optimize/_constraints.py | 2 +- scipy/optimize/_linesearch.py | 2 +- scipy/optimize/_linprog_simplex.py | 12 ++-- scipy/optimize/_lsq/common.py | 4 +- scipy/optimize/_lsq/least_squares.py | 11 ++-- scipy/optimize/_minimize.py | 18 +++--- scipy/optimize/_minpack_py.py | 32 +++++----- scipy/optimize/_nonlin.py | 9 ++- scipy/optimize/_numdiff.py | 2 +- scipy/optimize/_optimize.py | 16 ++--- scipy/optimize/_root.py | 4 +- scipy/optimize/_root_scalar.py | 16 ++--- scipy/optimize/_shgo.py | 4 +- scipy/optimize/_slsqp_py.py | 6 +- scipy/optimize/_trustregion.py | 2 +- .../minimize_trustregion_constr.py | 12 ++-- scipy/optimize/_zeros_py.py | 24 ++++---- .../tests/test__differential_evolution.py | 2 +- scipy/optimize/tests/test_zeros.py | 2 +- scipy/signal/_filter_design.py | 30 ++++----- scipy/signal/_fir_filter_design.py | 8 +-- scipy/signal/_lti_conversion.py | 2 +- scipy/signal/_signaltools.py | 9 ++- scipy/signal/_spectral_py.py | 10 +-- scipy/signal/_waveforms.py | 13 ++-- scipy/signal/tests/test_filter_design.py | 6 +- scipy/signal/tests/test_peak_finding.py | 2 +- scipy/signal/tests/test_signaltools.py | 2 +- scipy/signal/tests/test_splines.py | 2 +- scipy/signal/tests/test_upfirdn.py | 2 +- scipy/signal/windows/_windows.py | 4 +- scipy/sparse/_bsr.py | 6 +- scipy/sparse/_coo.py | 4 +- scipy/sparse/_dia.py | 4 +- scipy/sparse/_generate_sparsetools.py | 2 +- scipy/sparse/_sputils.py | 14 ++--- scipy/sparse/linalg/_eigen/arpack/arpack.py | 15 ++--- .../linalg/_eigen/arpack/tests/test_arpack.py | 22 +++---- scipy/sparse/linalg/_eigen/tests/test_svds.py | 2 +- scipy/sparse/linalg/_expm_multiply.py | 11 ++-- scipy/sparse/linalg/_interface.py | 8 +-- scipy/sparse/linalg/_isolve/lgmres.py | 2 +- scipy/sparse/linalg/_isolve/lsmr.py | 4 +- scipy/spatial/_kdtree.py | 2 +- scipy/spatial/distance.py | 33 +++++----- scipy/spatial/tests/test_distance.py | 2 +- scipy/spatial/tests/test_spherical_voronoi.py | 4 +- scipy/spatial/transform/_rotation_spline.py | 6 +- .../spatial/transform/tests/test_rotation.py | 2 +- scipy/special/_generate_pyx.py | 46 +++++++------- scipy/special/_mptestutils.py | 2 +- scipy/special/_testutils.py | 8 +-- scipy/special/_ufuncs.pyi | 6 +- scipy/special/tests/test_kolmogorov.py | 2 +- scipy/special/utils/convert.py | 4 +- scipy/special/utils/makenpz.py | 2 +- scipy/stats/_binned_statistic.py | 4 +- scipy/stats/_continuous_distns.py | 4 +- scipy/stats/_distn_infrastructure.py | 8 +-- scipy/stats/_kde.py | 7 +-- scipy/stats/_morestats.py | 2 +- scipy/stats/_mstats_basic.py | 15 +++-- scipy/stats/_multivariate.py | 27 ++++---- scipy/stats/tests/common_tests.py | 4 +- scipy/stats/tests/test_discrete_distns.py | 2 +- scipy/stats/tests/test_distributions.py | 2 +- scipy/stats/tests/test_fit.py | 10 +-- 128 files changed, 548 insertions(+), 585 deletions(-) diff --git a/benchmarks/benchmarks/optimize.py b/benchmarks/benchmarks/optimize.py index 4084e562dc19..2b3c5ccd4ba7 100644 --- a/benchmarks/benchmarks/optimize.py +++ b/benchmarks/benchmarks/optimize.py @@ -88,7 +88,7 @@ def print_results(self): return print("") print("=========================================================") - print("Optimizer benchmark: %s" % (self.function_name)) + print(f"Optimizer benchmark: {self.function_name}") print("dimensions: %d, extra kwargs: %s" % (results[0].ndim, str(self.minimizer_kwargs))) print("averaged over %d starting configurations" % (results[0].ntrials)) diff --git a/doc/source/conf.py b/doc/source/conf.py index ba383f566ec7..e517a789d0a4 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -73,7 +73,7 @@ # General substitutions. project = 'SciPy' -copyright = '2008-%s, The SciPy community' % date.today().year +copyright = f'2008-{date.today().year}, The SciPy community' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. diff --git a/scipy/_build_utils/_wrappers_common.py b/scipy/_build_utils/_wrappers_common.py index 97f2d874844d..60694f716ba1 100644 --- a/scipy/_build_utils/_wrappers_common.py +++ b/scipy/_build_utils/_wrappers_common.py @@ -103,7 +103,7 @@ def newer(dst, src): both exist and 'dst' is the same age or younger than 'src'. """ if not os.path.exists(dst): - raise ValueError("file '%s' does not exist" % os.path.abspath(dst)) + raise ValueError(f"file '{os.path.abspath(dst)}' does not exist") if not os.path.exists(src): return 1 diff --git a/scipy/_lib/_docscrape.py b/scipy/_lib/_docscrape.py index f5ad8058366f..114ab28a9f42 100644 --- a/scipy/_lib/_docscrape.py +++ b/scipy/_lib/_docscrape.py @@ -155,7 +155,7 @@ def __getitem__(self, key): def __setitem__(self, key, val): if key not in self._parsed_data: - self._error_location("Unknown section %s" % key, error=False) + self._error_location(f"Unknown section {key}", error=False) else: self._parsed_data[key] = val @@ -288,7 +288,7 @@ def parse_item_name(text): """Match ':role:`name`' or 'name'.""" m = self._func_rgx.match(text) if not m: - raise ParseError("%s is not a item name" % text) + raise ParseError(f"{text} is not a item name") role = m.group('role') name = m.group('name') if role else m.group('name2') return name, role, m.end() @@ -324,7 +324,7 @@ def parse_item_name(text): rest = list(filter(None, [description])) items.append((funcs, rest)) else: - raise ParseError("%s is not a item name" % line) + raise ParseError(f"{line} is not a item name") return items def _parse_index(self, section, content): @@ -390,8 +390,7 @@ def _parse(self): section = (s.capitalize() for s in section.split(' ')) section = ' '.join(section) if self.get(section): - self._error_location("The section %s appears twice" - % section) + self._error_location(f"The section {section} appears twice") if section in ('Parameters', 'Other Parameters', 'Attributes', 'Methods'): @@ -489,7 +488,7 @@ def _str_see_also(self, func_role): elif func_role: link = f':{func_role}:`{func}`' else: - link = "`%s`_" % func + link = f"`{func}`_" links.append(link) link = ', '.join(links) out += [link] @@ -512,7 +511,7 @@ def _str_index(self): default_index = idx.get('default', '') if default_index: output_index = True - out += ['.. index:: %s' % default_index] + out += [f'.. index:: {default_index}'] for section, references in idx.items(): if section == 'default': continue @@ -587,7 +586,7 @@ def __str__(self): if self._role: if self._role not in roles: - print("Warning: invalid role %s" % self._role) + print(f"Warning: invalid role {self._role}") out += '.. {}:: {}\n \n\n'.format(roles.get(self._role, ''), func_name) @@ -602,7 +601,7 @@ class ClassDoc(NumpyDocString): def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc, config={}): if not inspect.isclass(cls) and cls is not None: - raise ValueError("Expected a class or None, but got %r" % cls) + raise ValueError(f"Expected a class or None, but got {repr(cls)}") self._cls = cls if 'sphinx' in sys.modules: diff --git a/scipy/_lib/_threadsafety.py b/scipy/_lib/_threadsafety.py index feea0c592390..530339ec7075 100644 --- a/scipy/_lib/_threadsafety.py +++ b/scipy/_lib/_threadsafety.py @@ -52,7 +52,7 @@ def non_reentrant(err_msg=None): def decorator(func): msg = err_msg if msg is None: - msg = "%s is not re-entrant" % func.__name__ + msg = f"{func.__name__} is not re-entrant" lock = ReentrancyLock(msg) return lock.decorate(func) return decorator diff --git a/scipy/_lib/decorator.py b/scipy/_lib/decorator.py index 02121774d3c2..8c4ab90e3d52 100644 --- a/scipy/_lib/decorator.py +++ b/scipy/_lib/decorator.py @@ -98,7 +98,7 @@ def __init__(self, func=None, name=None, signature=None, elif self.kwonlyargs: allargs.append('*') # single star syntax for a in self.kwonlyargs: - allargs.append('%s=None' % a) + allargs.append(f'{a}=None') allshortargs.append(f'{a}={a}') if self.varkw: allargs.append('**' + self.varkw) @@ -122,7 +122,7 @@ def __init__(self, func=None, name=None, signature=None, # check existence required attributes assert hasattr(self, 'name') if not hasattr(self, 'signature'): - raise TypeError('You are decorating a non-function: %s' % func) + raise TypeError(f'You are decorating a non-function: {func}') def update(self, func, **kw): "Update the signature of func with the data in self" @@ -147,7 +147,7 @@ def make(self, src_templ, evaldict=None, addsource=False, **attrs): evaldict = evaldict or {} mo = DEF.match(src) if mo is None: - raise SyntaxError('not a valid function template\n%s' % src) + raise SyntaxError(f'not a valid function template\n{src}') name = mo.group(1) # extract the function name names = set([name] + [arg.strip(' *') for arg in self.shortsignature.split(',')]) @@ -238,7 +238,7 @@ def decorator(caller, _func=None): evaldict['_call_'] = caller evaldict['_decorate_'] = decorate return FunctionMaker.create( - '%s(func)' % name, 'return _decorate_(func, _call_)', + f'{name}(func)', 'return _decorate_(func, _call_)', evaldict, doc=doc, module=caller.__module__, __wrapped__=caller) @@ -301,13 +301,13 @@ def dispatch_on(*dispatch_args): dispatching on the given arguments. """ assert dispatch_args, 'No dispatch args passed' - dispatch_str = '(%s,)' % ', '.join(dispatch_args) + dispatch_str = f"({', '.join(dispatch_args)},)" def check(arguments, wrong=operator.ne, msg=''): """Make sure one passes the expected number of arguments""" if wrong(len(arguments), len(dispatch_args)): - raise TypeError('Expected %d arguments, got %d%s' % - (len(dispatch_args), len(arguments), msg)) + raise TypeError(f'Expected {len(dispatch_args)} arguments, ' + 'got {len(arguments)}{msg}') def gen_func_dec(func): """Decorator turning a function into a generic function""" @@ -315,7 +315,7 @@ def gen_func_dec(func): # first check the dispatch arguments argset = set(getfullargspec(func).args) if not set(dispatch_args) <= argset: - raise NameError('Unknown dispatch arguments %s' % dispatch_str) + raise NameError(f'Unknown dispatch arguments {dispatch_str}') typemap = {} @@ -390,7 +390,7 @@ def _dispatch(dispatch_args, *args, **kw): return func(*args, **kw) return FunctionMaker.create( - func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str, + func, f'return _f_({dispatch_str}, %%(shortsignature)s)', dict(_f_=_dispatch), register=register, default=func, typemap=typemap, vancestors=vancestors, ancestors=ancestors, dispatch_info=dispatch_info, __wrapped__=func) diff --git a/scipy/_lib/deprecation.py b/scipy/_lib/deprecation.py index 0823c78c579a..5802324afcda 100644 --- a/scipy/_lib/deprecation.py +++ b/scipy/_lib/deprecation.py @@ -150,7 +150,7 @@ def deprecate_cython_api(module, routine_name, new_name=None, message=None): old_name = f"{module.__name__}.{routine_name}" if new_name is None: - depdoc = "`%s` is deprecated!" % old_name + depdoc = f"`{old_name}` is deprecated!" else: depdoc = f"`{old_name}` is deprecated, use `{new_name}` instead!" diff --git a/scipy/cluster/hierarchy.py b/scipy/cluster/hierarchy.py index 321e8873d453..f100d654fd40 100644 --- a/scipy/cluster/hierarchy.py +++ b/scipy/cluster/hierarchy.py @@ -157,7 +157,7 @@ class ClusterWarning(UserWarning): def _warning(s): - warnings.warn('scipy.cluster: %s' % s, ClusterWarning, stacklevel=3) + warnings.warn(f'scipy.cluster: {s}', ClusterWarning, stacklevel=3) def int_floor(arr, xp): @@ -2107,29 +2107,29 @@ def is_valid_im(R, warning=False, throw=False, name=None): xp = array_namespace(R) R = _asarray(R, order='c', xp=xp) valid = True - name_str = "%r " % name if name else '' + name_str = f"{name!r} " if name else '' try: if R.dtype != xp.float64: - raise TypeError('Inconsistency matrix %smust contain doubles ' - '(double).' % name_str) + raise TypeError(f'Inconsistency matrix {name_str}must contain doubles ' + '(double).') if len(R.shape) != 2: - raise ValueError('Inconsistency matrix %smust have shape=2 (i.e. ' - 'be two-dimensional).' % name_str) + raise ValueError(f'Inconsistency matrix {name_str}must have shape=2 (i.e. ' + 'be two-dimensional).') if R.shape[1] != 4: - raise ValueError('Inconsistency matrix %smust have 4 columns.' % - name_str) + raise ValueError(f'Inconsistency matrix {name_str}' + 'must have 4 columns.') if R.shape[0] < 1: - raise ValueError('Inconsistency matrix %smust have at least one ' - 'row.' % name_str) + raise ValueError(f'Inconsistency matrix {name_str}' + 'must have at least one row.') if xp.any(R[:, 0] < 0): - raise ValueError('Inconsistency matrix %scontains negative link ' - 'height means.' % name_str) + raise ValueError(f'Inconsistency matrix {name_str}' + 'contains negative link height means.') if xp.any(R[:, 1] < 0): - raise ValueError('Inconsistency matrix %scontains negative link ' - 'height standard deviations.' % name_str) + raise ValueError(f'Inconsistency matrix {name_str}' + 'contains negative link height standard deviations.') if xp.any(R[:, 2] < 0): - raise ValueError('Inconsistency matrix %scontains negative link ' - 'counts.' % name_str) + raise ValueError(f'Inconsistency matrix {name_str}' + 'contains negative link counts.') except Exception as e: if throw: raise @@ -2224,35 +2224,31 @@ def is_valid_linkage(Z, warning=False, throw=False, name=None): xp = array_namespace(Z) Z = _asarray(Z, order='c', xp=xp) valid = True - name_str = "%r " % name if name else '' + name_str = f"{name!r} " if name else '' try: if Z.dtype != xp.float64: - raise TypeError('Linkage matrix %smust contain doubles.' % name_str) + raise TypeError(f'Linkage matrix {name_str}must contain doubles.') if len(Z.shape) != 2: - raise ValueError('Linkage matrix %smust have shape=2 (i.e. be ' - 'two-dimensional).' % name_str) + raise ValueError(f'Linkage matrix {name_str}must have shape=2 (i.e. be' + ' two-dimensional).') if Z.shape[1] != 4: - raise ValueError('Linkage matrix %smust have 4 columns.' % name_str) + raise ValueError(f'Linkage matrix {name_str}must have 4 columns.') if Z.shape[0] == 0: raise ValueError('Linkage must be computed on at least two ' 'observations.') n = Z.shape[0] if n > 1: if (xp.any(Z[:, 0] < 0) or xp.any(Z[:, 1] < 0)): - raise ValueError('Linkage %scontains negative indices.' % - name_str) + raise ValueError(f'Linkage {name_str}contains negative indices.') if xp.any(Z[:, 2] < 0): - raise ValueError('Linkage %scontains negative distances.' % - name_str) + raise ValueError(f'Linkage {name_str}contains negative distances.') if xp.any(Z[:, 3] < 0): - raise ValueError('Linkage %scontains negative counts.' % - name_str) + raise ValueError(f'Linkage {name_str}contains negative counts.') if _check_hierarchy_uses_cluster_before_formed(Z): - raise ValueError('Linkage %suses non-singleton cluster before ' - 'it is formed.' % name_str) + raise ValueError(f'Linkage {name_str}uses non-singleton cluster before' + ' it is formed.') if _check_hierarchy_uses_cluster_more_than_once(Z): - raise ValueError('Linkage %suses the same cluster more than once.' - % name_str) + raise ValueError(f'Linkage {name_str}uses the same cluster more than once.') except Exception as e: if throw: raise @@ -2575,8 +2571,7 @@ def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None): elif criterion == 'maxclust_monocrit': _hierarchy.cluster_maxclust_monocrit(Z, monocrit, T, int(n), int(t)) else: - raise ValueError('Invalid cluster formation criterion: %s' - % str(criterion)) + raise ValueError(f'Invalid cluster formation criterion: {str(criterion)}') return xp.asarray(T) diff --git a/scipy/cluster/vq.py b/scipy/cluster/vq.py index f4b5179ce524..15e1ef58f79d 100644 --- a/scipy/cluster/vq.py +++ b/scipy/cluster/vq.py @@ -469,13 +469,12 @@ def kmeans(obs, k_or_guess, iter=20, thresh=1e-5, check_finite=True, obs = _asarray(obs, xp=xp, check_finite=check_finite) guess = _asarray(k_or_guess, xp=xp, check_finite=check_finite) if iter < 1: - raise ValueError("iter must be at least 1, got %s" % iter) + raise ValueError(f"iter must be at least 1, got {iter}") # Determine whether a count (scalar) or an initial guess (array) was passed. if size(guess) != 1: if size(guess) < 1: - raise ValueError("Asked for 0 clusters. Initial book was %s" % - guess) + raise ValueError(f"Asked for 0 clusters. Initial book was {guess}") return _kmeans(obs, guess, thresh=thresh, xp=xp) # k_or_guess is a scalar, now verify that it's an integer @@ -772,8 +771,7 @@ def kmeans2(data, k, iter=10, thresh=1e-5, minit='random', """ if int(iter) < 1: - raise ValueError("Invalid iter (%s), " - "must be a positive integer." % iter) + raise ValueError(f"Invalid iter ({iter}), must be a positive integer.") try: miss_meth = _valid_miss_meth[missing] except KeyError as e: diff --git a/scipy/fft/_basic_backend.py b/scipy/fft/_basic_backend.py index 14b77661482d..9857665de73f 100644 --- a/scipy/fft/_basic_backend.py +++ b/scipy/fft/_basic_backend.py @@ -142,8 +142,8 @@ def _swap_direction(norm): elif norm == 'forward': norm = 'backward' elif norm != 'ortho': - raise ValueError('Invalid norm value %s; should be "backward", ' - '"ortho", or "forward".' % norm) + raise ValueError(f'Invalid norm value {norm}; should be "backward", ' + '"ortho", or "forward".') return norm diff --git a/scipy/fft/_pocketfft/tests/test_basic.py b/scipy/fft/_pocketfft/tests/test_basic.py index 8960cace3e08..4e0918fabc83 100644 --- a/scipy/fft/_pocketfft/tests/test_basic.py +++ b/scipy/fft/_pocketfft/tests/test_basic.py @@ -870,10 +870,10 @@ def _check(self, x, routine, fftsize, axis, overwrite_x, should_overwrite): for fake in [lambda x: x, FakeArray, FakeArray2]: routine(fake(x2), fftsize, axis, overwrite_x=overwrite_x) - sig = "{}({}{!r}, {!r}, axis={!r}, overwrite_x={!r})".format( - routine.__name__, x.dtype, x.shape, fftsize, axis, overwrite_x) + sig = (f"{routine.__name__}({x.dtype}{x.shape!r}, {fftsize!r}, " + f"axis={axis!r}, overwrite_x={overwrite_x!r})") if not should_overwrite: - assert_equal(x2, x, err_msg="spurious overwrite in %s" % sig) + assert_equal(x2, x, err_msg=f"spurious overwrite in {sig}") def _check_1d(self, routine, dtype, shape, axis, overwritable_dtypes, fftsize, overwrite_x): diff --git a/scipy/fft/_pocketfft/tests/test_real_transforms.py b/scipy/fft/_pocketfft/tests/test_real_transforms.py index 2cb47f40c6bc..6f16ce08bd37 100644 --- a/scipy/fft/_pocketfft/tests/test_real_transforms.py +++ b/scipy/fft/_pocketfft/tests/test_real_transforms.py @@ -412,10 +412,10 @@ def test_overwrite(routine, dtype, shape, axis, type, norm, overwrite_x): x2 = x.copy() routine(x2, type, None, axis, norm, overwrite_x=overwrite_x) - sig = "{}({}{!r}, {!r}, axis={!r}, overwrite_x={!r})".format( - routine.__name__, x.dtype, x.shape, None, axis, overwrite_x) + sig = (f"{routine.__name__}({x.dtype}{x.shape!r}, {None!r}, axis={axis!r}, " + f"overwrite_x={overwrite_x!r})") if not overwrite_x: - assert_equal(x2, x, err_msg="spurious overwrite in %s" % sig) + assert_equal(x2, x, err_msg=f"spurious overwrite in {sig}") class Test_DCTN_IDCTN: diff --git a/scipy/fftpack/_helper.py b/scipy/fftpack/_helper.py index ee0dd7b0f8d6..789254373290 100644 --- a/scipy/fftpack/_helper.py +++ b/scipy/fftpack/_helper.py @@ -45,8 +45,8 @@ def rfftfreq(n, d=1.0): """ n = operator.index(n) if n < 0: - raise ValueError("n = %s is not valid. " - "n must be a nonnegative integer." % n) + raise ValueError(f"n = {n} is not valid. " + "n must be a nonnegative integer.") return (np.arange(1, n + 1, dtype=int) // 2) / float(n * d) diff --git a/scipy/fftpack/tests/gen_fftw_ref.py b/scipy/fftpack/tests/gen_fftw_ref.py index f14379054bc6..287879cfbeac 100644 --- a/scipy/fftpack/tests/gen_fftw_ref.py +++ b/scipy/fftpack/tests/gen_fftw_ref.py @@ -15,7 +15,7 @@ def gen_data(dt): elif dt == np.float32: pg = './fftw_single' else: - raise ValueError("unknown: %s" % dt) + raise ValueError(f"unknown: {dt}") # Generate test data using FFTW for reference for type in [1, 2, 3, 4, 5, 6, 7, 8]: arrays[type] = {} diff --git a/scipy/fftpack/tests/test_basic.py b/scipy/fftpack/tests/test_basic.py index a7c4b1de867f..0df14fb9b579 100644 --- a/scipy/fftpack/tests/test_basic.py +++ b/scipy/fftpack/tests/test_basic.py @@ -759,10 +759,10 @@ def _check(self, x, routine, fftsize, axis, overwrite_x): for fake in [lambda x: x, FakeArray, FakeArray2]: routine(fake(x2), fftsize, axis, overwrite_x=overwrite_x) - sig = "{}({}{!r}, {!r}, axis={!r}, overwrite_x={!r})".format( - routine.__name__, x.dtype, x.shape, fftsize, axis, overwrite_x) + sig = (f"{routine.__name__}({x.dtype}{x.shape!r}, {fftsize!r}, " + f"axis={axis!r}, overwrite_x={overwrite_x!r})") if not overwrite_x: - assert_equal(x2, x, err_msg="spurious overwrite in %s" % sig) + assert_equal(x2, x, err_msg=f"spurious overwrite in {sig}") def _check_1d(self, routine, dtype, shape, axis, overwritable_dtypes, fftsize, overwrite_x): diff --git a/scipy/fftpack/tests/test_pseudo_diffs.py b/scipy/fftpack/tests/test_pseudo_diffs.py index cec131caced4..9563f7ddbcba 100644 --- a/scipy/fftpack/tests/test_pseudo_diffs.py +++ b/scipy/fftpack/tests/test_pseudo_diffs.py @@ -332,7 +332,7 @@ def _check(self, x, routine, *args, **kwargs): sig += repr(args) if kwargs: sig += repr(kwargs) - assert_equal(x2, x, err_msg="spurious overwrite in %s" % sig) + assert_equal(x2, x, err_msg=f"spurious overwrite in {sig}") def _check_1d(self, routine, dtype, shape, *args, **kwargs): np.random.seed(1234) diff --git a/scipy/fftpack/tests/test_real_transforms.py b/scipy/fftpack/tests/test_real_transforms.py index 6108d460c786..d3e969df3fcf 100644 --- a/scipy/fftpack/tests/test_real_transforms.py +++ b/scipy/fftpack/tests/test_real_transforms.py @@ -695,10 +695,10 @@ def _check(self, x, routine, type, fftsize, axis, norm, overwrite_x, **kw): x2 = x.copy() routine(x2, type, fftsize, axis, norm, overwrite_x=overwrite_x) - sig = "{}({}{!r}, {!r}, axis={!r}, overwrite_x={!r})".format( - routine.__name__, x.dtype, x.shape, fftsize, axis, overwrite_x) + sig = (f"{routine.__name__}({x.dtype}{x.shape!r}, {fftsize!r}, " + f"axis={axis!r}, overwrite_x={overwrite_x!r})") if not overwrite_x: - assert_equal(x2, x, err_msg="spurious overwrite in %s" % sig) + assert_equal(x2, x, err_msg=f"spurious overwrite in {sig}") def _check_1d(self, routine, dtype, shape, axis): np.random.seed(1234) diff --git a/scipy/integrate/_bvp.py b/scipy/integrate/_bvp.py index f988fdd6e052..e76e6586f26a 100644 --- a/scipy/integrate/_bvp.py +++ b/scipy/integrate/_bvp.py @@ -500,15 +500,14 @@ def solve_newton(n, m, h, col_fun, bc, jac, y, p, B, bvp_tol, bc_tol): def print_iteration_header(): - print("{:^15}{:^15}{:^15}{:^15}{:^15}".format( - "Iteration", "Max residual", "Max BC residual", "Total nodes", - "Nodes added")) + print(f"{'Iteration':^15}{'Max residual':^15}{'Max BC residual':^15}" + f"{'Total nodes':^15}{'Nodes added':^15}") def print_iteration_progress(iteration, residual, bc_residual, total_nodes, nodes_added): - print("{:^15}{:^15.2e}{:^15.2e}{:^15}{:^15}".format( - iteration, residual, bc_residual, total_nodes, nodes_added)) + print(f"{iteration:^15}{residual:^15.2e}{bc_residual:^15.2e}" + f"{total_nodes:^15}{nodes_added:^15}") class BVPResult(OptimizeResult): diff --git a/scipy/integrate/_ivp/bdf.py b/scipy/integrate/_ivp/bdf.py index 29bd94615192..33b47a642b97 100644 --- a/scipy/integrate/_ivp/bdf.py +++ b/scipy/integrate/_ivp/bdf.py @@ -290,9 +290,8 @@ def jac_wrapped(t, y): return np.asarray(jac(t, y), dtype=y0.dtype) if J.shape != (self.n, self.n): - raise ValueError("`jac` is expected to have shape {}, but " - "actually has {}." - .format((self.n, self.n), J.shape)) + raise ValueError(f"`jac` is expected to have shape {(self.n, self.n)}," + f" but actually has {J.shape}.") else: if issparse(jac): J = csc_matrix(jac, dtype=y0.dtype) @@ -300,9 +299,8 @@ def jac_wrapped(t, y): J = np.asarray(jac, dtype=y0.dtype) if J.shape != (self.n, self.n): - raise ValueError("`jac` is expected to have shape {}, but " - "actually has {}." - .format((self.n, self.n), J.shape)) + raise ValueError(f"`jac` is expected to have shape {(self.n, self.n)}," + f" but actually has {J.shape}.") jac_wrapped = None return jac_wrapped, J diff --git a/scipy/integrate/_ivp/common.py b/scipy/integrate/_ivp/common.py index 4ff0b7056a0e..82f54bc0637b 100644 --- a/scipy/integrate/_ivp/common.py +++ b/scipy/integrate/_ivp/common.py @@ -36,8 +36,8 @@ def warn_extraneous(extraneous): Extraneous keyword arguments """ if extraneous: - warn("The following arguments have no effect for a chosen solver: {}." - .format(", ".join(f"`{x}`" for x in extraneous)), + warn("The following arguments have no effect for a chosen solver: " + f"{', '.join(f'`{x}`' for x in extraneous)}.", stacklevel=3) diff --git a/scipy/integrate/_ivp/radau.py b/scipy/integrate/_ivp/radau.py index e13cb0f14c3c..0d572b48de51 100644 --- a/scipy/integrate/_ivp/radau.py +++ b/scipy/integrate/_ivp/radau.py @@ -381,9 +381,8 @@ def jac_wrapped(t, y, _=None): return np.asarray(jac(t, y), dtype=float) if J.shape != (self.n, self.n): - raise ValueError("`jac` is expected to have shape {}, but " - "actually has {}." - .format((self.n, self.n), J.shape)) + raise ValueError(f"`jac` is expected to have shape {(self.n, self.n)}," + f" but actually has {J.shape}.") else: if issparse(jac): J = csc_matrix(jac) @@ -391,9 +390,8 @@ def jac_wrapped(t, y, _=None): J = np.asarray(jac, dtype=float) if J.shape != (self.n, self.n): - raise ValueError("`jac` is expected to have shape {}, but " - "actually has {}." - .format((self.n, self.n), J.shape)) + raise ValueError(f"`jac` is expected to have shape {(self.n, self.n)}," + f" but actually has {J.shape}.") jac_wrapped = None return jac_wrapped, J diff --git a/scipy/integrate/_ode.py b/scipy/integrate/_ode.py index 794a4dc63721..2d52e093ef2d 100644 --- a/scipy/integrate/_ode.py +++ b/scipy/integrate/_ode.py @@ -765,10 +765,9 @@ class IntegratorConcurrencyError(RuntimeError): """ def __init__(self, name): - msg = ("Integrator `%s` can be used to solve only a single problem " - "at a time. If you want to integrate multiple problems, " - "consider using a different integrator " - "(see `ode.set_integrator`)") % name + msg = (f"Integrator `{name}` can be used to solve only a single problem " + "at a time. If you want to integrate multiple problems, " + "consider using a different integrator (see `ode.set_integrator`)") RuntimeError.__init__(self, msg) @@ -809,13 +808,13 @@ def run(self, f, jac, y0, t0, t1, f_params, jac_params): def step(self, f, jac, y0, t0, t1, f_params, jac_params): """Make one integration step and return (y1,t1).""" - raise NotImplementedError('%s does not support step() method' % - self.__class__.__name__) + raise NotImplementedError(f'{self.__class__.__name__} ' + 'does not support step() method') def run_relax(self, f, jac, y0, t0, t1, f_params, jac_params): """Integrate from t=t0 to t>=t1 and return (y1,t).""" - raise NotImplementedError('%s does not support run_relax() method' % - self.__class__.__name__) + raise NotImplementedError(f'{self.__class__.__name__} ' + 'does not support run_relax() method') # XXX: __str__ method for getting visual state of the integrator @@ -867,7 +866,7 @@ def __init__(self, elif re.match(method, r'bdf', re.I): self.meth = 2 else: - raise ValueError('Unknown integration method %s' % method) + raise ValueError(f'Unknown integration method {method}') self.with_jacobian = with_jacobian self.rtol = rtol self.atol = atol @@ -962,7 +961,7 @@ def reset(self, n, has_jac): elif mf in [24, 25]: lrw = 22 + 11 * n + (3 * self.ml + 2 * self.mu) * n else: - raise ValueError('Unexpected mf=%s' % mf) + raise ValueError(f'Unexpected mf={mf}') if mf % 10 in [0, 3]: liw = 30 @@ -1009,8 +1008,8 @@ def run(self, f, jac, y0, t0, t1, f_params, jac_params): self.istate = istate if istate < 0: unexpected_istate_msg = f'Unexpected istate={istate:d}' - warnings.warn('{:s}: {:s}'.format(self.__class__.__name__, - self.messages.get(istate, unexpected_istate_msg)), + warnings.warn(f'{self.__class__.__name__:s}: ' + f'{self.messages.get(istate, unexpected_istate_msg):s}', stacklevel=2) self.success = 0 else: @@ -1178,8 +1177,8 @@ def run(self, f, jac, y0, t0, t1, f_params, jac_params): self.istate = istate if istate < 0: unexpected_istate_msg = f'Unexpected istate={istate:d}' - warnings.warn('{:s}: {:s}'.format(self.__class__.__name__, - self.messages.get(istate, unexpected_istate_msg)), + warnings.warn(f'{self.__class__.__name__:s}: ' + f'{self.messages.get(istate, unexpected_istate_msg):s}', stacklevel=2) self.success = 0 return y, x @@ -1312,7 +1311,7 @@ def reset(self, n, has_jac): elif jt in [4, 5]: lrs = 22 + (self.max_order_s + 5 + 2 * self.ml + self.mu) * n else: - raise ValueError('Unexpected jt=%s' % jt) + raise ValueError(f'Unexpected jt={jt}') lrw = max(lrn, lrs) liw = 20 + n rwork = zeros((lrw,), float) @@ -1348,8 +1347,8 @@ def run(self, f, jac, y0, t0, t1, f_params, jac_params): self.istate = istate if istate < 0: unexpected_istate_msg = f'Unexpected istate={istate:d}' - warnings.warn('{:s}: {:s}'.format(self.__class__.__name__, - self.messages.get(istate, unexpected_istate_msg)), + warnings.warn(f'{self.__class__.__name__:s}: ' + f'{self.messages.get(istate, unexpected_istate_msg):s}', stacklevel=2) self.success = 0 else: diff --git a/scipy/integrate/_quad_vec.py b/scipy/integrate/_quad_vec.py index 74f2ba44cb1f..758bac513877 100644 --- a/scipy/integrate/_quad_vec.py +++ b/scipy/integrate/_quad_vec.py @@ -98,8 +98,10 @@ def __init__(self, **kwargs): self.__dict__.update(**kwargs) def __repr__(self): - return "_Bunch({})".format(", ".join(f"{k}={repr(self.__dict__[k])}" - for k in self.__keys)) + key_value_pairs = ', '.join( + f'{k}={repr(self.__dict__[k])}' for k in self.__keys + ) + return f"_Bunch({key_value_pairs})" def quad_vec(f, a, b, epsabs=1e-200, epsrel=1e-8, norm='2', cache_size=100e6, diff --git a/scipy/integrate/_quadpack_py.py b/scipy/integrate/_quadpack_py.py index af7ed047c0c5..0d273f6d2c99 100644 --- a/scipy/integrate/_quadpack_py.py +++ b/scipy/integrate/_quadpack_py.py @@ -623,7 +623,7 @@ def _quad(func,a,b,args,full_output,epsabs,epsrel,limit,points): def _quad_weight(func, a, b, args, full_output, epsabs, epsrel, limlst, limit, maxp1,weight, wvar, wopts): if weight not in ['cos','sin','alg','alg-loga','alg-logb','alg-log','cauchy']: - raise ValueError("%s not a recognized weighting function." % weight) + raise ValueError(f"{weight} not a recognized weighting function.") strdict = {'cos':1,'sin':2,'alg':1,'alg-loga':2,'alg-logb':3,'alg-log':4} diff --git a/scipy/interpolate/_bsplines.py b/scipy/interpolate/_bsplines.py index e7ccb65d1a6e..ae7aa2071dfe 100644 --- a/scipy/interpolate/_bsplines.py +++ b/scipy/interpolate/_bsplines.py @@ -802,8 +802,8 @@ def from_power_basis(cls, pp, bc_type='not-a-knot'): """ from ._cubic import CubicSpline if not isinstance(pp, CubicSpline): - raise NotImplementedError("Only CubicSpline objects are accepted " - "for now. Got %s instead." % type(pp)) + raise NotImplementedError(f"Only CubicSpline objects are accepted " + f"for now. Got {type(pp)} instead.") x = pp.x coef = pp.c k = pp.c.shape[0] - 1 @@ -816,7 +816,7 @@ def from_power_basis(cls, pp, bc_type='not-a-knot'): elif bc_type == 'periodic': t = _periodic_knots(x, k) else: - raise TypeError('Unknown boundary condition: %s' % bc_type) + raise TypeError(f'Unknown boundary condition: {bc_type}') nod = t.shape[0] - (n + k + 1) # number of derivatives at the ends c = np.zeros(n + nod, dtype=pp.c.dtype) @@ -929,7 +929,7 @@ def _not_a_knot(x, k): cf de Boor, XIII(12).""" x = np.asarray(x) if k % 2 != 1: - raise ValueError("Odd degree for now only. Got %s." % k) + raise ValueError(f"Odd degree for now only. Got {k}.") m = (k - 1) // 2 t = x[m+1:-m-1] @@ -949,7 +949,7 @@ def _convert_string_aliases(deriv, target_shape): elif deriv == "natural": deriv = [(2, np.zeros(target_shape))] else: - raise ValueError("Unknown boundary condition : %s" % deriv) + raise ValueError(f"Unknown boundary condition : {deriv}") return deriv @@ -1364,7 +1364,7 @@ def make_interp_spline(x, y, k=3, t=None, bc_type=None, axis=0, try: deriv_l, deriv_r = bc_type except TypeError as e: - raise ValueError("Unknown boundary condition: %s" % bc_type) from e + raise ValueError(f"Unknown boundary condition: {bc_type}") from e y = np.asarray(y) @@ -1439,7 +1439,7 @@ def make_interp_spline(x, y, k=3, t=None, bc_type=None, axis=0, raise ValueError('Got %d knots, need at least %d.' % (t.size, x.size + k + 1)) if (x[0] < t[k]) or (x[-1] > t[-k]): - raise ValueError('Out of bounds w/ x = %s.' % x) + raise ValueError(f'Out of bounds w/ x = {x}.') if bc_type == 'periodic': return _make_periodic_spline(x, y, t, k, axis) @@ -1640,7 +1640,7 @@ def make_lsq_spline(x, y, t, k=3, w=None, axis=0, check_finite=True): if x.size != y.shape[0]: raise ValueError(f'Shapes of x {x.shape} and y {y.shape} are incompatible') if k > 0 and np.any((x < t[k]) | (x > t[-k])): - raise ValueError('Out of bounds w/ x = %s.' % x) + raise ValueError(f'Out of bounds w/ x = {x}.') if x.size != w.size: raise ValueError(f'Shapes of x {x.shape} and w {w.shape} are incompatible') diff --git a/scipy/interpolate/_fitpack2.py b/scipy/interpolate/_fitpack2.py index 98d49f90c31f..307207189c05 100644 --- a/scipy/interpolate/_fitpack2.py +++ b/scipy/interpolate/_fitpack2.py @@ -272,7 +272,7 @@ def validate_input(x, y, w, bbox, k, s, ext, check_finite): try: ext = _extrap_modes[ext] except KeyError as e: - raise ValueError("Unknown extrapolation mode %s." % ext) from e + raise ValueError(f"Unknown extrapolation mode {ext}.") from e return x, y, w, bbox, ext @@ -309,7 +309,7 @@ def _reset_class(self): # error if ier == 1: self._set_class(LSQUnivariateSpline) - message = _curfit_messages.get(ier, 'ier=%s' % (ier)) + message = _curfit_messages.get(ier, f'ier={ier}') warnings.warn(message, stacklevel=3) def _set_class(self, cls): @@ -392,7 +392,7 @@ def __call__(self, x, nu=0, ext=None): try: ext = _extrap_modes[ext] except KeyError as e: - raise ValueError("Unknown extrapolation mode %s." % ext) from e + raise ValueError(f"Unknown extrapolation mode {ext}.") from e return _fitpack_impl.splev(x, self._eval_args, der=nu, ext=ext) def get_knots(self): @@ -1034,11 +1034,11 @@ def __call__(self, x, y, dx=0, dy=0, grid=True): if dx or dy: z, ier = dfitpack.parder(tx, ty, c, kx, ky, dx, dy, x, y) if not ier == 0: - raise ValueError("Error code returned by parder: %s" % ier) + raise ValueError(f"Error code returned by parder: {ier}") else: z, ier = dfitpack.bispev(tx, ty, c, kx, ky, x, y) if not ier == 0: - raise ValueError("Error code returned by bispev: %s" % ier) + raise ValueError(f"Error code returned by bispev: {ier}") else: # standard Numpy broadcasting if x.shape != y.shape: @@ -1054,11 +1054,11 @@ def __call__(self, x, y, dx=0, dy=0, grid=True): if dx or dy: z, ier = dfitpack.pardeu(tx, ty, c, kx, ky, dx, dy, x, y) if not ier == 0: - raise ValueError("Error code returned by pardeu: %s" % ier) + raise ValueError(f"Error code returned by pardeu: {ier}") else: z, ier = dfitpack.bispeu(tx, ty, c, kx, ky, x, y) if not ier == 0: - raise ValueError("Error code returned by bispeu: %s" % ier) + raise ValueError(f"Error code returned by bispeu: {ier}") z = z.reshape(shape) return z @@ -1314,10 +1314,10 @@ class _DerivedBivariateSpline(_BivariateSplineBase): @property def fp(self): - raise AttributeError("attribute \"fp\" %s" % self._invalid_why) + raise AttributeError(f"attribute \"fp\" {self._invalid_why}") def get_residual(self): - raise AttributeError("method \"get_residual\" %s" % self._invalid_why) + raise AttributeError(f"method \"get_residual\" {self._invalid_why}") class SmoothBivariateSpline(BivariateSpline): @@ -1416,7 +1416,7 @@ def __init__(self, x, y, z, w=None, bbox=[None] * 4, kx=3, ky=3, s=None, if ier in [0, -1, -2]: # normal return pass else: - message = _surfit_messages.get(ier, 'ier=%s' % (ier)) + message = _surfit_messages.get(ier, f'ier={ier}') warnings.warn(message, stacklevel=2) self.fp = fp @@ -1516,7 +1516,7 @@ def __init__(self, x, y, z, tx, ty, w=None, bbox=[None]*4, kx=3, ky=3, deficiency = (nx-kx-1)*(ny-ky-1)+ier message = _surfit_messages.get(-3) % (deficiency) else: - message = _surfit_messages.get(ier, 'ier=%s' % (ier)) + message = _surfit_messages.get(ier, f'ier={ier}') warnings.warn(message, stacklevel=2) self.fp = fp self.tck = tx1[:nx], ty1[:ny], c @@ -1603,7 +1603,7 @@ def __init__(self, x, y, z, bbox=[None] * 4, kx=3, ky=3, s=0): ye, kx, ky, s) if ier not in [0, -1, -2]: - msg = _surfit_messages.get(ier, 'ier=%s' % (ier)) + msg = _surfit_messages.get(ier, f'ier={ier}') raise ValueError(msg) self.fp = fp @@ -1924,7 +1924,7 @@ def __init__(self, theta, phi, r, w=None, s=0., eps=1E-16): r, w=w, s=s, eps=eps) if ier not in [0, -1, -2]: - message = _spherefit_messages.get(ier, 'ier=%s' % (ier)) + message = _spherefit_messages.get(ier, f'ier={ier}') raise ValueError(message) self.fp = fp @@ -2078,7 +2078,7 @@ def __init__(self, theta, phi, r, tt, tp, w=None, eps=1E-16): tt_, tp_, c, fp, ier = dfitpack.spherfit_lsq(theta, phi, r, tt_, tp_, w=w, eps=eps) if ier > 0: - message = _spherefit_messages.get(ier, 'ier=%s' % (ier)) + message = _spherefit_messages.get(ier, f'ier={ier}') raise ValueError(message) self.fp = fp @@ -2345,7 +2345,7 @@ def __init__(self, u, v, r, s=0., pole_continuity=False, pole_values=None, r0, r1, s) if ier not in [0, -1, -2]: - msg = _spfit_messages.get(ier, 'ier=%s' % (ier)) + msg = _spfit_messages.get(ier, f'ier={ier}') raise ValueError(msg) self.fp = fp diff --git a/scipy/interpolate/_fitpack_impl.py b/scipy/interpolate/_fitpack_impl.py index 307758b46b8e..a00ca101b591 100644 --- a/scipy/interpolate/_fitpack_impl.py +++ b/scipy/interpolate/_fitpack_impl.py @@ -314,7 +314,7 @@ def splev(x, tck, der=0, ext=0): if not (0 <= der <= k): raise ValueError("0<=der=%d<=k=%d must hold" % (der, k)) if ext not in (0, 1, 2, 3): - raise ValueError("ext = %s not in (0, 1, 2, 3) " % ext) + raise ValueError(f"ext = {ext} not in (0, 1, 2, 3) ") x = asarray(x) shape = x.shape @@ -368,7 +368,7 @@ def sproot(tck, mest=10): sproot([t, c, k], mest), c)) else: if len(t) < 8: - raise TypeError("The number of knots %d>=8" % len(t)) + raise TypeError(f"The number of knots {len(t)}>=8") z, m, ier = dfitpack.sproot(t, c, mest) if ier == 10: raise TypeError("Invalid input data. " diff --git a/scipy/interpolate/_fitpack_py.py b/scipy/interpolate/_fitpack_py.py index 3ca3258f3a21..7fb127d937e1 100644 --- a/scipy/interpolate/_fitpack_py.py +++ b/scipy/interpolate/_fitpack_py.py @@ -373,8 +373,8 @@ def splev(x, tck, der=0, ext=0): try: extrapolate = {0: True, }[ext] except KeyError as e: - raise ValueError("Extrapolation mode %s is not supported " - "by BSpline." % ext) from e + raise ValueError(f"Extrapolation mode {ext} is not supported " + "by BSpline.") from e return tck(x, der, extrapolate=extrapolate) else: @@ -438,8 +438,8 @@ def splint(a, b, tck, full_output=0): raise ValueError(mesg) if full_output != 0: - mesg = ("full_output = %s is not supported. Proceeding as if " - "full_output = 0" % full_output) + mesg = (f"full_output = {full_output} is not supported. Proceeding as if " + "full_output = 0") return tck.integrate(a, b, extrapolate=False) else: diff --git a/scipy/interpolate/_interpolate.py b/scipy/interpolate/_interpolate.py index 617d070156cd..3435a922ecd4 100644 --- a/scipy/interpolate/_interpolate.py +++ b/scipy/interpolate/_interpolate.py @@ -283,8 +283,8 @@ def __init__(self, x, y, kind='linear', axis=-1, kind = 'spline' elif kind not in ('linear', 'nearest', 'nearest-up', 'previous', 'next'): - raise NotImplementedError("%s is unsupported: Use fitpack " - "routines for other types." % kind) + raise NotImplementedError(f"{kind} is unsupported: Use fitpack " + "routines for other types.") x = array(x, copy=self.copy) y = array(y, copy=self.copy) @@ -1232,8 +1232,8 @@ def from_bernstein_basis(cls, bp, extrapolate=None): If 'periodic', periodic extrapolation is used. Default is True. """ if not isinstance(bp, BPoly): - raise TypeError(".from_bernstein_basis only accepts BPoly instances. " - "Got %s instead." % type(bp)) + raise TypeError(f".from_bernstein_basis only accepts BPoly instances. " + f"Got {type(bp)} instead.") dx = np.diff(bp.x) k = bp.c.shape[0] - 1 # polynomial order @@ -1541,8 +1541,8 @@ def from_power_basis(cls, pp, extrapolate=None): If 'periodic', periodic extrapolation is used. Default is True. """ if not isinstance(pp, PPoly): - raise TypeError(".from_power_basis only accepts PPoly instances. " - "Got %s instead." % type(pp)) + raise TypeError(f".from_power_basis only accepts PPoly instances. " + f"Got {type(pp)} instead.") dx = np.diff(pp.x) k = pp.c.shape[0] - 1 # polynomial order diff --git a/scipy/interpolate/_polyint.py b/scipy/interpolate/_polyint.py index 6ed06d8abdba..757ba67db15a 100644 --- a/scipy/interpolate/_polyint.py +++ b/scipy/interpolate/_polyint.py @@ -107,9 +107,9 @@ def _finish_y(self, y, x_shape): def _reshape_yi(self, yi, check=False): yi = np.moveaxis(np.asarray(yi), self._y_axis, 0) if check and yi.shape[1:] != self._y_extra_shape: - ok_shape = "{!r} + (N,) + {!r}".format(self._y_extra_shape[-self._y_axis:], - self._y_extra_shape[:-self._y_axis]) - raise ValueError("Data must be of shape %s" % ok_shape) + ok_shape = (f"{self._y_extra_shape[-self._y_axis:]!r} + (N,) + " + f"{self._y_extra_shape[:-self._y_axis]!r}") + raise ValueError(f"Data must be of shape {ok_shape}") return yi.reshape((yi.shape[0], -1)) def _set_yi(self, yi, xi=None, axis=None): diff --git a/scipy/interpolate/_rgi.py b/scipy/interpolate/_rgi.py index afdb8ff399ed..4a161b6092c9 100644 --- a/scipy/interpolate/_rgi.py +++ b/scipy/interpolate/_rgi.py @@ -267,7 +267,7 @@ class RegularGridInterpolator: def __init__(self, points, values, method="linear", bounds_error=True, fill_value=np.nan, *, solver=None, solver_args=None): if method not in self._ALL_METHODS: - raise ValueError("Method '%s' is not defined" % method) + raise ValueError(f"Method '{method}' is not defined") elif method in self._SPLINE_METHODS: self._validate_grid_dimensions(points, method) self.method = method @@ -390,7 +390,7 @@ def __call__(self, xi, method=None, *, nu=None): method = self.method if method is None else method is_method_changed = self.method != method if method not in self._ALL_METHODS: - raise ValueError("Method '%s' is not defined" % method) + raise ValueError(f"Method '{method}' is not defined") if is_method_changed and method in self._SPLINE_METHODS_ndbspl: self._spline = self._construct_spline(method) diff --git a/scipy/interpolate/tests/test_bsplines.py b/scipy/interpolate/tests/test_bsplines.py index a57125e610b4..2d1e39e285a5 100644 --- a/scipy/interpolate/tests/test_bsplines.py +++ b/scipy/interpolate/tests/test_bsplines.py @@ -844,7 +844,7 @@ def B_0123(x, der=0): lambda x: -2., lambda x: 1.] else: - raise ValueError('never be here: der=%s' % der) + raise ValueError(f'never be here: der={der}') pieces = np.piecewise(x, conds, funcs) return pieces diff --git a/scipy/interpolate/tests/test_rbf.py b/scipy/interpolate/tests/test_rbf.py index 418042c65a90..e0a734919091 100644 --- a/scipy/interpolate/tests/test_rbf.py +++ b/scipy/interpolate/tests/test_rbf.py @@ -105,7 +105,7 @@ def check_rbf1d_regularity(function, atol): rbf = Rbf(x, y, function=function) xi = linspace(0, 10, 100) yi = rbf(xi) - msg = "abs-diff: %f" % abs(yi - sin(xi)).max() + msg = f"abs-diff: {abs(yi - sin(xi)).max():f}" assert_(allclose(yi, sin(xi), atol=atol), msg) @@ -133,7 +133,7 @@ def check_2drbf1d_regularity(function, atol): rbf = Rbf(x, y, function=function, mode='N-D') xi = linspace(0, 10, 100) yi = rbf(xi) - msg = "abs-diff: %f" % abs(yi - np.vstack([sin(xi), cos(xi)]).T).max() + msg = f"abs-diff: {abs(yi - np.vstack([sin(xi), cos(xi)]).T).max():f}" assert_(allclose(yi, np.vstack([sin(xi), cos(xi)]).T, atol=atol), msg) diff --git a/scipy/io/_fast_matrix_market/__init__.py b/scipy/io/_fast_matrix_market/__init__.py index 182179d2e75c..0b24fda89950 100644 --- a/scipy/io/_fast_matrix_market/__init__.py +++ b/scipy/io/_fast_matrix_market/__init__.py @@ -530,7 +530,7 @@ def mmwrite(target, a, comment=None, field=None, precision=None, symmetry="AUTO" _fmm_core.write_body_coo(cursor, a.shape, a.row, a.col, data) else: - raise ValueError("unknown matrix type: %s" % type(a)) + raise ValueError(f"unknown matrix type: {type(a)}") def mminfo(source): diff --git a/scipy/io/_fortran.py b/scipy/io/_fortran.py index a4c93c57bcda..ac491dce68fe 100644 --- a/scipy/io/_fortran.py +++ b/scipy/io/_fortran.py @@ -120,7 +120,7 @@ def __init__(self, filename, mode='r', header_dtype=np.uint32): if hasattr(filename, 'seek'): self._fp = filename else: - self._fp = open(filename, '%sb' % mode) + self._fp = open(filename, f'{mode}b') self._header_dtype = header_dtype diff --git a/scipy/io/_harwell_boeing/_fortran_format_parser.py b/scipy/io/_harwell_boeing/_fortran_format_parser.py index c5ab77c1bfbf..81adb13c0768 100644 --- a/scipy/io/_harwell_boeing/_fortran_format_parser.py +++ b/scipy/io/_harwell_boeing/_fortran_format_parser.py @@ -263,7 +263,7 @@ def _parse_format(self, tokens): "%d (got '%s')" % (0, tokens[0].value)) elif not tokens[-1].type == "RPAR": raise SyntaxError("Expected right parenthesis at position " - "%d (got '%s')" % (len(tokens), tokens[-1].value)) + f"{len(tokens)} (got '{tokens[-1].value}')") tokens = tokens[1:-1] types = [t.type for t in tokens] @@ -299,7 +299,7 @@ def _parse_format(self, tokens): min = None return ExpFormat(width, significand, min, repeat) else: - raise SyntaxError("Invalid formatter type %s" % next.value) + raise SyntaxError(f"Invalid formatter type {next.value}") def _next(self, tokens, tp): if not len(tokens) > 0: diff --git a/scipy/io/_harwell_boeing/hb.py b/scipy/io/_harwell_boeing/hb.py index 534e0a4b7db6..7b2c5294c587 100644 --- a/scipy/io/_harwell_boeing/hb.py +++ b/scipy/io/_harwell_boeing/hb.py @@ -96,8 +96,8 @@ def from_data(cls, m, title="Default title", key="0", mxtype=None, fmt=None): elif values.dtype.kind in np.typecodes["AllFloat"]: tp = "real" else: - raise NotImplementedError("type %s for values not implemented" - % values.dtype) + raise NotImplementedError( + f"type {values.dtype} for values not implemented") mxtype = HBMatrixType(tp, "unsymmetric", "assembled") else: raise ValueError("mxtype argument not handled yet.") @@ -138,7 +138,7 @@ def from_file(cls, fid): line = fid.readline().strip("\n") if not len(line) > 72: raise ValueError("Expected at least 72 characters for first line, " - "got: \n%s" % line) + f"got: \n{line}") title = line[:72] key = line[72:] @@ -146,7 +146,7 @@ def from_file(cls, fid): line = fid.readline().strip("\n") if not len(line.rstrip()) >= 56: raise ValueError("Expected at least 56 characters for second line, " - "got: \n%s" % line) + f"got: \n{line}") total_nlines = _expect_int(line[:14]) pointer_nlines = _expect_int(line[14:28]) indices_nlines = _expect_int(line[28:42]) @@ -164,8 +164,8 @@ def from_file(cls, fid): # Third line line = fid.readline().strip("\n") if not len(line) >= 70: - raise ValueError("Expected at least 72 character for third line, got:\n" - "%s" % line) + raise ValueError(f"Expected at least 72 character for third line, " + f"got:\n{line}") mxtype_s = line[:3].upper() if not len(mxtype_s) == 3: @@ -174,15 +174,15 @@ def from_file(cls, fid): mxtype = HBMatrixType.from_fortran(mxtype_s) if mxtype.value_type not in ["real", "integer"]: raise ValueError("Only real or integer matrices supported for " - "now (detected %s)" % mxtype) + f"now (detected {mxtype})") if not mxtype.structure == "unsymmetric": raise ValueError("Only unsymmetric matrices supported for " - "now (detected %s)" % mxtype) + f"now (detected {mxtype})") if not mxtype.storage == "assembled": raise ValueError("Only assembled matrices supported for now") if not line[3:14] == " " * 11: - raise ValueError("Malformed data for third line: %s" % line) + raise ValueError(f"Malformed data for third line: {line}") nrows = _expect_int(line[14:28]) ncols = _expect_int(line[28:42]) @@ -197,7 +197,7 @@ def from_file(cls, fid): ct = line.split() if not len(ct) == 3: - raise ValueError("Expected 3 formats, got %s" % ct) + raise ValueError(f"Expected 3 formats, got {ct}") return cls(title, key, total_nlines, pointer_nlines, indices_nlines, values_nlines, @@ -219,7 +219,7 @@ def __init__(self, title, key, if key is None: key = "|No Key" if len(key) > 8: - warnings.warn("key is > 8 characters (key is %s)" % key, + warnings.warn(f"key is > 8 characters (key is {key})", LineOverflow, stacklevel=3) self.title = title self.key = key @@ -232,13 +232,13 @@ def __init__(self, title, key, parser = FortranFormatParser() pointer_format = parser.parse(pointer_format_str) if not isinstance(pointer_format, IntFormat): - raise ValueError("Expected int format for pointer format, got %s" - % pointer_format) + raise ValueError("Expected int format for pointer format, got " + f"{pointer_format}") indices_format = parser.parse(indices_format_str) if not isinstance(indices_format, IntFormat): - raise ValueError("Expected int format for indices format, got %s" % - indices_format) + raise ValueError("Expected int format for indices format, got " + f"{indices_format}") values_format = parser.parse(values_format_str) if isinstance(values_format, ExpFormat): @@ -395,7 +395,7 @@ def from_fortran(cls, fmt): storage = cls._f2q_storage[fmt[2]] return cls(value_type, structure, storage) except KeyError as e: - raise ValueError("Unrecognized format %s" % fmt) from e + raise ValueError(f"Unrecognized format {fmt}") from e def __init__(self, value_type, structure, storage="assembled"): self.value_type = value_type @@ -403,11 +403,11 @@ def __init__(self, value_type, structure, storage="assembled"): self.storage = storage if value_type not in self._q2f_type: - raise ValueError("Unrecognized type %s" % value_type) + raise ValueError(f"Unrecognized type {value_type}") if structure not in self._q2f_structure: - raise ValueError("Unrecognized structure %s" % structure) + raise ValueError(f"Unrecognized structure {structure}") if storage not in self._q2f_storage: - raise ValueError("Unrecognized storage %s" % storage) + raise ValueError(f"Unrecognized storage {storage}") @property def fortran_format(self): diff --git a/scipy/io/_idl.py b/scipy/io/_idl.py index 6d4e95718788..e440c3732211 100644 --- a/scipy/io/_idl.py +++ b/scipy/io/_idl.py @@ -756,7 +756,7 @@ def readsav(file_name, idict=None, python_dict=False, # Read the signature, which should be 'SR' signature = _read_bytes(f, 2) if signature != b'SR': - raise Exception("Invalid SIGNATURE: %s" % signature) + raise Exception(f"Invalid SIGNATURE: {signature}") # Next, the record format, which is '\x00\x04' for normal .sav # files, and '\x00\x06' for compressed .sav files. @@ -776,7 +776,7 @@ def readsav(file_name, idict=None, python_dict=False, fout = tempfile.NamedTemporaryFile(suffix='.sav') if verbose: - print(" -> expanding to %s" % fout.name) + print(f" -> expanding to {fout.name}") # Write header fout.write(b'SR\x00\x04') @@ -828,7 +828,7 @@ def readsav(file_name, idict=None, python_dict=False, f.seek(4) else: - raise Exception("Invalid RECFMT: %s" % recfmt) + raise Exception(f"Invalid RECFMT: {recfmt}") # Loop through records, and add them to the list while True: @@ -861,40 +861,39 @@ def readsav(file_name, idict=None, python_dict=False, for record in records: if record['rectype'] == "TIMESTAMP": print("-"*50) - print("Date: %s" % record['date']) - print("User: %s" % record['user']) - print("Host: %s" % record['host']) + print(f"Date: {record['date']}") + print(f"User: {record['user']}") + print(f"Host: {record['host']}") break # Print out version info about the file for record in records: if record['rectype'] == "VERSION": print("-"*50) - print("Format: %s" % record['format']) - print("Architecture: %s" % record['arch']) - print("Operating System: %s" % record['os']) - print("IDL Version: %s" % record['release']) + print(f"Format: {record['format']}") + print(f"Architecture: {record['arch']}") + print(f"Operating System: {record['os']}") + print(f"IDL Version: {record['release']}") break # Print out identification info about the file for record in records: if record['rectype'] == "IDENTIFICATON": print("-"*50) - print("Author: %s" % record['author']) - print("Title: %s" % record['title']) - print("ID Code: %s" % record['idcode']) + print(f"Author: {record['author']}") + print(f"Title: {record['title']}") + print(f"ID Code: {record['idcode']}") break # Print out descriptions saved with the file for record in records: if record['rectype'] == "DESCRIPTION": print("-"*50) - print("Description: %s" % record['description']) + print(f"Description: {record['description']}") break print("-"*50) - print("Successfully read %i records of which:" % - (len(records))) + print(f"Successfully read {len(records)} records of which:") # Create convenience list of record types rectypes = [r['rectype'] for r in records] diff --git a/scipy/io/_mmio.py b/scipy/io/_mmio.py index d39e03551b14..706e658be1a1 100644 --- a/scipy/io/_mmio.py +++ b/scipy/io/_mmio.py @@ -632,9 +632,8 @@ def _init_attrs(self, **kwargs): invalid_keys = set(kwargs.keys()) - set(public_attrs) if invalid_keys: - raise ValueError('''found {} invalid keyword arguments, please only - use {}'''.format(tuple(invalid_keys), - public_attrs)) + raise ValueError(f"found {tuple(invalid_keys)} invalid keyword " + f"arguments, please only use {public_attrs}") for attr in attrs: setattr(self, attr, kwargs.get(attr[1:], None)) @@ -809,7 +808,7 @@ def _write(self, stream, a, comment='', field=None, precision=None, else: if not issparse(a): - raise ValueError('unknown matrix type: %s' % type(a)) + raise ValueError(f'unknown matrix type: {type(a)}') rep = 'coordinate' rows, cols = a.shape @@ -851,7 +850,7 @@ def _write(self, stream, a, comment='', field=None, precision=None, # write comments for line in comment.split('\n'): - data = '%%%s\n' % (line) + data = f'%{line}\n' stream.write(data.encode('latin1')) template = self._field_template(field, precision) @@ -900,7 +899,7 @@ def _write(self, stream, a, comment='', field=None, precision=None, raise ValueError('pattern type inconsisted with dense format') else: - raise TypeError('Unknown field type %s' % field) + raise TypeError(f'Unknown field type {field}') # write sparse format else: @@ -934,7 +933,7 @@ def _write(self, stream, a, comment='', field=None, precision=None, data = ("%i %i " % (r, c)) + (template % (d.real, d.imag)) stream.write(data.encode('latin1')) else: - raise TypeError('Unknown field type %s' % field) + raise TypeError(f'Unknown field type {field}') def _is_fromfile_compatible(stream): diff --git a/scipy/io/_netcdf.py b/scipy/io/_netcdf.py index 4b8b576b8612..3f4bddd0126f 100644 --- a/scipy/io/_netcdf.py +++ b/scipy/io/_netcdf.py @@ -244,7 +244,7 @@ def __init__(self, filename, mode='r', mmap=None, version=1, else: # maybe it's a string self.filename = filename omode = 'r+' if mode == 'a' else mode - self.fp = open(self.filename, '%sb' % omode) + self.fp = open(self.filename, f'{omode}b') if mmap is None: # Mmapped files on PyPy cannot be usually closed # before the GC runs, so it's better to use mmap=False @@ -385,7 +385,7 @@ def createVariable(self, name, type, dimensions): type = dtype(type) typecode, size = type.char, type.itemsize if (typecode, size) not in REVERSE: - raise ValueError("NetCDF 3 does not support type %s" % type) + raise ValueError(f"NetCDF 3 does not support type {type}") # convert to big endian always for NetCDF 3 data = empty(shape_, dtype=type.newbyteorder("B")) @@ -576,7 +576,7 @@ def _write_att_values(self, values): break typecode, size = TYPEMAP[nc_type] - dtype_ = '>%s' % typecode + dtype_ = f'>{typecode}' # asarray() dies with bytes and '>c' in py3k. Change to 'S' dtype_ = 'S' if dtype_ == '>c' else dtype_ @@ -601,8 +601,7 @@ def _read(self): # Check magic bytes and version magic = self.fp.read(3) if not magic == b'CDF': - raise TypeError("Error: %s is not a valid NetCDF 3 file" % - self.filename) + raise TypeError(f"Error: {self.filename} is not a valid NetCDF 3 file") self.__dict__['version_byte'] = frombuffer(self.fp.read(1), '>b')[0] # Read file headers and set data. @@ -750,7 +749,7 @@ def _read_var(self): begin = [self._unpack_int, self._unpack_int64][self.version_byte-1]() typecode, size = TYPEMAP[nc_type] - dtype_ = '>%s' % typecode + dtype_ = f'>{typecode}' return name, dimensions, shape, attributes, typecode, size, dtype_, begin, vsize @@ -765,7 +764,7 @@ def _read_att_values(self): self.fp.read(-count % 4) # read padding if typecode != 'c': - values = frombuffer(values, dtype='>%s' % typecode).copy() + values = frombuffer(values, dtype=f'>{typecode}').copy() if values.shape == (1,): values = values[0] else: diff --git a/scipy/io/arff/_arffread.py b/scipy/io/arff/_arffread.py index 67b754d169a6..4559487c3e03 100644 --- a/scipy/io/arff/_arffread.py +++ b/scipy/io/arff/_arffread.py @@ -330,7 +330,7 @@ def parse_data(self, data_str): else: dt = datetime.datetime.strptime(date_str, self.date_format) return np.datetime64(dt).astype( - "datetime64[%s]" % self.datetime_unit) + f"datetime64[{self.datetime_unit}]") def __str__(self): return super().__str__() + ',' + self.date_format @@ -396,7 +396,7 @@ def to_attribute(name, attr_string): if attr is not None: return attr - raise ParseArffError("unknown attribute %s" % attr_string) + raise ParseArffError(f"unknown attribute {attr_string}") def csv_sniffer_has_bug_last_field(): @@ -558,7 +558,7 @@ def tokenize_attribute(iterable, attribute): # weka. raise ValueError("multi line not supported yet") else: - raise ValueError("First line unparsable: %s" % sattr) + raise ValueError(f"First line unparsable: {sattr}") attribute = to_attribute(name, type) @@ -580,7 +580,7 @@ def tokenize_single_comma(val): except IndexError as e: raise ValueError("Error while tokenizing attribute") from e else: - raise ValueError("Error while tokenizing single %s" % val) + raise ValueError(f"Error while tokenizing single {val}") return name, type @@ -595,7 +595,7 @@ def tokenize_single_wcomma(val): except IndexError as e: raise ValueError("Error while tokenizing attribute") from e else: - raise ValueError("Error while tokenizing single %s" % val) + raise ValueError(f"Error while tokenizing single {val}") return name, type @@ -613,7 +613,7 @@ def read_relational_attribute(ofile, relational_attribute, i): attr, i = tokenize_attribute(ofile, i) relational_attribute.attributes.append(attr) else: - raise ValueError("Error parsing line %s" % i) + raise ValueError(f"Error parsing line {i}") else: i = next(ofile) @@ -644,7 +644,7 @@ def read_header(ofile): if isrel: relation = isrel.group(1) else: - raise ValueError("Error parsing line %s" % i) + raise ValueError(f"Error parsing line {i}") i = next(ofile) else: i = next(ofile) @@ -687,11 +687,11 @@ def __init__(self, rel, attr): def __repr__(self): msg = "" - msg += "Dataset: %s\n" % self.name + msg += f"Dataset: {self.name}\n" for i in self._attributes: msg += f"\t{i}'s type is {self._attributes[i].type_name}" if self._attributes[i].range: - msg += ", range is %s" % str(self._attributes[i].range) + msg += f", range is {str(self._attributes[i].range)}" msg += '\n' return msg diff --git a/scipy/io/matlab/_byteordercodes.py b/scipy/io/matlab/_byteordercodes.py index f9c02a0e0130..21d35e4b6568 100644 --- a/scipy/io/matlab/_byteordercodes.py +++ b/scipy/io/matlab/_byteordercodes.py @@ -72,4 +72,4 @@ def to_numpy_code(code): return swapped_code else: raise ValueError( - 'We cannot handle byte order %s' % code) + f'We cannot handle byte order {code}') diff --git a/scipy/io/matlab/_mio.py b/scipy/io/matlab/_mio.py index fb52c38fe69f..052e1e5deca4 100644 --- a/scipy/io/matlab/_mio.py +++ b/scipy/io/matlab/_mio.py @@ -80,7 +80,7 @@ def mat_reader_factory(file_name, appendmat=True, **kwargs): raise NotImplementedError('Please use HDF reader for matlab v7.3 ' 'files, e.g. h5py') else: - raise TypeError('Did not recognize version %s' % mjv) + raise TypeError(f'Did not recognize version {mjv}') @docfiller diff --git a/scipy/io/matlab/_mio4.py b/scipy/io/matlab/_mio4.py index 399ecd10d5fe..b4d62f317d87 100644 --- a/scipy/io/matlab/_mio4.py +++ b/scipy/io/matlab/_mio4.py @@ -118,8 +118,8 @@ def read_header(self): raise ValueError('Mat 4 mopt wrong format, byteswapping problem?') M, rest = divmod(data['mopt'], 1000) # order code if M not in (0, 1): - warnings.warn("We do not support byte ordering '%s'; returned " - "data may be corrupt" % order_codes[M], + warnings.warn(f"We do not support byte ordering '{order_codes[M]}';" + " returned data may be corrupt", UserWarning, stacklevel=3) O, rest = divmod(rest, 100) # unused, should be 0 if O != 0: @@ -148,7 +148,7 @@ def array_from_header(self, hdr, process=True): # no current processing (below) makes sense for sparse return self.read_sparse_array(hdr) else: - raise TypeError('No reader for class code %s' % mclass) + raise TypeError(f'No reader for class code {mclass}') if process and self.squeeze_me: return squeeze_element(arr) return arr @@ -177,10 +177,11 @@ def read_sub_array(self, hdr, copy=True): num_bytes *= d buffer = self.mat_stream.read(int(num_bytes)) if len(buffer) != num_bytes: - raise ValueError("Not enough bytes to read matrix '%s'; is this " - "a badly-formed file? Consider listing matrices " - "with `whosmat` and loading named matrices with " - "`variable_names` kwarg to `loadmat`" % hdr.name) + raise ValueError(f"Not enough bytes to read matrix '{hdr.name}';" + "is this a badly-formed file? " + "Consider listing matrices with `whosmat` " + "and loading named matrices with " + "`variable_names` kwarg to `loadmat`") arr = np.ndarray(shape=dims, dtype=dt, buffer=buffer, @@ -299,7 +300,7 @@ def shape_from_header(self, hdr): shape = (int(rows), int(cols)) else: - raise TypeError('No reader for class code %s' % mclass) + raise TypeError(f'No reader for class code {mclass}') if self.squeeze_me: shape = tuple([x for x in shape if x != 1]) diff --git a/scipy/io/matlab/_mio5.py b/scipy/io/matlab/_mio5.py index bcd78db198a8..3788e7501732 100644 --- a/scipy/io/matlab/_mio5.py +++ b/scipy/io/matlab/_mio5.py @@ -335,7 +335,7 @@ def get_variables(self, variable_names=None): warnings.warn( f'Unreadable variable "{name}", because "{err}"', Warning, stacklevel=2) - res = "Read error: %s" % err + res = f"Read error: {err}" self.mat_stream.seek(next_position) mdict[name] = res if hdr.is_global: diff --git a/scipy/io/matlab/_miobase.py b/scipy/io/matlab/_miobase.py index 05425d235cbd..45f7deeaf4e1 100644 --- a/scipy/io/matlab/_miobase.py +++ b/scipy/io/matlab/_miobase.py @@ -319,8 +319,7 @@ def matdims(arr, oned_as='column'): elif oned_as == 'row': return (1,) + shape else: - raise ValueError('1-D option "%s" is strange' - % oned_as) + raise ValueError(f'1-D option "{oned_as}" is strange') return shape diff --git a/scipy/io/matlab/tests/test_mio.py b/scipy/io/matlab/tests/test_mio.py index cd3ee2fb35c3..7c6a36108f0e 100644 --- a/scipy/io/matlab/tests/test_mio.py +++ b/scipy/io/matlab/tests/test_mio.py @@ -299,7 +299,7 @@ def _load_check_case(name, files, case): label = f"test {name}; file {file_name}" for k, expected in case.items(): k_label = f"{label}, variable {k}" - assert_(k in matdict, "Missing key at %s" % k_label) + assert_(k in matdict, f"Missing key at {k_label}") _check_level(k_label, expected, matdict[k]) @@ -1273,7 +1273,7 @@ def test_simplify_cells(): (1, '8*_*', None), ]) def test_matfile_version(version, filt, regex): - use_filt = pjoin(test_data_path, 'test*%s.mat' % filt) + use_filt = pjoin(test_data_path, f'test*{filt}.mat') files = glob(use_filt) if regex is not None: files = [file for file in files if re.match(regex, file) is not None] diff --git a/scipy/io/tests/test_fortran.py b/scipy/io/tests/test_fortran.py index 905140764411..d4b93ecccb59 100644 --- a/scipy/io/tests/test_fortran.py +++ b/scipy/io/tests/test_fortran.py @@ -23,7 +23,7 @@ def test_fortranfiles_read(): for filename in iglob(path.join(DATA_PATH, "fortran-*-*x*x*.dat")): m = re.search(r'fortran-([^-]+)-(\d+)x(\d+)x(\d+).dat', filename, re.I) if not m: - raise RuntimeError("Couldn't match %s filename to regex" % filename) + raise RuntimeError(f"Couldn't match {filename} filename to regex") dims = (int(m.group(2)), int(m.group(3)), int(m.group(4))) @@ -52,7 +52,7 @@ def test_fortranfiles_write(): for filename in iglob(path.join(DATA_PATH, "fortran-*-*x*x*.dat")): m = re.search(r'fortran-([^-]+)-(\d+)x(\d+)x(\d+).dat', filename, re.I) if not m: - raise RuntimeError("Couldn't match %s filename to regex" % filename) + raise RuntimeError(f"Couldn't match {filename} filename to regex") dims = (int(m.group(2)), int(m.group(3)), int(m.group(4))) dtype = m.group(1).replace('s', '<') diff --git a/scipy/io/tests/test_mmio.py b/scipy/io/tests/test_mmio.py index f8b5ccbb2b81..7f37977a08c7 100644 --- a/scipy/io/tests/test_mmio.py +++ b/scipy/io/tests/test_mmio.py @@ -645,7 +645,7 @@ def test_bzip2_py3(self): mmwrite(self.fn, b) - fn_bzip2 = "%s.bz2" % self.fn + fn_bzip2 = f"{self.fn}.bz2" with open(self.fn, 'rb') as f_in: f_out = bz2.BZ2File(fn_bzip2, 'wb') f_out.write(f_in.read()) @@ -669,7 +669,7 @@ def test_gzip_py3(self): mmwrite(self.fn, b) - fn_gzip = "%s.gz" % self.fn + fn_gzip = f"{self.fn}.gz" with open(self.fn, 'rb') as f_in: f_out = gzip.open(fn_gzip, 'wb') f_out.write(f_in.read()) diff --git a/scipy/io/wavfile.py b/scipy/io/wavfile.py index 9e931541aceb..b6978a1c461c 100644 --- a/scipy/io/wavfile.py +++ b/scipy/io/wavfile.py @@ -799,7 +799,7 @@ def write(filename, rate, data): allowed_dtypes = ['float32', 'float64', 'uint8', 'int16', 'int32', 'int64'] if data.dtype.name not in allowed_dtypes: - raise ValueError("Unsupported data type '%s'" % data.dtype) + raise ValueError(f"Unsupported data type '{data.dtype}'") header_data = b'' diff --git a/scipy/linalg/_basic.py b/scipy/linalg/_basic.py index 71be3319b6b7..1c07681767d7 100644 --- a/scipy/linalg/_basic.py +++ b/scipy/linalg/_basic.py @@ -1273,10 +1273,10 @@ def lstsq(a, b, cond=None, overwrite_a=False, overwrite_b=False, if driver is None: driver = lstsq.default_lapack_driver if driver not in ('gelsd', 'gelsy', 'gelss'): - raise ValueError('LAPACK driver "%s" is not found' % driver) + raise ValueError(f'LAPACK driver "{driver}" is not found') lapack_func, lapack_lwork = get_lapack_funcs((driver, - '%s_lwork' % driver), + f'{driver}_lwork'), (a1, b1)) real_data = True if (lapack_func.dtype.kind == 'f') else False diff --git a/scipy/linalg/_decomp_cossin.py b/scipy/linalg/_decomp_cossin.py index bb8603d03c79..aac572872836 100644 --- a/scipy/linalg/_decomp_cossin.py +++ b/scipy/linalg/_decomp_cossin.py @@ -148,8 +148,8 @@ def cossin(X, p=None, q=None, separate=False, if p + mmp != q + mmq: raise ValueError("The subblocks have compatible sizes but " "don't form a square array (instead they form a" - " {}x{} array). This might be due to missing " - "p, q arguments.".format(p + mmp, q + mmq)) + f" {p + mmp}x{q + mmq} array). This might be " + "due to missing p, q arguments.") m = p + mmp diff --git a/scipy/linalg/_decomp_schur.py b/scipy/linalg/_decomp_schur.py index 6f55f4fe8d37..69b110af1039 100644 --- a/scipy/linalg/_decomp_schur.py +++ b/scipy/linalg/_decomp_schur.py @@ -288,7 +288,7 @@ def rsf2csf(T, Z, check_finite=True): for ind, X in enumerate([Z, T]): if X.ndim != 2 or X.shape[0] != X.shape[1]: - raise ValueError("Input '{}' must be square.".format('ZT'[ind])) + raise ValueError(f"Input '{'ZT'[ind]}' must be square.") if T.shape[0] != Z.shape[0]: message = f"Input array shapes must match: Z: {Z.shape} vs. T: {T.shape}" diff --git a/scipy/linalg/_expm_frechet.py b/scipy/linalg/_expm_frechet.py index 83d308687298..56ddbc45c3bc 100644 --- a/scipy/linalg/_expm_frechet.py +++ b/scipy/linalg/_expm_frechet.py @@ -108,7 +108,7 @@ def expm_frechet(A, E, method=None, compute_expm=True, check_finite=True): elif method == 'blockEnlarge': expm_A, expm_frechet_AE = expm_frechet_block_enlarge(A, E) else: - raise ValueError('Unknown implementation %s' % method) + raise ValueError(f'Unknown implementation {method}') if compute_expm: return expm_A, expm_frechet_AE else: diff --git a/scipy/linalg/_procrustes.py b/scipy/linalg/_procrustes.py index 1e835f15681c..df1b187174a8 100644 --- a/scipy/linalg/_procrustes.py +++ b/scipy/linalg/_procrustes.py @@ -80,7 +80,7 @@ def orthogonal_procrustes(A, B, check_finite=True): A = np.asanyarray(A) B = np.asanyarray(B) if A.ndim != 2: - raise ValueError('expected ndim to be 2, but observed %s' % A.ndim) + raise ValueError(f'expected ndim to be 2, but observed {A.ndim}') if A.shape != B.shape: raise ValueError(f'the shapes of A and B differ ({A.shape} vs {B.shape})') # Be clever with transposes, with the intention to save memory. diff --git a/scipy/linalg/_solvers.py b/scipy/linalg/_solvers.py index 0974eac56a11..23a872ec8132 100644 --- a/scipy/linalg/_solvers.py +++ b/scipy/linalg/_solvers.py @@ -166,7 +166,7 @@ def solve_continuous_lyapunov(a, q): r_or_c = complex if not np.equal(*_.shape): - raise ValueError("Matrix {} should be square.".format("aq"[ind])) + raise ValueError(f"Matrix {'aq'[ind]} should be square.") # Shape consistency check if a.shape != q.shape: @@ -181,7 +181,7 @@ def solve_continuous_lyapunov(a, q): # Call the Sylvester equation solver trsyl = get_lapack_funcs('trsyl', (r, f)) - dtype_string = 'T' if r_or_c == float else 'C' + dtype_string = 'T' if r_or_c is float else 'C' y, scale, info = trsyl(r, r, f, tranb=dtype_string) if info < 0: @@ -318,7 +318,7 @@ def solve_discrete_lyapunov(a, q, method=None): elif meth == 'bilinear': x = _solve_discrete_lyapunov_bilinear(a, q) else: - raise ValueError('Unknown solver %s' % method) + raise ValueError(f'Unknown solver {method}') return x @@ -487,7 +487,7 @@ def solve_continuous_are(a, b, q, r, e=None, s=None, balanced=True): J = q[:2*m, n:].conj().T.dot(J[:2*m, :2*m]) # Decide on which output type is needed for QZ - out_str = 'real' if r_or_c == float else 'complex' + out_str = 'real' if r_or_c is float else 'complex' _, _, _, _, _, u = ordqz(H, J, sort='lhp', overwrite_a=True, overwrite_b=True, check_finite=False, @@ -693,7 +693,7 @@ def solve_discrete_are(a, b, q, r, e=None, s=None, balanced=True): J = q_of_qr[:, n:].conj().T.dot(J[:, :2*m]) # Decide on which output type is needed for QZ - out_str = 'real' if r_or_c == float else 'complex' + out_str = 'real' if r_or_c is float else 'complex' _, _, _, _, _, u = ordqz(H, J, sort='iuc', overwrite_a=True, @@ -794,7 +794,7 @@ def _are_validate_args(a, b, q, r, e, s, eq_type='care'): r_or_c = complex if not np.equal(*mat.shape): - raise ValueError("Matrix {} should be square.".format("aqr"[ind])) + raise ValueError(f"Matrix {'aqr'[ind]} should be square.") # Shape consistency checks m, n = b.shape @@ -808,8 +808,7 @@ def _are_validate_args(a, b, q, r, e, s, eq_type='care'): # Check if the data matrices q, r are (sufficiently) hermitian for ind, mat in enumerate((q, r)): if norm(mat - mat.conj().T, 1) > np.spacing(norm(mat, 1))*100: - raise ValueError("Matrix {} should be symmetric/hermitian." - "".format("qr"[ind])) + raise ValueError(f"Matrix {'qr'[ind]} should be symmetric/hermitian.") # Continuous time ARE should have a nonsingular r matrix. if eq_type == 'care': diff --git a/scipy/linalg/_special_matrices.py b/scipy/linalg/_special_matrices.py index 99b7b49d6b39..47ee5ec9f80d 100644 --- a/scipy/linalg/_special_matrices.py +++ b/scipy/linalg/_special_matrices.py @@ -419,8 +419,8 @@ def block_diag(*arrs): bad_args = [k for k in range(len(arrs)) if arrs[k].ndim > 2] if bad_args: - raise ValueError("arguments in the following positions have dimension " - "greater than 2: %s" % bad_args) + raise ValueError("arguments in the following positions " + f"have dimension greater than 2: {bad_args}") shapes = np.array([a.shape for a in arrs]) out_dtype = np.result_type(*[arr.dtype for arr in arrs]) diff --git a/scipy/linalg/lapack.py b/scipy/linalg/lapack.py index 487ff5ad6efb..8177f6890d49 100644 --- a/scipy/linalg/lapack.py +++ b/scipy/linalg/lapack.py @@ -863,10 +863,9 @@ def backtickrepl(m): if m.group('s'): - return ('with bounds ``{}`` with ``{}`` storage\n' - ''.format(m.group('b'), m.group('s'))) + return (f"with bounds ``{m.group('b')}`` with ``{m.group('s')}`` storage\n") else: - return 'with bounds ``{}``\n'.format(m.group('b')) + return f"with bounds ``{m.group('b')}``\n" for routine in [ssyevr, dsyevr, cheevr, zheevr, diff --git a/scipy/linalg/tests/test_basic.py b/scipy/linalg/tests/test_basic.py index 8a3f641f663e..806b1e69b166 100644 --- a/scipy/linalg/tests/test_basic.py +++ b/scipy/linalg/tests/test_basic.py @@ -1179,11 +1179,11 @@ def test_simple_exact(self): x = out[0] r = out[2] assert_(r == 2, - 'expected efficient rank 2, got %s' % r) + f'expected efficient rank 2, got {r}') assert_allclose(dot(a, x), b, atol=25 * _eps_cast(a1.dtype), rtol=25 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") def test_simple_overdet(self): for dtype in REAL_DTYPES: @@ -1203,16 +1203,16 @@ def test_simple_overdet(self): else: residuals = out[1] r = out[2] - assert_(r == 2, 'expected efficient rank 2, got %s' % r) + assert_(r == 2, f'expected efficient rank 2, got {r}') assert_allclose(abs((dot(a, x) - b)**2).sum(axis=0), residuals, rtol=25 * _eps_cast(a1.dtype), atol=25 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") assert_allclose(x, (-0.428571428571429, 0.85714285714285), rtol=25 * _eps_cast(a1.dtype), atol=25 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") def test_simple_overdet_complex(self): for dtype in COMPLEX_DTYPES: @@ -1234,18 +1234,18 @@ def test_simple_overdet_complex(self): else: residuals = out[1] r = out[2] - assert_(r == 2, 'expected efficient rank 2, got %s' % r) + assert_(r == 2, f'expected efficient rank 2, got {r}') assert_allclose(abs((dot(a, x) - b)**2).sum(axis=0), residuals, rtol=25 * _eps_cast(a1.dtype), atol=25 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") assert_allclose( x, (-0.4831460674157303 + 0.258426966292135j, 0.921348314606741 + 0.292134831460674j), rtol=25 * _eps_cast(a1.dtype), atol=25 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") def test_simple_underdet(self): for dtype in REAL_DTYPES: @@ -1262,12 +1262,12 @@ def test_simple_underdet(self): x = out[0] r = out[2] - assert_(r == 2, 'expected efficient rank 2, got %s' % r) + assert_(r == 2, f'expected efficient rank 2, got {r}') assert_allclose(x, (-0.055555555555555, 0.111111111111111, 0.277777777777777), rtol=25 * _eps_cast(a1.dtype), atol=25 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") @pytest.mark.parametrize("dtype", REAL_DTYPES) @pytest.mark.parametrize("n", (20, 200)) @@ -1297,13 +1297,13 @@ def test_random_exact(self, dtype, n, lapack_driver, overwrite): dot(a, x), b, rtol=500 * _eps_cast(a1.dtype), atol=500 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") else: assert_allclose( dot(a, x), b, rtol=1000 * _eps_cast(a1.dtype), atol=1000 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") @pytest.mark.skipif(IS_MUSL, reason="may segfault on Alpine, see gh-17630") @pytest.mark.parametrize("dtype", COMPLEX_DTYPES) @@ -1334,13 +1334,13 @@ def test_random_complex_exact(self, dtype, n, lapack_driver, overwrite): dot(a, x), b, rtol=400 * _eps_cast(a1.dtype), atol=400 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") else: assert_allclose( dot(a, x), b, rtol=1000 * _eps_cast(a1.dtype), atol=1000 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") def test_random_overdet(self): rng = np.random.RandomState(1234) @@ -1368,7 +1368,7 @@ def test_random_overdet(self): x, direct_lstsq(a, b, cmplx=0), rtol=25 * _eps_cast(a1.dtype), atol=25 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") def test_random_complex_overdet(self): rng = np.random.RandomState(1234) @@ -1398,7 +1398,7 @@ def test_random_complex_overdet(self): x, direct_lstsq(a, b, cmplx=1), rtol=25 * _eps_cast(a1.dtype), atol=25 * _eps_cast(a1.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") def test_check_finite(self): with suppress_warnings() as sup: @@ -1426,11 +1426,11 @@ def test_check_finite(self): overwrite_b=overwrite) x = out[0] r = out[2] - assert_(r == 2, 'expected efficient rank 2, got %s' % r) + assert_(r == 2, f'expected efficient rank 2, got {r}') assert_allclose(dot(a, x), b, rtol=25 * _eps_cast(a.dtype), atol=25 * _eps_cast(a.dtype), - err_msg="driver: %s" % lapack_driver) + err_msg=f"driver: {lapack_driver}") def test_empty(self): for a_shape, b_shape in (((0, 2), (0,)), diff --git a/scipy/odr/_odrpack.py b/scipy/odr/_odrpack.py index 59604d30b847..e21f4bfedbb5 100644 --- a/scipy/odr/_odrpack.py +++ b/scipy/odr/_odrpack.py @@ -289,7 +289,7 @@ def __getattr__(self, attr): if attr in self.meta: return self.meta[attr] else: - raise AttributeError("'%s' not in metadata" % attr) + raise AttributeError(f"'{attr}' not in metadata") class RealData(Data): @@ -417,7 +417,7 @@ def __getattr__(self, attr): if attr in self.meta: return self.meta[attr] else: - raise AttributeError("'%s' not in metadata" % attr) + raise AttributeError(f"'{attr}' not in metadata") else: func, arg = lookup_tbl[(attr, self._ga_flags[attr])] @@ -536,7 +536,7 @@ def __getattr__(self, attr): if attr in self.meta: return self.meta[attr] else: - raise AttributeError("'%s' not in metadata" % attr) + raise AttributeError(f"'{attr}' not in metadata") class Output: @@ -614,7 +614,7 @@ def pprint(self): print('Inverse Condition #:', self.inv_condnum) print('Reason(s) for Halting:') for r in self.stopreason: - print(' %s' % r) + print(f' {r}') class ODR: @@ -849,24 +849,24 @@ def _check(self): if res.shape not in fcn_perms: print(res.shape) print(fcn_perms) - raise OdrError("fcn does not output %s-shaped array" % y_s) + raise OdrError(f"fcn does not output {y_s}-shaped array") if self.model.fjacd is not None: res = self.model.fjacd(*arglist) if res.shape not in fjacd_perms: raise OdrError( - "fjacd does not output %s-shaped array" % repr((q, m, n))) + f"fjacd does not output {repr((q, m, n))}-shaped array") if self.model.fjacb is not None: res = self.model.fjacb(*arglist) if res.shape not in fjacb_perms: raise OdrError( - "fjacb does not output %s-shaped array" % repr((q, p, n))) + f"fjacb does not output {repr((q, p, n))}-shaped array") # check shape of delta0 if self.delta0 is not None and self.delta0.shape != self.data.x.shape: raise OdrError( - "delta0 is not a %s-shaped array" % repr(self.data.x.shape)) + f"delta0 is not a {repr(self.data.x.shape)}-shaped array") if self.data.x.size == 0: warn("Empty data detected for ODR instance. " diff --git a/scipy/optimize/_cobyla_py.py b/scipy/optimize/_cobyla_py.py index 9007fe38a06a..7e99acf373df 100644 --- a/scipy/optimize/_cobyla_py.py +++ b/scipy/optimize/_cobyla_py.py @@ -248,8 +248,8 @@ def ub_constraint(x): raise TypeError("Constraint's type must be a string.") from e else: if ctype != 'ineq': - raise ValueError("Constraints of type '%s' not handled by " - "COBYLA." % con['type']) + raise ValueError(f"Constraints of type '{con['type']}' not handled by " + "COBYLA.") # check function if 'fun' not in con: diff --git a/scipy/optimize/_constraints.py b/scipy/optimize/_constraints.py index 1c7ff5e170b2..ebee3857b310 100644 --- a/scipy/optimize/_constraints.py +++ b/scipy/optimize/_constraints.py @@ -564,7 +564,7 @@ def old_constraint_to_new(ic, con): raise TypeError("Constraint's type must be a string.") from e else: if ctype not in ['eq', 'ineq']: - raise ValueError("Unknown constraint type '%s'." % con['type']) + raise ValueError(f"Unknown constraint type '{con['type']}'.") if 'fun' not in con: raise ValueError('Constraint %d has no function defined.' % ic) diff --git a/scipy/optimize/_linesearch.py b/scipy/optimize/_linesearch.py index 39e0b3826d5f..31442e02d323 100644 --- a/scipy/optimize/_linesearch.py +++ b/scipy/optimize/_linesearch.py @@ -425,7 +425,7 @@ def extra_condition(alpha, phi): msg = 'Rounding errors prevent the line search from converging' else: msg = "The line search algorithm could not find a solution " + \ - "less than or equal to amax: %s" % amax + f"less than or equal to amax: {amax}" warn(msg, LineSearchWarning, stacklevel=2) break diff --git a/scipy/optimize/_linprog_simplex.py b/scipy/optimize/_linprog_simplex.py index b13418c36986..c47806c9a595 100644 --- a/scipy/optimize/_linprog_simplex.py +++ b/scipy/optimize/_linprog_simplex.py @@ -638,12 +638,14 @@ def _linprog_simplex(c, c0, A, b, callback, postsolve_args, status = 2 messages[status] = ( "Phase 1 of the simplex method failed to find a feasible " - "solution. The pseudo-objective function evaluates to {0:.1e} " - "which exceeds the required tolerance of {1} for a solution to be " + "solution. The pseudo-objective function evaluates to " + f"{abs(T[-1, -1]):.1e} " + f"which exceeds the required tolerance of {tol} for a solution to be " "considered 'close enough' to zero to be a basic solution. " - "Consider increasing the tolerance to be greater than {0:.1e}. " - "If this tolerance is unacceptably large the problem may be " - "infeasible.".format(abs(T[-1, -1]), tol) + "Consider increasing the tolerance to be greater than " + f"{abs(T[-1, -1]):.1e}. " + "If this tolerance is unacceptably large the problem may be " + "infeasible." ) if status == 0: diff --git a/scipy/optimize/_lsq/common.py b/scipy/optimize/_lsq/common.py index 995c3b64ea64..0f8117f23ec1 100644 --- a/scipy/optimize/_lsq/common.py +++ b/scipy/optimize/_lsq/common.py @@ -560,9 +560,7 @@ def print_iteration_nonlinear(iteration, nfev, cost, cost_reduction, else: step_norm = f"{step_norm:^15.2e}" - print("{:^15}{:^15}{:^15.4e}{}{}{:^15.2e}" - .format(iteration, nfev, cost, cost_reduction, - step_norm, optimality)) + print(f"{iteration:^15}{nfev:^15}{cost:^15.4e}{cost_reduction}{step_norm}{optimality:^15.2e}") def print_header_linear(): diff --git a/scipy/optimize/_lsq/least_squares.py b/scipy/optimize/_lsq/least_squares.py index db8bb31c7b15..00b2fa0536ae 100644 --- a/scipy/optimize/_lsq/least_squares.py +++ b/scipy/optimize/_lsq/least_squares.py @@ -777,8 +777,8 @@ def least_squares( raise ValueError("`tr_solver` must be None, 'exact' or 'lsmr'.") if loss not in IMPLEMENTED_LOSSES and not callable(loss): - raise ValueError("`loss` must be one of {} or a callable." - .format(IMPLEMENTED_LOSSES.keys())) + raise ValueError(f"`loss` must be one of {IMPLEMENTED_LOSSES.keys()}" + " or a callable.") if method == 'lm' and loss != 'linear': raise ValueError("method='lm' supports only 'linear' loss function.") @@ -959,9 +959,8 @@ def jac_wrapped(x, f): if verbose >= 1: print(result.message) - print("Function evaluations {}, initial cost {:.4e}, final cost " - "{:.4e}, first-order optimality {:.2e}." - .format(result.nfev, initial_cost, result.cost, - result.optimality)) + print(f"Function evaluations {result.nfev}, initial cost {initial_cost:.4e}, " + f"final cost {result.cost:.4e}, " + f"first-order optimality {result.optimality:.2e}.") return result diff --git a/scipy/optimize/_minimize.py b/scipy/optimize/_minimize.py index 195e31f23e22..8ebf500b26ab 100644 --- a/scipy/optimize/_minimize.py +++ b/scipy/optimize/_minimize.py @@ -574,34 +574,34 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, # check if optional parameters are supported by the selected method # - jac if meth in ('nelder-mead', 'powell', 'cobyla', 'cobyqa') and bool(jac): - warn('Method %s does not use gradient information (jac).' % method, + warn(f'Method {method} does not use gradient information (jac).', RuntimeWarning, stacklevel=2) # - hess if meth not in ('newton-cg', 'dogleg', 'trust-ncg', 'trust-constr', 'trust-krylov', 'trust-exact', '_custom') and hess is not None: - warn('Method %s does not use Hessian information (hess).' % method, + warn(f'Method {method} does not use Hessian information (hess).', RuntimeWarning, stacklevel=2) # - hessp if meth not in ('newton-cg', 'trust-ncg', 'trust-constr', 'trust-krylov', '_custom') \ and hessp is not None: - warn('Method %s does not use Hessian-vector product ' - 'information (hessp).' % method, + warn(f'Method {method} does not use Hessian-vector product' + ' information (hessp).', RuntimeWarning, stacklevel=2) # - constraints or bounds if (meth not in ('cobyla', 'cobyqa', 'slsqp', 'trust-constr', '_custom') and np.any(constraints)): - warn('Method %s cannot handle constraints.' % method, + warn(f'Method {method} cannot handle constraints.', RuntimeWarning, stacklevel=2) if meth not in ( 'nelder-mead', 'powell', 'l-bfgs-b', 'cobyla', 'cobyqa', 'slsqp', 'tnc', 'trust-constr', '_custom') and bounds is not None: - warn('Method %s cannot handle bounds.' % method, + warn(f'Method {method} cannot handle bounds.', RuntimeWarning, stacklevel=2) # - return_all if (meth in ('l-bfgs-b', 'tnc', 'cobyla', 'cobyqa', 'slsqp') and options.get('return_all', False)): - warn('Method %s does not support the return_all option.' % method, + warn(f'Method {method} does not support the return_all option.', RuntimeWarning, stacklevel=2) # check gradient vector @@ -759,7 +759,7 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, res = _minimize_trustregion_exact(fun, x0, args, jac, hess, callback=callback, **options) else: - raise ValueError('Unknown solver %s' % method) + raise ValueError(f'Unknown solver {method}') if remove_vars: res.x = _add_to_array(res.x, i_fixed, x_fixed) @@ -968,7 +968,7 @@ def minimize_scalar(fun, bracket=None, bounds=None, args=(), res = _recover_from_bracket_error(_minimize_scalar_golden, fun, bracket, args, **options) else: - raise ValueError('Unknown solver %s' % method) + raise ValueError(f'Unknown solver {method}') # gh-16196 reported inconsistencies in the output shape of `res.x`. While # fixing this, future-proof it for when the function is vectorized: diff --git a/scipy/optimize/_minpack_py.py b/scipy/optimize/_minpack_py.py index b67a17ae41b4..04a5178b89ce 100644 --- a/scipy/optimize/_minpack_py.py +++ b/scipy/optimize/_minpack_py.py @@ -30,7 +30,7 @@ def _check_func(checker, argname, thefunc, x0, args, numinputs, f"shape of the '{argname}' argument" func_name = getattr(thefunc, '__name__', None) if func_name: - msg += " '%s'." % func_name + msg += f" '{func_name}'." else: msg += "." msg += f'Shape should be {output_shape} but it is {shape(res)}.' @@ -261,14 +261,13 @@ def _root_hybr(func, x0, args=(), jac=None, 1: "The solution converged.", 2: "The number of calls to function has " "reached maxfev = %d." % maxfev, - 3: "xtol=%f is too small, no further improvement " - "in the approximate\n solution " - "is possible." % xtol, + 3: f"xtol={xtol:f} is too small, no further improvement " + "in the approximate\n solution is possible.", 4: "The iteration is not making good progress, as measured " - "by the \n improvement from the last five " + "by the \n improvement from the last five " "Jacobian evaluations.", 5: "The iteration is not making good progress, " - "as measured by the \n improvement from the last " + "as measured by the \n improvement from the last " "ten iterations.", 'unknown': "An error occurred."} @@ -451,27 +450,26 @@ def leastsq(func, x0, args=(), Dfun=None, full_output=False, errors = {0: ["Improper input parameters.", TypeError], 1: ["Both actual and predicted relative reductions " - "in the sum of squares\n are at most %f" % ftol, None], + f"in the sum of squares\n are at most {ftol:f}", None], 2: ["The relative error between two consecutive " - "iterates is at most %f" % xtol, None], + f"iterates is at most {xtol:f}", None], 3: ["Both actual and predicted relative reductions in " f"the sum of squares\n are at most {ftol:f} and the " "relative error between two consecutive " f"iterates is at \n most {xtol:f}", None], 4: ["The cosine of the angle between func(x) and any " - "column of the\n Jacobian is at most %f in " - "absolute value" % gtol, None], + f"column of the\n Jacobian is at most {gtol:f} in " + "absolute value", None], 5: ["Number of calls to function has reached " "maxfev = %d." % maxfev, ValueError], - 6: ["ftol=%f is too small, no further reduction " - "in the sum of squares\n is possible." % ftol, + 6: [f"ftol={ftol:f} is too small, no further reduction " + "in the sum of squares\n is possible.", ValueError], - 7: ["xtol=%f is too small, no further improvement in " - "the approximate\n solution is possible." % xtol, + 7: [f"xtol={xtol:f} is too small, no further improvement in " + "the approximate\n solution is possible.", ValueError], - 8: ["gtol=%f is too small, func(x) is orthogonal to the " - "columns of\n the Jacobian to machine " - "precision." % gtol, ValueError]} + 8: [f"gtol={gtol:f} is too small, func(x) is orthogonal to the " + "columns of\n the Jacobian to machine precision.", ValueError]} # The FORTRAN return value (possible return values are >= 0 and <= 8) info = retval[-1] diff --git a/scipy/optimize/_nonlin.py b/scipy/optimize/_nonlin.py index cbaa3d4ced44..b4ad154bdad6 100644 --- a/scipy/optimize/_nonlin.py +++ b/scipy/optimize/_nonlin.py @@ -421,7 +421,7 @@ def __init__(self, **kw): "matmat", "todense", "shape", "dtype"] for name, value in kw.items(): if name not in names: - raise ValueError("Unknown keyword argument %s" % name) + raise ValueError(f"Unknown keyword argument {name}") if value is not None: setattr(self, name, kw[name]) @@ -896,8 +896,7 @@ def __init__(self, alpha=None, reduction_method='restart', max_rank=None): elif reduction_method == 'restart': self._reduce = lambda: self.Gm.restart_reduce(*reduce_params) else: - raise ValueError("Unknown rank reduction method '%s'" % - reduction_method) + raise ValueError(f"Unknown rank reduction method '{reduction_method}'") def setup(self, x, F, func): GenericBroyden.setup(self, x, F, func) @@ -1476,7 +1475,7 @@ def __init__(self, rdiff=None, method='lgmres', inner_maxiter=20, for key, value in kw.items(): if not key.startswith('inner_'): - raise ValueError("Unknown parameter %s" % key) + raise ValueError(f"Unknown parameter {key}") self.method_kw[key[6:]] = value def _update_diff_step(self): @@ -1551,7 +1550,7 @@ def _nonlin_wrapper(name, jac): if kwkw_str: kwkw_str = kwkw_str + ", " if kwonlyargs: - raise ValueError('Unexpected signature %s' % signature) + raise ValueError(f'Unexpected signature {signature}') # Construct the wrapper function so that its keyword arguments # are visible in pydoc.help etc. diff --git a/scipy/optimize/_numdiff.py b/scipy/optimize/_numdiff.py index b5cb5724d863..aa6ff88c6bc9 100644 --- a/scipy/optimize/_numdiff.py +++ b/scipy/optimize/_numdiff.py @@ -437,7 +437,7 @@ def approx_derivative(fun, x0, method='3-point', rel_step=None, abs_step=None, array([ 2.]) """ if method not in ['2-point', '3-point', 'cs']: - raise ValueError("Unknown method '%s'. " % method) + raise ValueError(f"Unknown method '{method}'. ") xp = array_namespace(x0) _x = atleast_nd(x0, ndim=1, xp=xp) diff --git a/scipy/optimize/_optimize.py b/scipy/optimize/_optimize.py index a1cc6ab84a0f..b58eaaa61732 100644 --- a/scipy/optimize/_optimize.py +++ b/scipy/optimize/_optimize.py @@ -175,7 +175,7 @@ def _check_unknown_options(unknown_options): # Stack level 4: this is called from _minimize_*, which is # called from another function in SciPy. Level 4 is the first # level in user code. - warnings.warn("Unknown solver options: %s" % msg, OptimizeWarning, stacklevel=4) + warnings.warn(f"Unknown solver options: {msg}", OptimizeWarning, stacklevel=4) def is_finite_scalar(x): @@ -922,7 +922,7 @@ def _minimize_neldermead(func, x0, args=(), callback=None, msg = _status_message['success'] if disp: print(msg) - print(" Current function value: %f" % fval) + print(f" Current function value: {fval:f}") print(" Iterations: %d" % iterations) print(" Function evaluations: %d" % fcalls[0]) @@ -1466,7 +1466,7 @@ def _minimize_bfgs(fun, x0, args=(), jac=None, callback=None, if disp: _print_success_message_or_warn(warnflag, msg) - print(" Current function value: %f" % fval) + print(f" Current function value: {fval:f}") print(" Iterations: %d" % k) print(" Function evaluations: %d" % sf.nfev) print(" Gradient evaluations: %d" % sf.ngev) @@ -1810,7 +1810,7 @@ def descent_condition(alpha, xkp1, fp1, gfkp1): if disp: _print_success_message_or_warn(warnflag, msg) - print(" Current function value: %f" % fval) + print(f" Current function value: {fval:f}") print(" Iterations: %d" % k) print(" Function evaluations: %d" % sf.nfev) print(" Gradient evaluations: %d" % sf.ngev) @@ -2013,7 +2013,7 @@ def _hessp(x, p, *args): def terminate(warnflag, msg): if disp: _print_success_message_or_warn(warnflag, msg) - print(" Current function value: %f" % old_fval) + print(f" Current function value: {old_fval:f}") print(" Iterations: %d" % k) print(" Function evaluations: %d" % sf.nfev) print(" Gradient evaluations: %d" % sf.ngev) @@ -2654,7 +2654,7 @@ def _minimize_scalar_brent(func, brack=None, args=(), xtol=1.48e-8, _check_unknown_options(unknown_options) tol = xtol if tol < 0: - raise ValueError('tolerance should be >= 0, got %r' % tol) + raise ValueError(f'tolerance should be >= 0, got {tol!r}') brent = Brent(func=func, args=args, tol=tol, full_output=True, maxiter=maxiter, disp=disp) @@ -3564,7 +3564,7 @@ def _minimize_powell(func, x0, args=(), callback=None, bounds=None, if disp: _print_success_message_or_warn(warnflag, msg, RuntimeWarning) - print(" Current function value: %f" % fval) + print(f" Current function value: {fval:f}") print(" Iterations: %d" % iter) print(" Function evaluations: %d" % fcalls[0]) @@ -3588,7 +3588,7 @@ def _endprint(x, flag, fval, maxfun, xtol, disp): msg = ("\nMaximum number of function evaluations exceeded --- " "increase maxfun argument.\n") elif flag == 2: - msg = "\n{}".format(_status_message['nan']) + msg = f"\n{_status_message['nan']}" _print_success_message_or_warn(flag, msg) return diff --git a/scipy/optimize/_root.py b/scipy/optimize/_root.py index 2847619bb803..ea3b1da51390 100644 --- a/scipy/optimize/_root.py +++ b/scipy/optimize/_root.py @@ -217,7 +217,7 @@ def _wrapped_fun(*fargs): options = {} if callback is not None and meth in ('hybr', 'lm'): - warn('Method %s does not accept callback.' % method, + warn(f'Method {method} does not accept callback.', RuntimeWarning, stacklevel=2) # fun also returns the Jacobian @@ -257,7 +257,7 @@ def _wrapped_fun(*fargs): _method=meth, _callback=callback, **options) else: - raise ValueError('Unknown solver %s' % method) + raise ValueError(f'Unknown solver {method}') sol.nfev = _wrapped_fun.nfev return sol diff --git a/scipy/optimize/_root_scalar.py b/scipy/optimize/_root_scalar.py index a1d9fde0af0a..de538b6ca5aa 100644 --- a/scipy/optimize/_root_scalar.py +++ b/scipy/optimize/_root_scalar.py @@ -268,11 +268,11 @@ def root_scalar(f, args=(), method=None, bracket=None, try: methodc = getattr(optzeros, map2underlying.get(meth, meth)) except AttributeError as e: - raise ValueError('Unknown solver %s' % meth) from e + raise ValueError(f'Unknown solver {meth}') from e if meth in ['bisect', 'ridder', 'brentq', 'brenth', 'toms748']: if not isinstance(bracket, (list, tuple, np.ndarray)): - raise ValueError('Bracket needed for %s' % method) + raise ValueError(f'Bracket needed for {method}') a, b = bracket[:2] try: @@ -292,14 +292,14 @@ def root_scalar(f, args=(), method=None, bracket=None, elif meth in ['secant']: if x0 is None: - raise ValueError('x0 must not be None for %s' % method) + raise ValueError(f'x0 must not be None for {method}') if 'xtol' in kwargs: kwargs['tol'] = kwargs.pop('xtol') r, sol = methodc(f, x0, args=args, fprime=None, fprime2=None, x1=x1, **kwargs) elif meth in ['newton']: if x0 is None: - raise ValueError('x0 must not be None for %s' % method) + raise ValueError(f'x0 must not be None for {method}') if not fprime: # approximate fprime with finite differences @@ -316,16 +316,16 @@ def fprime(x, *args): **kwargs) elif meth in ['halley']: if x0 is None: - raise ValueError('x0 must not be None for %s' % method) + raise ValueError(f'x0 must not be None for {method}') if not fprime: - raise ValueError('fprime must be specified for %s' % method) + raise ValueError(f'fprime must be specified for {method}') if not fprime2: - raise ValueError('fprime2 must be specified for %s' % method) + raise ValueError(f'fprime2 must be specified for {method}') if 'xtol' in kwargs: kwargs['tol'] = kwargs.pop('xtol') r, sol = methodc(f, x0, args=args, fprime=fprime, fprime2=fprime2, **kwargs) else: - raise ValueError('Unknown solver %s' % method) + raise ValueError(f'Unknown solver {method}') if is_memoized: # Replace the function_calls count with the memoized count. diff --git a/scipy/optimize/_shgo.py b/scipy/optimize/_shgo.py index 9c8f36a777f8..68b0f6ede297 100644 --- a/scipy/optimize/_shgo.py +++ b/scipy/optimize/_shgo.py @@ -525,8 +525,8 @@ def __init__(self, func, bounds, args=(), constraints=None, n=None, # Check if bounds are correctly specified bnderr = abound[:, 0] > abound[:, 1] if bnderr.any(): - raise ValueError('Error: lb > ub in bounds {}.' - .format(', '.join(str(b) for b in bnderr))) + raise ValueError("Error: lb > ub in bounds " + f"{', '.join(str(b) for b in bnderr)}.") self.bounds = abound diff --git a/scipy/optimize/_slsqp_py.py b/scipy/optimize/_slsqp_py.py index 19b5541c9cbf..7dc38aac5abb 100644 --- a/scipy/optimize/_slsqp_py.py +++ b/scipy/optimize/_slsqp_py.py @@ -284,7 +284,7 @@ def _minimize_slsqp(func, x0, args=(), jac=None, bounds=None, raise TypeError("Constraint's type must be a string.") from e else: if ctype not in ['eq', 'ineq']: - raise ValueError("Unknown constraint type '%s'." % con['type']) + raise ValueError(f"Unknown constraint type '{con['type']}'.") # check function if 'fun' not in con: @@ -367,8 +367,8 @@ def cjac(x, *args): bnderr = bnds[:, 0] > bnds[:, 1] if bnderr.any(): - raise ValueError('SLSQP Error: lb > ub in bounds %s.' % - ', '.join(str(b) for b in bnderr)) + raise ValueError("SLSQP Error: lb > ub in bounds " + f"{', '.join(str(b) for b in bnderr)}.") xl, xu = bnds[:, 0], bnds[:, 1] # Mark infinite bounds with nans; the Fortran code understands this diff --git a/scipy/optimize/_trustregion.py b/scipy/optimize/_trustregion.py index f2355cf68ac8..0dadc727e74e 100644 --- a/scipy/optimize/_trustregion.py +++ b/scipy/optimize/_trustregion.py @@ -284,7 +284,7 @@ def hessp(x, p, *args): print(status_messages[warnflag]) else: warnings.warn(status_messages[warnflag], RuntimeWarning, stacklevel=3) - print(" Current function value: %f" % m.fun) + print(f" Current function value: {m.fun:f}") print(" Iterations: %d" % k) print(" Function evaluations: %d" % sf.nfev) print(" Gradient evaluations: %d" % sf.ngev) diff --git a/scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py b/scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py index 2835ea5445c0..f45d3ed46121 100644 --- a/scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py +++ b/scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py @@ -555,10 +555,10 @@ def grad_and_jac(x): IPReport.print_footer() if verbose >= 1: print(result.message) - print("Number of iterations: {}, function evaluations: {}, " - "CG iterations: {}, optimality: {:.2e}, " - "constraint violation: {:.2e}, execution time: {:4.2} s." - .format(result.nit, result.nfev, result.cg_niter, - result.optimality, result.constr_violation, - result.execution_time)) + print(f"Number of iterations: {result.nit}, " + f"function evaluations: {result.nfev}, " + f"CG iterations: {result.cg_niter}, " + f"optimality: {result.optimality:.2e}, " + f"constraint violation: {result.constr_violation:.2e}, " + f"execution time: {result.execution_time:4.2} s.") return result diff --git a/scipy/optimize/_zeros_py.py b/scipy/optimize/_zeros_py.py index ccffcb207d6c..18cebd350b80 100644 --- a/scipy/optimize/_zeros_py.py +++ b/scipy/optimize/_zeros_py.py @@ -285,7 +285,7 @@ class of similar problems can be solved together. """ if tol <= 0: - raise ValueError("tol too small (%g <= 0)" % tol) + raise ValueError(f"tol too small ({tol:g} <= 0)") maxiter = operator.index(maxiter) if maxiter < 1: raise ValueError("maxiter must be greater than 0") @@ -362,7 +362,7 @@ class of similar problems can be solved together. for itr in range(maxiter): if q1 == q0: if p1 != p0: - msg = "Tolerance of %s reached." % (p1 - p0) + msg = f"Tolerance of {p1 - p0} reached." if disp: msg += ( " Failed to converge after %d iterations, value is %s." @@ -570,7 +570,7 @@ def bisect(f, a, b, args=(), args = (args,) maxiter = operator.index(maxiter) if xtol <= 0: - raise ValueError("xtol too small (%g <= 0)" % xtol) + raise ValueError(f"xtol too small ({xtol:g} <= 0)") if rtol < _rtol: raise ValueError(f"rtol too small ({rtol:g} < {_rtol:g})") f = _wrap_nan_raise(f) @@ -668,7 +668,7 @@ def ridder(f, a, b, args=(), args = (args,) maxiter = operator.index(maxiter) if xtol <= 0: - raise ValueError("xtol too small (%g <= 0)" % xtol) + raise ValueError(f"xtol too small ({xtol:g} <= 0)") if rtol < _rtol: raise ValueError(f"rtol too small ({rtol:g} < {_rtol:g})") f = _wrap_nan_raise(f) @@ -799,7 +799,7 @@ def brentq(f, a, b, args=(), args = (args,) maxiter = operator.index(maxiter) if xtol <= 0: - raise ValueError("xtol too small (%g <= 0)" % xtol) + raise ValueError(f"xtol too small ({xtol:g} <= 0)") if rtol < _rtol: raise ValueError(f"rtol too small ({rtol:g} < {_rtol:g})") f = _wrap_nan_raise(f) @@ -910,7 +910,7 @@ def brenth(f, a, b, args=(), args = (args,) maxiter = operator.index(maxiter) if xtol <= 0: - raise ValueError("xtol too small (%g <= 0)" % xtol) + raise ValueError(f"xtol too small ({xtol:g} <= 0)") if rtol < _rtol: raise ValueError(f"rtol too small ({rtol:g} < {_rtol:g})") f = _wrap_nan_raise(f) @@ -1125,9 +1125,9 @@ def start(self, f, a, b, args=()): self.args = args self.ab[:] = [a, b] if not np.isfinite(a) or np.imag(a) != 0: - raise ValueError("Invalid x value: %s " % (a)) + raise ValueError(f"Invalid x value: {a} ") if not np.isfinite(b) or np.imag(b) != 0: - raise ValueError("Invalid x value: %s " % (b)) + raise ValueError(f"Invalid x value: {b} ") fa = self._callf(a) if not np.isfinite(fa) or np.imag(fa) != 0: @@ -1377,20 +1377,20 @@ def toms748(f, a, b, args=(), k=1, method: toms748 """ if xtol <= 0: - raise ValueError("xtol too small (%g <= 0)" % xtol) + raise ValueError(f"xtol too small ({xtol:g} <= 0)") if rtol < _rtol / 4: raise ValueError(f"rtol too small ({rtol:g} < {_rtol/4:g})") maxiter = operator.index(maxiter) if maxiter < 1: raise ValueError("maxiter must be greater than 0") if not np.isfinite(a): - raise ValueError("a is not finite %s" % a) + raise ValueError(f"a is not finite {a}") if not np.isfinite(b): - raise ValueError("b is not finite %s" % b) + raise ValueError(f"b is not finite {b}") if a >= b: raise ValueError(f"a and b are not an interval [{a}, {b}]") if not k >= 1: - raise ValueError("k too small (%s < 1)" % k) + raise ValueError(f"k too small ({k} < 1)") if not isinstance(args, tuple): args = (args,) diff --git a/scipy/optimize/tests/test__differential_evolution.py b/scipy/optimize/tests/test__differential_evolution.py index f3c7af51d161..6467e0879c1c 100644 --- a/scipy/optimize/tests/test__differential_evolution.py +++ b/scipy/optimize/tests/test__differential_evolution.py @@ -376,7 +376,7 @@ def test_args_tuple_is_passed(self): args = (1., 2., 3.) def quadratic(x, *args): - if type(args) != tuple: + if not isinstance(args, tuple): raise ValueError('args should be a tuple') return args[0] + args[1] * x + args[2] * x**2. diff --git a/scipy/optimize/tests/test_zeros.py b/scipy/optimize/tests/test_zeros.py index 86606d8c4318..c043d1d997a7 100644 --- a/scipy/optimize/tests/test_zeros.py +++ b/scipy/optimize/tests/test_zeros.py @@ -498,7 +498,7 @@ def f(x): for method in methods: res = method(f, -1e8, 1e7, xtol=xtol, rtol=rtol) assert_allclose(root, res, atol=xtol, rtol=rtol, - err_msg='method %s' % method.__name__) + err_msg=f'method {method.__name__}') def test_gh_5557(): diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index cc6d8a5548bc..413574a4f637 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -705,16 +705,16 @@ def group_delay(system, w=512, whole=False, fs=2*pi): if np.any(singular): gd[singular] = 0 warnings.warn( - "The group delay is singular at frequencies [{}], setting to 0". - format(", ".join(f"{ws:.3f}" for ws in w[singular])), + "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 [{}], \ - around which a singularity may be present". - format(", ".join(f"{ws:.3f}" for ws in w[near_singular])), + "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 ) @@ -2379,10 +2379,10 @@ def iirdesign(wp, ws, gpass, gstop, analog=False, ftype='ellip', output='ba', try: ordfunc = filter_dict[ftype][1] except KeyError as e: - raise ValueError("Invalid IIR filter type: %s" % ftype) from e + raise ValueError(f"Invalid IIR filter type: {ftype}") from e except IndexError as e: - raise ValueError(("%s does not have order selection. Use " - "iirfilter function.") % ftype) from e + raise ValueError(f"{ftype} does not have order selection. " + "Use iirfilter function.") from e _validate_gpass_gstop(gpass, gstop) @@ -2576,15 +2576,15 @@ def iirfilter(N, Wn, rp=None, rs=None, btype='band', analog=False, try: btype = band_dict[btype] except KeyError as e: - raise ValueError("'%s' is an invalid bandtype for filter." % btype) from 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("'%s' is not a valid basic IIR filter." % ftype) from e + raise ValueError(f"'{ftype}' is not a valid basic IIR filter.") from e if output not in ['ba', 'zpk', 'sos']: - raise ValueError("'%s' is not a valid output form." % output) + 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") @@ -2613,7 +2613,7 @@ def iirfilter(N, Wn, rp=None, rs=None, btype='band', analog=False, "elliptic filter.") z, p, k = typefunc(N, rp, rs) else: - raise NotImplementedError("'%s' not implemented in iirfilter." % ftype) + raise NotImplementedError(f"'{ftype}' not implemented in iirfilter.") # Pre-warp frequencies for digital filter design if not analog: @@ -2651,7 +2651,7 @@ def iirfilter(N, Wn, rp=None, rs=None, btype='band', analog=False, elif btype == 'bandstop': z, p, k = lp2bs_zpk(z, p, k, wo=wo, bw=bw) else: - raise NotImplementedError("'%s' not implemented in iirfilter." % btype) + raise NotImplementedError(f"'{btype}' not implemented in iirfilter.") # Find discrete equivalent if necessary if not analog: @@ -3812,7 +3812,7 @@ def band_stop_obj(wp, ind, passb, stopb, gpass, gstop, type): d1 = special.ellipk([arg1 ** 2, 1 - arg1 ** 2]) n = (d0[0] * d1[1] / (d0[1] * d1[0])) else: - raise ValueError("Incorrect type: %s" % type) + raise ValueError(f"Incorrect type: {type}") return n @@ -4001,7 +4001,7 @@ def buttord(wp, ws, gpass, gstop, analog=False, fs=None): passb[0] * passb[1])) WN = np.sort(abs(WN)) else: - raise ValueError("Bad type: %s" % filter_type) + raise ValueError(f"Bad type: {filter_type}") wn = _postprocess_wn(WN, analog, fs) diff --git a/scipy/signal/_fir_filter_design.py b/scipy/signal/_fir_filter_design.py index 42b881462659..136378efc82f 100644 --- a/scipy/signal/_fir_filter_design.py +++ b/scipy/signal/_fir_filter_design.py @@ -237,8 +237,8 @@ def kaiserord(ripple, width): 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 is too " - "small for the Kaiser formula." % A) + 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 @@ -956,7 +956,7 @@ def firls(numtaps, bands, desired, *, weight=None, fs=None): # normalize bands 0->1 and make it 2 columns nyq = float(nyq) if nyq <= 0: - raise ValueError('nyq must be positive, got %s <= 0.' % nyq) + 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.") @@ -1243,7 +1243,7 @@ def minimum_phase(h: np.ndarray, n_fft = 2 ** int(np.ceil(np.log2(2 * (len(h) - 1) / 0.01))) n_fft = int(n_fft) if n_fft < len(h): - raise ValueError('n_fft must be at least len(h)==%s' % len(h)) + raise ValueError(f'n_fft must be at least len(h)=={len(h)}') if method == 'hilbert': w = np.arange(n_fft) * (2 * np.pi / n_fft * n_half) H = np.real(fft(h, n_fft) * np.exp(1j * w)) diff --git a/scipy/signal/_lti_conversion.py b/scipy/signal/_lti_conversion.py index 3402d294b717..52c6efbbfa53 100644 --- a/scipy/signal/_lti_conversion.py +++ b/scipy/signal/_lti_conversion.py @@ -528,6 +528,6 @@ def cont2discrete(system, dt, method="zoh", alpha=None): dd = c @ b * dt else: - raise ValueError("Unknown transformation method '%s'" % method) + raise ValueError(f"Unknown transformation method '{method}'") return ad, bd, cd, dd, dt diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index 0ce6676e1cb5..49b1ca01f502 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -2117,7 +2117,7 @@ def lfilter(b, a, x, axis=-1, zi=None): dtype = np.result_type(*inputs) if dtype.char not in 'fdgFDGO': - raise NotImplementedError("input type '%s' not supported" % dtype) + raise NotImplementedError(f"input type '{dtype}' not supported") b = np.array(b, dtype=dtype) a = np.asarray(a, dtype=dtype) @@ -4218,9 +4218,8 @@ def filtfilt(b, a, x, axis=-1, padtype='odd', padlen=None, method='pad', def _validate_pad(padtype, padlen, x, axis, ntaps): """Helper to validate padding for filtfilt""" if padtype not in ['even', 'odd', 'constant', None]: - raise ValueError(("Unknown value '%s' given to padtype. padtype " - "must be 'even', 'odd', 'constant', or None.") % - padtype) + raise ValueError(f"Unknown value '{padtype}' given to padtype. " + "padtype must be 'even', 'odd', 'constant', or None.") if padtype is None: padlen = 0 @@ -4337,7 +4336,7 @@ def sosfilt(sos, x, axis=-1, zi=None): inputs.append(np.asarray(zi)) dtype = np.result_type(*inputs) if dtype.char not in 'fdgFDGO': - raise NotImplementedError("input type '%s' not supported" % dtype) + raise NotImplementedError(f"input type '{dtype}' not supported") if zi is not None: zi = np.array(zi, dtype) # make a copy so that we can operate in place if zi.shape != x_zi_shape: diff --git a/scipy/signal/_spectral_py.py b/scipy/signal/_spectral_py.py index 6117ad421164..3c23a2ec5729 100644 --- a/scipy/signal/_spectral_py.py +++ b/scipy/signal/_spectral_py.py @@ -1761,8 +1761,8 @@ def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, .. versionadded:: 0.16.0 """ if mode not in ['psd', 'stft']: - raise ValueError("Unknown value for mode %s, must be one of: " - "{'psd', 'stft'}" % mode) + raise ValueError(f"Unknown value for mode {mode}, must be one of: " + "{'psd', 'stft'}") boundary_funcs = {'even': even_ext, 'odd': odd_ext, @@ -1771,8 +1771,8 @@ def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, None: None} if boundary not in boundary_funcs: - raise ValueError("Unknown boundary option '{}', must be one of: {}" - .format(boundary, list(boundary_funcs.keys()))) + 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 @@ -1898,7 +1898,7 @@ def detrend_func(d): elif scaling == 'spectrum': scale = 1.0 / win.sum()**2 else: - raise ValueError('Unknown scaling: %r' % scaling) + raise ValueError(f'Unknown scaling: {scaling!r}') if mode == 'stft': scale = np.sqrt(scale) diff --git a/scipy/signal/_waveforms.py b/scipy/signal/_waveforms.py index 324e99c1c78c..70bf0412f738 100644 --- a/scipy/signal/_waveforms.py +++ b/scipy/signal/_waveforms.py @@ -217,12 +217,12 @@ def gausspulse(t, fc=1000, bw=0.5, bwr=-6, tpr=-60, retquad=False, """ if fc < 0: - raise ValueError("Center frequency (fc=%.2f) must be >=0." % fc) + raise ValueError(f"Center frequency (fc={fc:.2f}) must be >=0.") if bw <= 0: - raise ValueError("Fractional bandwidth (bw=%.2f) must be > 0." % bw) + raise ValueError(f"Fractional bandwidth (bw={bw:.2f}) must be > 0.") if bwr >= 0: - raise ValueError("Reference level for bandwidth (bwr=%.2f) must " - "be < 0 dB" % bwr) + 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) @@ -461,9 +461,8 @@ def _chirp_phase(t, f0, t1, f1, method='linear', vertex_zero=True): phase = 2 * pi * (-sing * f0) * log(np.abs(1 - t/sing)) else: - raise ValueError("method must be 'linear', 'quadratic', 'logarithmic'," - " or 'hyperbolic', but a value of %r was given." - % method) + raise ValueError("method must be 'linear', 'quadratic', 'logarithmic', " + f"or 'hyperbolic', but a value of {method!r} was given.") return phase diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index 59b07f8e8789..960eaa71536f 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -3636,15 +3636,15 @@ def test_sos_consistency(): b, a = func(2, *args, output='ba') sos = func(2, *args, output='sos') - assert_allclose(sos, [np.hstack((b, a))], err_msg="%s(2,...)" % name) + assert_allclose(sos, [np.hstack((b, a))], err_msg=f"{name}(2,...)") zpk = func(3, *args, output='zpk') sos = func(3, *args, output='sos') - assert_allclose(sos, zpk2sos(*zpk), err_msg="%s(3,...)" % name) + assert_allclose(sos, zpk2sos(*zpk), err_msg=f"{name}(3,...)") zpk = func(4, *args, output='zpk') sos = func(4, *args, output='sos') - assert_allclose(sos, zpk2sos(*zpk), err_msg="%s(4,...)" % name) + assert_allclose(sos, zpk2sos(*zpk), err_msg=f"{name}(4,...)") class TestIIRNotch: diff --git a/scipy/signal/tests/test_peak_finding.py b/scipy/signal/tests/test_peak_finding.py index 77380c549636..b3a6c760d28f 100644 --- a/scipy/signal/tests/test_peak_finding.py +++ b/scipy/signal/tests/test_peak_finding.py @@ -830,7 +830,7 @@ def test_find_peaks_withnoise(self): diffs = np.abs(found_locs - act_locs) max_diffs = np.array(sigmas) / 5 np.testing.assert_array_less(diffs, max_diffs, 'Maximum location differed' + - 'by more than %s' % (max_diffs)) + f'by more than {max_diffs}') def test_find_peaks_nopeak(self): """ diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py index ea7634736bd5..8639a4da96ec 100644 --- a/scipy/signal/tests/test_signaltools.py +++ b/scipy/signal/tests/test_signaltools.py @@ -2362,7 +2362,7 @@ def test_equivalence(self): sos = zpk2sos(*zpk) y = filtfilt(b, a, x) y_sos = sosfiltfilt(sos, x) - assert_allclose(y, y_sos, atol=1e-12, err_msg='order=%s' % order) + assert_allclose(y, y_sos, atol=1e-12, err_msg=f'order={order}') def filtfilt_gust_opt(b, a, x): diff --git a/scipy/signal/tests/test_splines.py b/scipy/signal/tests/test_splines.py index bc4fbb3d3c4d..8c50388d0577 100644 --- a/scipy/signal/tests/test_splines.py +++ b/scipy/signal/tests/test_splines.py @@ -309,4 +309,4 @@ def test_symiir2_integer_input(self): s = np.where(np.arange(100) % 2, -1, 1) expected = symiirorder2(s.astype(float), 0.5, np.pi / 3.0) out = symiirorder2(s, 0.5, np.pi / 3.0) - assert_allclose(out, expected) \ No newline at end of file + assert_allclose(out, expected) diff --git a/scipy/signal/tests/test_upfirdn.py b/scipy/signal/tests/test_upfirdn.py index af23fd41c0f6..1d85ec01c6f7 100644 --- a/scipy/signal/tests/test_upfirdn.py +++ b/scipy/signal/tests/test_upfirdn.py @@ -211,7 +211,7 @@ def _random_factors(self, p_max, q_max, h_dtype, x_dtype): 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 == complex: + if h_dtype is complex: h += 1j * random_state.randint(len_h) tests.append(UpFIRDnCase(p, q, h, x_dtype)) diff --git a/scipy/signal/windows/_windows.py b/scipy/signal/windows/_windows.py index bafd48b24576..c1cf3016b3c4 100644 --- a/scipy/signal/windows/_windows.py +++ b/scipy/signal/windows/_windows.py @@ -2355,8 +2355,8 @@ def get_window(window, Nx, fftbins=True): else: winstr = window else: - raise ValueError("%s as window type is not supported." % - str(type(window))) from e + raise ValueError( + f"{str(type(window))} as window type is not supported.") from e try: winfunc = _win_equiv[winstr] diff --git a/scipy/sparse/_bsr.py b/scipy/sparse/_bsr.py index 2352a535855f..eb206ce1f22b 100644 --- a/scipy/sparse/_bsr.py +++ b/scipy/sparse/_bsr.py @@ -47,7 +47,7 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False, blocksize = (1,1) else: if not isshape(blocksize): - raise ValueError('invalid blocksize=%s' % blocksize) + raise ValueError(f'invalid blocksize={blocksize}') blocksize = tuple(blocksize) self.data = np.zeros((0,) + blocksize, getdtype(dtype, default=float)) @@ -106,8 +106,8 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False, try: arg1 = np.asarray(arg1) except Exception as e: - raise ValueError("unrecognized form for" - " %s_matrix constructor" % self.format) from 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) diff --git a/scipy/sparse/_coo.py b/scipy/sparse/_coo.py index 39cd87fe37aa..371e5ae285cb 100644 --- a/scipy/sparse/_coo.py +++ b/scipy/sparse/_coo.py @@ -407,8 +407,8 @@ def todia(self, copy=False): if len(diags) > 100: # probably undesired, should todia() have a maxdiags parameter? - warn("Constructing a DIA matrix with %d diagonals " - "is inefficient" % len(diags), + warn(f"Constructing a DIA matrix with {len(diags)} diagonals " + "is inefficient", SparseEfficiencyWarning, stacklevel=2) #initialize and fill in data array diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index c48ca8c104b7..4a96592551d1 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -68,8 +68,8 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False, *, maxprint=None): try: arg1 = np.asarray(arg1) except Exception as e: - raise ValueError("unrecognized form for" - " %s_matrix constructor" % self.format) from 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() diff --git a/scipy/sparse/_generate_sparsetools.py b/scipy/sparse/_generate_sparsetools.py index da9a23b25163..c2b4e9863bff 100644 --- a/scipy/sparse/_generate_sparsetools.py +++ b/scipy/sparse/_generate_sparsetools.py @@ -204,7 +204,7 @@ def newer(source, target): both exist and 'target' is the same age or younger than 'source'. """ if not os.path.exists(source): - raise ValueError("file '%s' does not exist" % os.path.abspath(source)) + raise ValueError(f"file '{os.path.abspath(source)}' does not exist") if not os.path.exists(target): return 1 diff --git a/scipy/sparse/_sputils.py b/scipy/sparse/_sputils.py index 79fd38260189..a10c801dfe9d 100644 --- a/scipy/sparse/_sputils.py +++ b/scipy/sparse/_sputils.py @@ -280,7 +280,7 @@ def validateaxis(axis) -> None: # not very useful for sparse matrices given their limited # dimensions, so let's make it explicit that they are not # allowed to be passed in - if axis_type == tuple: + if isinstance(axis, tuple): raise TypeError("Tuples are not accepted for the 'axis' parameter. " "Please pass in one of the following: " "{-2, -1, 0, 1, None}.") @@ -345,16 +345,16 @@ def check_shape(args, current_shape=None, *, allow_1d=False) -> tuple[int, ...]: if not negative_indexes: new_size = prod(new_shape) if new_size != current_size: - raise ValueError('cannot reshape array of size {} into shape {}' - .format(current_size, new_shape)) + 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('cannot reshape array of size {} into shape {}' - ''.format(current_size, err_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') @@ -377,8 +377,8 @@ def check_reshape_kwargs(kwargs): order = kwargs.pop('order', 'C') copy = kwargs.pop('copy', False) if kwargs: # Some unused kwargs remain - raise TypeError('reshape() got unexpected keywords arguments: {}' - .format(', '.join(kwargs.keys()))) + raise TypeError("reshape() got unexpected keywords arguments: " + f"{', '.join(kwargs.keys())}") return order, copy diff --git a/scipy/sparse/linalg/_eigen/arpack/arpack.py b/scipy/sparse/linalg/_eigen/arpack/arpack.py index 4fb6f3e4eb09..8f48fe6010a2 100644 --- a/scipy/sparse/linalg/_eigen/arpack/arpack.py +++ b/scipy/sparse/linalg/_eigen/arpack/arpack.py @@ -507,8 +507,7 @@ def __init__(self, n, k, tp, matvec, mode=1, M_matvec=None, raise ValueError("mode=%i not implemented" % mode) if which not in _SEUPD_WHICH: - raise ValueError("which must be one of %s" - % ' '.join(_SEUPD_WHICH)) + raise ValueError(f"which must be one of {' '.join(_SEUPD_WHICH)}") if k >= n: raise ValueError("k must be less than ndim(A), k=%d" % k) @@ -516,7 +515,7 @@ def __init__(self, n, k, tp, matvec, mode=1, M_matvec=None, ncv, v0, maxiter, which, tol) if self.ncv > n or self.ncv <= k: - raise ValueError("ncv must be k= n - 1: raise ValueError("k must be less than ndim(A)-1, k=%d" % k) @@ -699,7 +697,7 @@ def __init__(self, n, k, tp, matvec, mode=1, M_matvec=None, ncv, v0, maxiter, which, tol) if self.ncv > n or self.ncv <= k + 1: - raise ValueError("ncv must be k+1" % self.name + return f"<{self.name}>" class SymmetricParams: diff --git a/scipy/sparse/linalg/_eigen/tests/test_svds.py b/scipy/sparse/linalg/_eigen/tests/test_svds.py index 587d0eb6ede9..216cf05c79da 100644 --- a/scipy/sparse/linalg/_eigen/tests/test_svds.py +++ b/scipy/sparse/linalg/_eigen/tests/test_svds.py @@ -665,7 +665,7 @@ def test_small_sigma_sparse(self, shape, dtype): k = 5 (m, n) = shape S = random(m, n, density=0.1, random_state=rng) - if dtype == complex: + if dtype is complex: S = + 1j * random(m, n, density=0.1, random_state=rng) e = np.ones(m) e[0:5] *= 1e1 ** np.arange(-5, 0, 1) diff --git a/scipy/sparse/linalg/_expm_multiply.py b/scipy/sparse/linalg/_expm_multiply.py index 6bc8d83b75a7..4c4a4388423b 100644 --- a/scipy/sparse/linalg/_expm_multiply.py +++ b/scipy/sparse/linalg/_expm_multiply.py @@ -245,8 +245,8 @@ def _expm_multiply_simple(A, B, t=1.0, traceA=None, balance=False): 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('shapes of matrices A {} and B {} are incompatible' - .format(A.shape, B.shape)) + 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] @@ -443,7 +443,8 @@ def onenorm(self): def d(self, p): """ - Lazily estimate :math:`d_p(A) ~= || A^p ||^(1/p)` where :math:`||.||` is the 1-norm. + 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) @@ -645,8 +646,8 @@ def _expm_multiply_interval(A, B, start=None, stop=None, num=None, 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('shapes of matrices A {} and B {} are incompatible' - .format(A.shape, B.shape)) + 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] diff --git a/scipy/sparse/linalg/_interface.py b/scipy/sparse/linalg/_interface.py index 7c515167c326..5e4093dcc885 100644 --- a/scipy/sparse/linalg/_interface.py +++ b/scipy/sparse/linalg/_interface.py @@ -445,8 +445,7 @@ def dot(self, x): elif x.ndim == 2: return self.matmat(x) else: - raise ValueError('expected 1-d or 2-d array or matrix, got %r' - % x) + raise ValueError(f'expected 1-d or 2-d array or matrix, got {x!r}') def __matmul__(self, other): if np.isscalar(other): @@ -500,8 +499,7 @@ def _rdot(self, x): elif x.ndim == 2: return self.T.matmat(x.T).T else: - raise ValueError('expected 1-d or 2-d array or matrix, got %r' - % x) + raise ValueError(f'expected 1-d or 2-d array or matrix, got {x!r}') def __pow__(self, p): if np.isscalar(p): @@ -759,7 +757,7 @@ def __init__(self, A, p): if not isinstance(A, LinearOperator): raise ValueError('LinearOperator expected as A') if A.shape[0] != A.shape[1]: - raise ValueError('square LinearOperator expected, got %r' % A) + raise ValueError(f'square LinearOperator expected, got {A!r}') if not isintlike(p) or p < 0: raise ValueError('non-negative integer expected as p') diff --git a/scipy/sparse/linalg/_isolve/lgmres.py b/scipy/sparse/linalg/_isolve/lgmres.py index 3e105f5283a6..2dd64d404711 100644 --- a/scipy/sparse/linalg/_isolve/lgmres.py +++ b/scipy/sparse/linalg/_isolve/lgmres.py @@ -170,7 +170,7 @@ def lgmres(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=1000, M=None, callback= if inner_res_0 == 0: rnorm = nrm2(r_outer) raise RuntimeError("Preconditioner returned a zero vector; " - "|v| ~ %.1g, |M v| = 0" % rnorm) + f"|v| ~ {rnorm:.1g}, |M v| = 0") v0 = scal(1.0/inner_res_0, v0) diff --git a/scipy/sparse/linalg/_isolve/lsmr.py b/scipy/sparse/linalg/_isolve/lsmr.py index 3eeb05af62a6..e5c8e3772407 100644 --- a/scipy/sparse/linalg/_isolve/lsmr.py +++ b/scipy/sparse/linalg/_isolve/lsmr.py @@ -230,7 +230,7 @@ def lsmr(A, b, damp=0.0, atol=1e-6, btol=1e-6, conlim=1e8, print(' ') print('LSMR Least-squares solution of Ax = b\n') print(f'The matrix A has {m} rows and {n} columns') - print('damp = %20.14e\n' % (damp)) + 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') @@ -479,7 +479,7 @@ def lsmr(A, b, damp=0.0, atol=1e-6, btol=1e-6, conlim=1e8, 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(' normx =%8.1e' % (normx)) + print(f' normx ={normx:8.1e}') print(str1, str2) print(str3, str4) diff --git a/scipy/spatial/_kdtree.py b/scipy/spatial/_kdtree.py index b76c7e58168f..292210e22eb4 100644 --- a/scipy/spatial/_kdtree.py +++ b/scipy/spatial/_kdtree.py @@ -107,7 +107,7 @@ def __init__(self, maxes, mins): self.m, = self.maxes.shape def __repr__(self): - return "" % list(zip(self.mins, self.maxes)) + return f"" def volume(self): """Total volume.""" diff --git a/scipy/spatial/distance.py b/scipy/spatial/distance.py index ab058b7f04ed..f56c7cb37a84 100644 --- a/scipy/spatial/distance.py +++ b/scipy/spatial/distance.py @@ -2187,7 +2187,7 @@ def pdist(X, metric='euclidean', *, out=None, **kwargs): return _pdist_callable( X, metric=metric_info.dist_func, out=out, **kwargs) else: - raise ValueError('Unknown Distance Metric: %s' % mstr) + raise ValueError(f'Unknown Distance Metric: {mstr}') else: raise TypeError('2nd argument metric must be a string identifier ' 'or a function.') @@ -2342,9 +2342,8 @@ def squareform(X, force="no", checks=True): _distance_wrap.to_vector_from_squareform_wrap(X, v) return v else: - raise ValueError(('The first argument must be one or two dimensional ' - 'array. A %d-dimensional array is not ' - 'permitted') % len(s)) + 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): @@ -2421,22 +2420,20 @@ def is_valid_dm(D, tol=0.0, throw=False, name="D", warning=False): s = D.shape if len(D.shape) != 2: if name: - raise ValueError(('Distance matrix \'%s\' must have shape=2 ' - '(i.e. be two-dimensional).') % 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(('Distance matrix \'%s\' must be ' - 'symmetric.') % 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(('Distance matrix \'%s\' diagonal must ' - 'be zero.') % name) + raise ValueError(f"Distance matrix '{name}' diagonal must be zero.") else: raise ValueError('Distance matrix diagonal must be zero.') else: @@ -2446,7 +2443,7 @@ def is_valid_dm(D, tol=0.0, throw=False, name="D", warning=False): f'symmetric within tolerance {tol:5.5f}.') else: raise ValueError('Distance matrix must be symmetric within ' - 'tolerance %5.5f.' % tol) + 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 ' @@ -2516,9 +2513,8 @@ def is_valid_y(y, warning=False, throw=False, name=None): try: if len(y.shape) != 1: if name: - raise ValueError(('Condensed distance matrix \'%s\' must ' - 'have shape=1 (i.e. be one-dimensional).') - % name) + raise ValueError(f"Condensed distance matrix '{name}' must " + "have shape=1 (i.e. be one-dimensional).") else: raise ValueError('Condensed distance matrix must have shape=1 ' '(i.e. be one-dimensional).') @@ -2526,10 +2522,9 @@ def is_valid_y(y, warning=False, throw=False, name=None): d = int(np.ceil(np.sqrt(n * 2))) if (d * (d - 1) / 2) != n: if name: - raise ValueError(('Length n of condensed distance matrix ' - '\'%s\' must be a binomial coefficient, i.e.' - 'there must be a k such that ' - '(k \\choose 2)=n)!') % name) + raise ValueError(f"Length n of condensed distance matrix '{name}' " + "must be a binomial coefficient, i.e." + "there must be a k such that (k \\choose 2)=n)!") else: raise ValueError('Length n of condensed distance matrix must ' 'be a binomial coefficient, i.e. there must ' @@ -2987,7 +2982,7 @@ def cdist(XA, XB, metric='euclidean', *, out=None, **kwargs): return _cdist_callable( XA, XB, metric=metric_info.dist_func, out=out, **kwargs) else: - raise ValueError('Unknown Distance Metric: %s' % mstr) + raise ValueError(f'Unknown Distance Metric: {mstr}') else: raise TypeError('2nd argument metric must be a string identifier ' 'or a function.') diff --git a/scipy/spatial/tests/test_distance.py b/scipy/spatial/tests/test_distance.py index 14989b9a2bc6..9805d98e58b3 100644 --- a/scipy/spatial/tests/test_distance.py +++ b/scipy/spatial/tests/test_distance.py @@ -209,7 +209,7 @@ def _freq_weights(weights): return weights int_weights = weights.astype(int) if (weights != int_weights).any(): - raise ValueError("frequency (integer count-type) weights required %s" % weights) + raise ValueError(f"frequency (integer count-type) weights required {weights}") return int_weights diff --git a/scipy/spatial/tests/test_spherical_voronoi.py b/scipy/spatial/tests/test_spherical_voronoi.py index 621a961ebb6e..8bf4764e9e37 100644 --- a/scipy/spatial/tests/test_spherical_voronoi.py +++ b/scipy/spatial/tests/test_spherical_voronoi.py @@ -353,6 +353,6 @@ def test_region_types(self): for region in sv.regions: assert isinstance(region, list) sv.sort_vertices_of_regions() - assert type(sv.regions[0][0]) == dtype + assert isinstance(sv.regions[0][0], dtype) sv.sort_vertices_of_regions() - assert type(sv.regions[0][0]) == dtype + assert isinstance(sv.regions[0][0], dtype) diff --git a/scipy/spatial/transform/_rotation_spline.py b/scipy/spatial/transform/_rotation_spline.py index 1c07129721e3..867b724fdf44 100644 --- a/scipy/spatial/transform/_rotation_spline.py +++ b/scipy/spatial/transform/_rotation_spline.py @@ -376,9 +376,9 @@ def __init__(self, times, rotations): if len(times) != len(rotations): raise ValueError("Expected number of rotations to be equal to " - "number of timestamps given, got {} rotations " - "and {} timestamps." - .format(len(rotations), len(times))) + "number of timestamps given, " + f"got {len(rotations)} rotations " + f"and {len(times)} timestamps.") dt = np.diff(times) if np.any(dt <= 0): diff --git a/scipy/spatial/transform/tests/test_rotation.py b/scipy/spatial/transform/tests/test_rotation.py index 381ce7ca06f2..778c6ed19ceb 100644 --- a/scipy/spatial/transform/tests/test_rotation.py +++ b/scipy/spatial/transform/tests/test_rotation.py @@ -1026,7 +1026,7 @@ def test_reduction_no_indices(): def test_reduction_none_indices(): result = Rotation.identity().reduce(return_indices=True) - assert type(result) == tuple + assert type(result) is tuple assert len(result) == 3 reduced, left_best, right_best = result diff --git a/scipy/special/_generate_pyx.py b/scipy/special/_generate_pyx.py index 8044a6c79154..58d9d54120ea 100644 --- a/scipy/special/_generate_pyx.py +++ b/scipy/special/_generate_pyx.py @@ -306,7 +306,7 @@ def generate_loop(func_inputs, func_outputs, func_retval, for j, outtype in enumerate(func_outputs): body += " cdef %s ov%d\n" % (CY_TYPES[outtype], j+func_joff) - ftypes.append("%s *" % CY_TYPES[outtype]) + ftypes.append(f"{CY_TYPES[outtype]} *") fvars.append("&ov%d" % (j+func_joff)) outtypecodes.append(outtype) @@ -316,8 +316,10 @@ def generate_loop(func_inputs, func_outputs, func_retval, else: rv = "" - funcall = " {}(<{}(*)({}) noexcept nogil>func)({})\n".format( - rv, CY_TYPES[func_retval], ", ".join(ftypes), ", ".join(fvars)) + funcall = ( + f" {rv}(<{CY_TYPES[func_retval]}(*)({', '.join(ftypes)}) " + f"noexcept nogil>func)({', '.join(fvars)})\n" + ) # Cast-check inputs and call function input_checks = [] @@ -329,7 +331,7 @@ def generate_loop(func_inputs, func_outputs, func_retval, input_checks.append(chk) if input_checks: - body += " if %s:\n" % (" and ".join(input_checks)) + body += f" if {' and '.join(input_checks)}:\n" body += " " + funcall body += " else:\n" body += (" sf_error.error(func_name, sf_error.DOMAIN, " @@ -447,12 +449,11 @@ def get_prototypes(self, nptypes_for_h=False): + [C_TYPES[x] + ' *' for x in outarg]) cy_args = ([CY_TYPES[x] for x in inarg] + [CY_TYPES[x] + ' *' for x in outarg]) - c_proto = "{} (*)({})".format(C_TYPES[ret], ", ".join(c_args)) + c_proto = f"{C_TYPES[ret]} (*)({', '.join(c_args)})" if header.endswith("h") and nptypes_for_h: cy_proto = c_proto + "nogil" else: - cy_proto = ("{} (*)({}) noexcept nogil" - .format(CY_TYPES[ret], ", ".join(cy_args))) + cy_proto = f"{CY_TYPES[ret]} (*)({', '.join(cy_args)}) noexcept nogil" prototypes.append((func_name, c_proto, cy_proto, header)) return prototypes @@ -470,7 +471,7 @@ def cython_func_name(self, c_name, specialized=False, prefix="_func_", else: c_base_name, fused_part = c_name, "" if specialized: - return "{}{}{}".format(prefix, c_base_name, fused_part.replace(' ', '_')) + return f"{prefix}{c_base_name}{fused_part.replace(' ', '_')}" else: return f"{prefix}{c_base_name}" @@ -507,7 +508,7 @@ def __init__(self, name, signatures): super().__init__(name, signatures) self.doc = add_newdocs.get(name) if self.doc is None: - raise ValueError("No docstring for ufunc %r" % name) + raise ValueError(f"No docstring for ufunc {name!r}") self.doc = textwrap.dedent(self.doc).strip() def _get_signatures_and_loops(self, all_loops): @@ -579,11 +580,12 @@ def generate(self, all_loops): loops.append(loop_name) funcs.append(func_name) - toplevel += ("cdef np.PyUFuncGenericFunction ufunc_%s_loops[%d]\n" % - (self.name, len(loops))) + toplevel += ( + f"cdef np.PyUFuncGenericFunction ufunc_{self.name}_loops[{len(loops)}]\n" + ) toplevel += "cdef void *ufunc_%s_ptr[%d]\n" % (self.name, 2*len(funcs)) - toplevel += "cdef void *ufunc_%s_data[%d]\n" % (self.name, len(funcs)) - toplevel += "cdef char ufunc_%s_types[%d]\n" % (self.name, len(types)) + toplevel += f"cdef void *ufunc_{self.name}_data[{len(funcs)}]\n" + toplevel += f"cdef char ufunc_{self.name}_types[{len(types)}]\n" toplevel += 'cdef char *ufunc_{}_doc = (\n "{}")\n'.format( self.name, self.doc.replace("\\", "\\\\").replace('"', '\\"') @@ -629,13 +631,13 @@ def get_declaration(ufunc, c_name, c_proto, cy_proto, header, var_name = c_name.replace('[', '_').replace(']', '_').replace(' ', '_') if header.endswith('.pxd'): - defs.append("from .{} cimport {} as {}".format( - header[:-4], ufunc.cython_func_name(c_name, prefix=""), - ufunc.cython_func_name(c_name))) + defs.append( + f"from .{header[:-4]} cimport {ufunc.cython_func_name(c_name, prefix='')}" + f" as {ufunc.cython_func_name(c_name)}") # check function signature at compile time - proto_name = '_proto_%s_t' % var_name - defs.append("ctypedef %s" % (cy_proto.replace('(*)', proto_name))) + proto_name = f'_proto_{var_name}_t' + defs.append(f"ctypedef {cy_proto.replace('(*)', proto_name)}") defs.append(f"cdef {proto_name} *{proto_name}_var = " f"&{ufunc.cython_func_name(c_name, specialized=True)}") else: @@ -644,9 +646,9 @@ def get_declaration(ufunc, c_name, c_proto, cy_proto, header, new_name = f"{ufunc.cython_func_name(c_name)} \"{c_name}\"" proto_h_filename = os.path.basename(proto_h_filename) defs.append(f'cdef extern from r"{proto_h_filename}":') - defs.append(" cdef %s" % (cy_proto.replace('(*)', new_name))) + defs.append(f" cdef {cy_proto.replace('(*)', new_name)}") defs_h.append(f'#include "{header}"') - defs_h.append("%s;" % (c_proto.replace('(*)', c_name))) + defs_h.append(f"{c_proto.replace('(*)', c_name)};") return defs, defs_h, var_name @@ -727,7 +729,7 @@ def generate_ufuncs(fn_prefix, cxx_fn_prefix, ufuncs): for name in special_ufuncs if not name.startswith('_') ] ) - module_all = '__all__ = [{}]'.format(', '.join(all_ufuncs)) + module_all = f"__all__ = [{', '.join(all_ufuncs)}]" with open(filename, 'w') as f: f.write(UFUNCS_EXTRA_CODE_COMMON) @@ -780,7 +782,7 @@ def newer(source, target): both exist and 'target' is the same age or younger than 'source'. """ if not os.path.exists(source): - raise ValueError("file '%s' does not exist" % os.path.abspath(source)) + raise ValueError(f"file '{os.path.abspath(source)}' does not exist") if not os.path.exists(target): return 1 diff --git a/scipy/special/_mptestutils.py b/scipy/special/_mptestutils.py index f7b88f6b244b..9e519093dfac 100644 --- a/scipy/special/_mptestutils.py +++ b/scipy/special/_mptestutils.py @@ -333,7 +333,7 @@ def wrap(*a, **kw): sys.stderr.flush() try: r = func(*a, **kw) - sys.stderr.write("-> %r" % r) + sys.stderr.write(f"-> {r!r}") finally: sys.stderr.write("\n") sys.stderr.flush() diff --git a/scipy/special/_testutils.py b/scipy/special/_testutils.py index 68c1eb361114..744d7f62c65a 100644 --- a/scipy/special/_testutils.py +++ b/scipy/special/_testutils.py @@ -292,8 +292,8 @@ def eval_func_at_params(func, skip_mask=None): if np.any(bad_j): # Some bad results: inform what, where, and how bad msg = [""] - msg.append("Max |adiff|: %g" % diff[bad_j].max()) - msg.append("Max |rdiff|: %g" % rdiff[bad_j].max()) + msg.append(f"Max |adiff|: {diff[bad_j].max():g}") + msg.append(f"Max |rdiff|: {rdiff[bad_j].max():g}") msg.append("Bad results (%d out of %d) for the following points " "(in output %d):" % (np.sum(bad_j), point_count, output_num,)) @@ -315,7 +315,7 @@ def __repr__(self): else: is_complex = "" if self.dataname: - return "".format(self.func.__name__, is_complex, - os.path.basename(self.dataname)) + return (f"") else: return f"" diff --git a/scipy/special/_ufuncs.pyi b/scipy/special/_ufuncs.pyi index 3f0cf70149ae..9a769db8179e 100644 --- a/scipy/special/_ufuncs.pyi +++ b/scipy/special/_ufuncs.pyi @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any import numpy as np @@ -242,8 +242,8 @@ __all__ = [ 'zetac' ] -def geterr() -> Dict[str, str]: ... -def seterr(**kwargs: str) -> Dict[str, str]: ... +def geterr() -> dict[str, str]: ... +def seterr(**kwargs: str) -> dict[str, str]: ... class errstate: def __init__(self, **kargs: str) -> None: ... diff --git a/scipy/special/tests/test_kolmogorov.py b/scipy/special/tests/test_kolmogorov.py index bc427b0584ab..3c2989bd912f 100644 --- a/scipy/special/tests/test_kolmogorov.py +++ b/scipy/special/tests/test_kolmogorov.py @@ -117,7 +117,7 @@ def test_n_large(self): 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='Not all diffs negative %s' % dfs) + assert_(np.all(dfs <= 0), msg=f'Not all diffs negative {dfs}') class TestSmirnovi: diff --git a/scipy/special/utils/convert.py b/scipy/special/utils/convert.py index d4211ca0f3a9..00e5bdd57737 100644 --- a/scipy/special/utils/convert.py +++ b/scipy/special/utils/convert.py @@ -96,14 +96,14 @@ def dump_dataset(filename, data): fid = open(filename, 'w') try: for line in data: - fid.write("%s\n" % " ".join(line)) + fid.write(f"{' '.join(line)}\n") finally: fid.close() def dump_datasets(filename): base, ext = os.path.splitext(os.path.basename(filename)) - base += '_%s' % ext[1:] + base += f'_{ext[1:]}' datadir = os.path.join(DATA_DIR, base) os.makedirs(datadir) datasets = parse_ipp_file(filename) diff --git a/scipy/special/utils/makenpz.py b/scipy/special/utils/makenpz.py index fc9b1cce02a7..15e5c0282b7f 100644 --- a/scipy/special/utils/makenpz.py +++ b/scipy/special/utils/makenpz.py @@ -18,7 +18,7 @@ def newer(source, target): both exist and 'target' is the same age or younger than 'source'. """ if not os.path.exists(source): - raise ValueError("file '%s' does not exist" % os.path.abspath(source)) + raise ValueError(f"file '{os.path.abspath(source)}' does not exist") if not os.path.exists(target): return 1 diff --git a/scipy/stats/_binned_statistic.py b/scipy/stats/_binned_statistic.py index c624bb8c79be..c87492ce9e77 100644 --- a/scipy/stats/_binned_statistic.py +++ b/scipy/stats/_binned_statistic.py @@ -733,8 +733,8 @@ def _bin_edges(sample, bins=None, range=None): for i in builtins.range(Ndim): if range[i][1] < range[i][0]: raise ValueError( - "In {}range, start must be <= stop".format( - f"dimension {i + 1} of " if Ndim > 1 else "")) + 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. diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py index 787203f6752b..921a86fa8b74 100644 --- a/scipy/stats/_continuous_distns.py +++ b/scipy/stats/_continuous_distns.py @@ -52,7 +52,7 @@ def _remove_optimizer_parameters(kwds): kwds.pop('optimizer', None) kwds.pop('method', None) if kwds: - raise TypeError("Unknown arguments: %s." % kwds) + raise TypeError(f"Unknown arguments: {kwds}.") def _call_super_mom(fun): @@ -3344,7 +3344,7 @@ def func(x): value, info, ier, mesg = optimize.fsolve(func, x0, xtol=1e-11, full_output=True) if ier != 1: - raise RuntimeError("_digammainv: fsolve failed, y = %r" % y) + raise RuntimeError(f"_digammainv: fsolve failed, y = {y!r}") return value[0] diff --git a/scipy/stats/_distn_infrastructure.py b/scipy/stats/_distn_infrastructure.py index 256810ba0913..a7a0bb66b396 100644 --- a/scipy/stats/_distn_infrastructure.py +++ b/scipy/stats/_distn_infrastructure.py @@ -464,7 +464,7 @@ def _fit_determine_optimizer(optimizer): try: optimizer = getattr(optimize, optimizer) except AttributeError as e: - raise ValueError("%s is not a valid optimizer" % optimizer) from e + raise ValueError(f"{optimizer} is not a valid optimizer") from e return optimizer def _isintegral(x): @@ -826,7 +826,7 @@ def _construct_doc(self, docdict, shapes_vals=None): if shapes_vals is None: shapes_vals = () - vals = ', '.join('%.3g' % val for val in shapes_vals) + vals = ', '.join(f'{val:.3g}' for val in shapes_vals) tempdict['vals'] = vals tempdict['shapes_'] = self.shapes or '' @@ -2712,7 +2712,7 @@ def fit(self, data, *args, **kwds): optimizer = _fit_determine_optimizer(optimizer) # by now kwds must be empty, since everybody took what they needed if kwds: - raise TypeError("Unknown arguments: %s." % 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, @@ -3925,7 +3925,7 @@ def _iter_chunked(x0, x1, chunksize=4, inc=1): if inc == 0: raise ValueError('Cannot increment by zero.') if chunksize <= 0: - raise ValueError('Chunk size must be positive; got %s.' % chunksize) + raise ValueError(f'Chunk size must be positive; got {chunksize}.') s = 1 if inc > 0 else -1 stepsize = abs(chunksize * inc) diff --git a/scipy/stats/_kde.py b/scipy/stats/_kde.py index 40ff35934e21..e33d06eefbf4 100644 --- a/scipy/stats/_kde.py +++ b/scipy/stats/_kde.py @@ -301,9 +301,9 @@ def integrate_gaussian(self, mean, cov): cov = atleast_2d(cov) if mean.shape != (self.d,): - raise ValueError("mean does not have dimension %s" % self.d) + raise ValueError(f"mean does not have dimension {self.d}") if cov.shape != (self.d, self.d): - raise ValueError("covariance does not have dimension %s" % self.d) + raise ValueError(f"covariance does not have dimension {self.d}") # make mean a column vector mean = mean[:, newaxis] @@ -388,8 +388,7 @@ def integrate_box(self, low_bounds, high_bounds, maxpts=None): self.dataset, self.weights, self.covariance, **extra_kwds) if inform: - msg = ('An integral in _mvn.mvnun requires more points than %s' % - (self.d * 1000)) + msg = f'An integral in _mvn.mvnun requires more points than {self.d * 1000}' warnings.warn(msg, stacklevel=2) return value diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 2ed20f90778e..536f20687318 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -650,7 +650,7 @@ def probplot(x, sparams=(), dist='norm', fit=True, plot=None, rvalue=False): ymax = amax(x) posx = xmin + 0.70 * (xmax - xmin) posy = ymin + 0.01 * (ymax - ymin) - plot.text(posx, posy, "$R^2=%1.4f$" % r**2) + plot.text(posx, posy, f"$R^2={r ** 2:1.4f}$") if fit: return (osm, osr), (slope, intercept, r) diff --git a/scipy/stats/_mstats_basic.py b/scipy/stats/_mstats_basic.py index f8a97049aec1..ce9569bb6152 100644 --- a/scipy/stats/_mstats_basic.py +++ b/scipy/stats/_mstats_basic.py @@ -1973,10 +1973,10 @@ def _trimr1D(a, low_limit, up_limit, low_inclusive, up_inclusive): 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' + "(got %s)" % lolim) + raise ValueError(errmsg % 'beginning' + f"(got {lolim})") if uplim is not None: if uplim > 1. or uplim < 0: - raise ValueError(errmsg % 'end' + "(got %s)" % uplim) + raise ValueError(errmsg % 'end' + f"(got {uplim})") (loinc, upinc) = inclusive @@ -2253,10 +2253,10 @@ def _trimmed_stde_1D(a, low_limit, up_limit, low_inclusive, up_inclusive): 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' + "(got %s)" % lolim) + raise ValueError(errmsg % 'beginning' + f"(got {lolim})") if uplim is not None: if uplim > 1. or uplim < 0: - raise ValueError(errmsg % 'end' + "(got %s)" % uplim) + raise ValueError(errmsg % 'end' + f"(got {uplim})") (loinc, upinc) = inclusive if (axis is None): @@ -2655,10 +2655,10 @@ def _winsorize1D(a, low_limit, up_limit, low_include, up_include, 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' + "(got %s)" % lolim) + raise ValueError(errmsg % 'beginning' + f"(got {lolim})") if uplim is not None: if uplim > 1. or uplim < 0: - raise ValueError(errmsg % 'end' + "(got %s)" % uplim) + raise ValueError(errmsg % 'end' + f"(got {uplim})") (loinc, upinc) = inclusive @@ -3330,8 +3330,7 @@ def scoreatpercentile(data, per, limit=(), alphap=.4, betap=.4): """ if (per < 0) or (per > 100.): - raise ValueError("The percentile should be between 0. and 100. !" - " (got %s)" % per) + 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() diff --git a/scipy/stats/_multivariate.py b/scipy/stats/_multivariate.py index fe226704a7d5..aadd35b97882 100644 --- a/scipy/stats/_multivariate.py +++ b/scipy/stats/_multivariate.py @@ -478,7 +478,7 @@ def _process_parameters_psd(self, dim, mean, cov): rows, cols = cov.shape if rows != cols: msg = ("Array 'cov' must be square if it is two dimensional," - " but cov.shape = %s." % str(cov.shape)) + f" but cov.shape = {str(cov.shape)}.") else: msg = ("Dimension mismatch: array 'cov' is of shape %s," " but 'mean' is a vector of length %d.") @@ -1497,7 +1497,7 @@ def _dirichlet_check_input(alpha, x): if (np.abs(np.sum(x, 0) - 1.0) > 10e-10).any(): raise ValueError("The input vector 'x' must lie within the normal " - "simplex. but np.sum(x, 0) = %s." % np.sum(x, 0)) + f"simplex. but np.sum(x, 0) = {np.sum(x, 0)}.") return x @@ -2006,9 +2006,8 @@ def _process_parameters(self, df, scale): 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, but scale.scale = %s." - % str(scale.shape)) + 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("Array 'scale' must be at most two-dimensional," " but scale.ndim = %d" % scale.ndim) @@ -2041,15 +2040,15 @@ def _process_quantiles(self, x, dim): 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, but x.shape = %s." - % str(x.shape)) + 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 if they are three dimensional" - ", but x.shape = %s." % str(x.shape)) + 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("Quantiles must be at most two-dimensional with" " an additional dimension for multiple" @@ -2069,8 +2068,8 @@ def _process_size(self, size): size = size[np.newaxis] elif size.ndim > 1: raise ValueError('Size must be an integer or tuple of integers;' - ' thus must have dimension <= 1.' - ' Got size.ndim = %s' % str(tuple(size))) + ' thus must have dimension <= 1.' + f' Got size.ndim = {str(tuple(size))}') n = size.prod() shape = tuple(size) @@ -4772,7 +4771,7 @@ def _process_parameters(self, loc, shape, df): rows, cols = shape.shape if rows != cols: msg = ("Array 'cov' must be square if it is two dimensional," - " but cov.shape = %s." % str(shape.shape)) + 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.") diff --git a/scipy/stats/tests/common_tests.py b/scipy/stats/tests/common_tests.py index cd943a7c2c21..c296f1fea9e6 100644 --- a/scipy/stats/tests/common_tests.py +++ b/scipy/stats/tests/common_tests.py @@ -50,7 +50,7 @@ def check_moment(distfn, arg, m, v, msg): err_msg=msg + ' - 1st moment') else: # or np.isnan(m1), npt.assert_(np.isinf(m1), - msg + ' - 1st moment -infinite, m1=%s' % str(m1)) + msg + f' - 1st moment -infinite, m1={str(m1)}') if not np.isinf(v): npt.assert_almost_equal(m2 - m1 * m1, v, decimal=10, @@ -346,7 +346,7 @@ def check_freezing(distfn, args): def check_rvs_broadcast(distfunc, distname, allargs, shape, shape_only, otype): np.random.seed(123) sample = distfunc.rvs(*allargs) - assert_equal(sample.shape, shape, "%s: rvs failed to broadcast" % distname) + assert_equal(sample.shape, shape, f"{distname}: rvs failed to broadcast") if not shape_only: rvs = np.vectorize(lambda *allargs: distfunc.rvs(*allargs), otypes=otype) np.random.seed(123) diff --git a/scipy/stats/tests/test_discrete_distns.py b/scipy/stats/tests/test_discrete_distns.py index 3741cf79c96d..51e42085d4bb 100644 --- a/scipy/stats/tests/test_discrete_distns.py +++ b/scipy/stats/tests/test_discrete_distns.py @@ -645,4 +645,4 @@ def test_gh20692(self): dist = zipf(9) pmf = dist.pmf(k) pmf_k_int32 = dist.pmf(k_int32) - assert_equal(pmf, pmf_k_int32) \ No newline at end of file + assert_equal(pmf, pmf_k_int32) diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py index ae3ffa7a6f8e..53532c6ee414 100644 --- a/scipy/stats/tests/test_distributions.py +++ b/scipy/stats/tests/test_distributions.py @@ -5864,7 +5864,7 @@ def setup_method(self): def test_fit_w_non_finite_data_values(self, dist, args): """gh-10300""" if dist in self.fitSkipNonFinite: - pytest.skip("%s fit known to fail or deprecated" % dist) + 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) diff --git a/scipy/stats/tests/test_fit.py b/scipy/stats/tests/test_fit.py index f3264b945596..b3b31ac0b8ca 100644 --- a/scipy/stats/tests/test_fit.py +++ b/scipy/stats/tests/test_fit.py @@ -174,17 +174,17 @@ def test_cont_fit(distname, arg, method): if np.all(np.abs(diff) <= diffthreshold): break else: - txt = 'parameter: %s\n' % str(truearg) - txt += 'estimated: %s\n' % str(est) - txt += 'diff : %s\n' % str(diff) - raise AssertionError('fit not very good in %s\n' % distfn.name + txt) + 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='poor mle fit of (loc, scale) in %s' % name) + err_msg=f'poor mle fit of (loc, scale) in {name}') def test_non_default_loc_scale_mle_fit(): From 1fd0e270660d9745e5d63b382ad5df6af98e0253 Mon Sep 17 00:00:00 2001 From: lucascolley Date: Sat, 29 Jun 2024 21:09:32 +0100 Subject: [PATCH 472/500] MAINT: blame ignore for UP031/2 lint rule clean-up --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index c1c1b844af6e..85fe52a63fbe 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -36,3 +36,5 @@ ecef3490da68a0c53ba543c618bab0c8e15dccee 7b921fd28659b02544bfb46368ddadd1048b37aa # Style cleanup to always `import numpy as np` ceafa8e730887b81cf10d483ce375559ebd1de09 +# Clean-up for UP031, UP032 lint rules (gh-21029) +d1b5af016e907e037136b7a38e485437165490f2 From c6b7723efc77358401bdf4f2cbf5cc22e7d6a334 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 29 Jun 2024 17:31:30 -0600 Subject: [PATCH 473/500] MAINT: simplify `_integrate_pdf` * Simplify the decorator situation for `_integrate_pdf()` now that we no longer support Python `3.9.x`. [skip cirrus] [skip circle] --- scipy/stats/_continuous_distns.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py index 787203f6752b..409973ff6b55 100644 --- a/scipy/stats/_continuous_distns.py +++ b/scipy/stats/_continuous_distns.py @@ -3903,10 +3903,7 @@ def _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. - # The first argument to `vectorize` is `func.__get__(object)` for - # compatibility with Python 3.9. In Python 3.10, this can be - # simplified to just `func`. - @lambda func: np.vectorize(func.__get__(object), otypes=[np.float64]) + @lambda func: np.vectorize(func, otypes=[np.float64]) @staticmethod def _integrate_pdf(x0, x1, p, a, b): """ From 652f33866b7fcf19c935833f5a2a740522e6a399 Mon Sep 17 00:00:00 2001 From: Nick ODell Date: Sun, 30 Jun 2024 01:15:57 +0000 Subject: [PATCH 474/500] BLD: Enable open_memstream on newer glibc On glibc >= 2.10, if -std=c17 is set and no feature test macros are set, glibc will not define an open_memstream definition. musl has similar behavior. This causes SciPy to unnecessarily open a temporary file, when capturing the error stream could have been done in memory. --- scipy/_lib/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/_lib/meson.build b/scipy/_lib/meson.build index fe1252bd3f94..3ed2de0a66ac 100644 --- a/scipy/_lib/meson.build +++ b/scipy/_lib/meson.build @@ -87,7 +87,7 @@ py3.extension_module('_test_deprecation_def', conf_memstream = configuration_data() if meson.get_compiler('c').has_function('open_memstream', - prefix: '#include ') + prefix: '#define _POSIX_C_SOURCE 200809L\n#include ') conf_memstream.set('has_openmemstream', '1') else conf_memstream.set('has_openmemstream', '0') From 5084e1c9538c23c1c9f8bb6f091522087df66541 Mon Sep 17 00:00:00 2001 From: Matt Hall Date: Sun, 30 Jun 2024 15:17:11 +0200 Subject: [PATCH 475/500] DOC: spatial: Fix typo in `seuclidean` docstring (#21086) --- scipy/spatial/distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/spatial/distance.py b/scipy/spatial/distance.py index ab058b7f04ed..b77bb1b7fe44 100644 --- a/scipy/spatial/distance.py +++ b/scipy/spatial/distance.py @@ -915,7 +915,7 @@ def seuclidean(u, v, V): Input array. V : (N,) array_like `V` is an 1-D array of component variances. It is usually computed - among a larger collection vectors. + among a larger collection of vectors. Returns ------- From 04410471b8a71cc3d9eef69805d876cd89f60319 Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Sun, 30 Jun 2024 15:04:43 +0100 Subject: [PATCH 476/500] MAINT: adapt to array-api-strict 2.0 (#21074) * MAINT: adapt to array-api-strict 2.0 * DEV: enforce array-api-strict>=2.0 --- pyproject.toml | 12 +++++++----- pytest.ini | 1 + requirements/test.txt | 2 +- scipy/cluster/hierarchy.py | 4 ++-- scipy/cluster/tests/test_hierarchy.py | 6 +++--- scipy/conftest.py | 6 ++++++ scipy/stats/_stats_py.py | 12 +++++++----- scipy/stats/tests/test_stats.py | 3 ++- 8 files changed, 29 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d4e47db6f403..5d30afc18119 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,9 @@ maintainers = [ # release branches, see: # https://scipy.github.io/devdocs/dev/core-dev/index.html#version-ranges-for-numpy-and-other-dependencies requires-python = ">=3.10" -dependencies = ["numpy>=1.23.5"] # keep in sync with `min_numpy_version` in meson.build +dependencies = [ + "numpy>=1.23.5", +] # keep in sync with `min_numpy_version` in meson.build readme = "README.rst" classifiers = [ "Development Status :: 5 - Production/Stable", @@ -80,7 +82,7 @@ test = [ "scikit-umfpack", "pooch", "hypothesis>=6.30", - "array-api-strict", + "array-api-strict>=2.0", "Cython", "meson", 'ninja; sys_platform != "emscripten"', @@ -144,13 +146,13 @@ before-build = "bash {project}/tools/wheels/cibw_before_build_linux.sh {project} [tool.cibuildwheel.linux.environment] # /project will be the $PWD equivalent inside the docker used to build the wheel -PKG_CONFIG_PATH="/project/" +PKG_CONFIG_PATH = "/project/" [tool.cibuildwheel.macos] before-build = "bash {project}/tools/wheels/cibw_before_build_macos.sh {project}" [tool.cibuildwheel.macos.environment] -PKG_CONFIG_PATH="{project}" +PKG_CONFIG_PATH = "{project}" [tool.cibuildwheel.windows] before-build = "bash {project}/tools/wheels/cibw_before_build_win.sh {project}" @@ -158,6 +160,6 @@ repair-wheel-command = "bash ./tools/wheels/repair_windows.sh {wheel} {dest_dir} [tool.cibuildwheel.windows.environment] # This does not work because pkg-config does not like backslashes, -PKG_CONFIG_PATH="{project}" +PKG_CONFIG_PATH = "{project}" # do this instead (which will override this setting) # set CIBW_ENVIRONMENT_WINDOWS=PKG_CONFIG_PATH=PWD.replace('\\', '/') diff --git a/pytest.ini b/pytest.ini index 669ab8939e1b..2b31a2d4225c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -17,3 +17,4 @@ filterwarnings = ignore:.*`numpy.core` has been made officially private.*:DeprecationWarning ignore:.*In the future `np.long` will be defined as.*:FutureWarning ignore:.*JAX is multithreaded.*:RuntimeWarning + ignore:.*The 2023.12 version of the array API specification is still preliminary.*:UserWarning diff --git a/requirements/test.txt b/requirements/test.txt index 211e83a8b472..d11eabc6d9df 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -11,7 +11,7 @@ threadpoolctl # scikit-umfpack # circular dependency issues pooch hypothesis>=6.30 -array-api-strict +array-api-strict>=2.0 Cython meson ninja; sys_platform != "emscripten" diff --git a/scipy/cluster/hierarchy.py b/scipy/cluster/hierarchy.py index 321e8873d453..f4242fd7f307 100644 --- a/scipy/cluster/hierarchy.py +++ b/scipy/cluster/hierarchy.py @@ -3321,7 +3321,7 @@ def llf(id): if color_threshold is None or (isinstance(color_threshold, str) and color_threshold == 'default'): - color_threshold = max(Z[:, 2]) * 0.7 + color_threshold = xp.max(Z[:, 2]) * 0.7 R = {'icoord': icoord_list, 'dcoord': dcoord_list, 'ivl': ivl, 'leaves': lvs, 'color_list': color_list} @@ -3355,7 +3355,7 @@ def llf(id): above_threshold_color=above_threshold_color) if not no_plot: - mh = max(Z[:, 2]) + mh = xp.max(Z[:, 2]) _plot_dendrogram(icoord_list, dcoord_list, ivl, p, n, mh, orientation, no_labels, color_list, leaf_font_size=leaf_font_size, diff --git a/scipy/cluster/tests/test_hierarchy.py b/scipy/cluster/tests/test_hierarchy.py index d80991f7f5f9..0b6ca2da121f 100644 --- a/scipy/cluster/tests/test_hierarchy.py +++ b/scipy/cluster/tests/test_hierarchy.py @@ -946,9 +946,9 @@ def test_valid_orientation(self, xp): def test_labels_as_array_or_list(self, xp): # test for gh-12418 Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') - labels = xp.asarray([1, 3, 2, 6, 4, 5]) - result1 = dendrogram(Z, labels=labels, no_plot=True) - result2 = dendrogram(Z, labels=list(labels), no_plot=True) + labels = [1, 3, 2, 6, 4, 5] + result1 = dendrogram(Z, labels=xp.asarray(labels), no_plot=True) + result2 = dendrogram(Z, labels=labels, no_plot=True) assert result1 == result2 @pytest.mark.skipif(not have_matplotlib, reason="no matplotlib") diff --git a/scipy/conftest.py b/scipy/conftest.py index 4e7576660b5b..4aa72dfb5841 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -13,6 +13,7 @@ from scipy._lib._fpumode import get_fpu_mode from scipy._lib._testutils import FPUModeChangeWarning from scipy._lib._array_api import SCIPY_ARRAY_API, SCIPY_DEVICE +from scipy._lib import _pep440 try: from scipy_doctest.conftest import dt_config @@ -117,6 +118,11 @@ def check_fpu_mode(request): try: import array_api_strict xp_available_backends.update({'array_api_strict': array_api_strict}) + if _pep440.parse(array_api_strict.__version__) < _pep440.Version('2.0'): + raise ImportError("array-api-strict must be >= version 2.0") + array_api_strict.set_array_api_strict_flags( + api_version='2023.12' + ) except ImportError: pass diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 834031047a00..c9253082f15b 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -1079,11 +1079,12 @@ def moment(a, order=1, axis=0, nan_policy='propagate', *, center=None): calculate_mean = center is None and xp.any(order > 1) mean = xp.mean(a, axis=axis, keepdims=True) if calculate_mean else None mmnt = [] - for i in order: - if center is None and i > 1: - mmnt.append(_moment(a, i, axis, mean=mean)[np.newaxis, ...]) + for i in range(order.shape[0]): + order_i = order[i] + if center is None and order_i > 1: + mmnt.append(_moment(a, order_i, axis, mean=mean)[np.newaxis, ...]) else: - mmnt.append(_moment(a, i, axis, mean=center)[np.newaxis, ...]) + mmnt.append(_moment(a, order_i, axis, mean=center)[np.newaxis, ...]) return xp.concat(mmnt, axis=0) else: return _moment(a, order, axis, mean=center) @@ -4449,7 +4450,8 @@ def _pearsonr_fisher_ci(r, n, confidence_level, alternative): zr = xp.atanh(r) ones = xp.ones_like(r) - n, confidence_level = xp.asarray([n, confidence_level], dtype=r.dtype) + n = xp.asarray(n, dtype=r.dtype) + confidence_level = xp.asarray(confidence_level, dtype=r.dtype) if n > 3: se = xp.sqrt(1 / (n - 3)) if alternative == "two-sided": diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 7c7629d2a563..920c88d3b85d 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -6397,7 +6397,8 @@ def test_against_R(self, alternative, xp): # test_result@test$p.value test_name = self.test_name test_fun = getattr(stats, test_name) - ref_statistic, ref_pvalue = xp.asarray(self.case_ref) + ref_statistic= xp.asarray(self.case_ref[0]) + ref_pvalue = xp.asarray(self.case_ref[1]) kwargs = {} if alternative in {'less', 'greater'}: From 5247c04bc9fbd0c4834d443d410bf9e9a0e1d5d7 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 30 Jun 2024 16:24:27 -0700 Subject: [PATCH 477/500] ENH: `stats.differential_entropy`: add array API support (#21076) * ENH: stats.differential_entropy: add array API support * TST: stats.differential_entropy: improve test precision; begin xp-conversion * TST: stats.differential_entropy: convert to array API * MAINT: stats.differential_entropy: adjustments per review * MAINT: stats.differential_entropy: appease mypy --- scipy/stats/_entropy.py | 70 ++++++++--------- scipy/stats/tests/test_entropy.py | 124 ++++++++++++++++-------------- 2 files changed, 101 insertions(+), 93 deletions(-) diff --git a/scipy/stats/_entropy.py b/scipy/stats/_entropy.py index 4d033bae4752..5c575fff11d0 100644 --- a/scipy/stats/_entropy.py +++ b/scipy/stats/_entropy.py @@ -9,7 +9,7 @@ import numpy as np from scipy import special from ._axis_nan_policy import _axis_nan_policy_factory, _broadcast_arrays -from scipy._lib._array_api import array_namespace +from scipy._lib._array_api import array_namespace, xp_moveaxis_to_end __all__ = ['entropy', 'differential_entropy'] @@ -317,9 +317,10 @@ class of statistics based on spacings. Scandinavian Journal of >>> plt.title('Entropy Estimator Error (Exponential Distribution)') """ - values = np.asarray(values) - values = np.moveaxis(values, axis, -1) - n = values.shape[-1] # number of observations + xp = array_namespace(values) + values = xp.asarray(values) + values = xp_moveaxis_to_end(values, axis, xp=xp) + n = values.shape[-1] # type: ignore[union-attr] if window_length is None: window_length = math.floor(math.sqrt(n) + 0.5) @@ -333,7 +334,7 @@ class of statistics based on spacings. Scandinavian Journal of if base is not None and base <= 0: raise ValueError("`base` must be a positive number or `None`.") - sorted_data = np.sort(values, axis=-1) + sorted_data = xp.sort(values, axis=-1) methods = {"vasicek": _vasicek_entropy, "van es": _van_es_entropy, @@ -353,74 +354,73 @@ class of statistics based on spacings. Scandinavian Journal of else: method = 'vasicek' - res = methods[method](sorted_data, window_length) + res = methods[method](sorted_data, window_length, xp=xp) if base is not None: - res /= np.log(base) + res /= math.log(base) return res -def _pad_along_last_axis(X, m): +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 = np.array(X.shape) - shape[-1] = m - Xl = np.broadcast_to(X[..., [0]], shape) # [0] vs 0 to maintain shape - Xr = np.broadcast_to(X[..., [-1]], shape) - return np.concatenate((Xl, X, Xr), axis=-1) + 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): +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) + X = _pad_along_last_axis(X, m, xp=xp) differences = X[..., 2 * m:] - X[..., : -2 * m:] - logs = np.log(n/(2*m) * differences) - return np.mean(logs, axis=-1) + logs = xp.log(n/(2*m) * differences) + return xp.mean(logs, axis=-1) -def _van_es_entropy(X, m): +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) * np.sum(np.log((n+1)/m * difference), axis=-1) - k = np.arange(m, n+1) - return term1 + np.sum(1/k) + np.log(m) - np.log(n+1) + term1 = 1/(n-m) * xp.sum(xp.log((n+1)/m * difference), axis=-1) + k = xp.arange(m, n+1, dtype=term1.dtype) + return term1 + xp.sum(1/k) + math.log(m) - math.log(n+1) -def _ebrahimi_entropy(X, m): +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) + X = _pad_along_last_axis(X, m, xp=xp) differences = X[..., 2 * m:] - X[..., : -2 * m:] - i = np.arange(1, n+1).astype(float) - ci = np.ones_like(i)*2 + i = xp.arange(1, n+1, dtype=X.dtype) + ci = xp.ones_like(i)*2 ci[i <= m] = 1 + (i[i <= m] - 1)/m ci[i >= n - m + 1] = 1 + (n - i[i >= n-m+1])/m - logs = np.log(n * differences / (ci * m)) - return np.mean(logs, axis=-1) + logs = xp.log(n * differences / (ci * m)) + return xp.mean(logs, axis=-1) -def _correa_entropy(X, m): +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) + X = _pad_along_last_axis(X, m, xp=xp) - i = np.arange(1, n+1) - dj = np.arange(-m, m+1)[:, None] + i = xp.arange(1, n+1) + dj = xp.arange(-m, m+1)[:, None] j = i + dj j0 = j + m - 1 # 0-indexed version of j - Xibar = np.mean(X[..., j0], axis=-2, keepdims=True) + Xibar = xp.mean(X[..., j0], axis=-2, keepdims=True) difference = X[..., j0] - Xibar - num = np.sum(difference*dj, axis=-2) # dj is d-i - den = n*np.sum(difference**2, axis=-2) - return -np.mean(np.log(num/den), axis=-1) + 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/scipy/stats/tests/test_entropy.py b/scipy/stats/tests/test_entropy.py index 4f72fa06f674..9d79891d7173 100644 --- a/scipy/stats/tests/test_entropy.py +++ b/scipy/stats/tests/test_entropy.py @@ -3,11 +3,12 @@ from pytest import raises as assert_raises import numpy as np -from numpy.testing import assert_allclose from scipy import stats +from scipy.stats import norm, expon # type: ignore[attr-defined] from scipy.conftest import array_api_compatible -from scipy._lib._array_api import xp_assert_close, xp_assert_equal, xp_assert_less +from scipy._lib._array_api import (xp_assert_close, xp_assert_equal, xp_assert_less, + is_jax, is_array_api_strict, array_namespace) class TestEntropy: @array_api_compatible @@ -129,6 +130,8 @@ def test_input_validation(self, xp): stats.entropy(x, base=-2) +@array_api_compatible +@pytest.mark.usefixtures("skip_xp_backends") class TestDifferentialEntropy: """ Vasicek results are compared with the R package vsgoftest. @@ -140,52 +143,47 @@ class TestDifferentialEntropy: """ - def test_differential_entropy_vasicek(self): + 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') - assert_allclose(entropy, 1.342551, rtol=1e-6) + xp_assert_close(entropy, xp.asarray(1.342551187000946)) entropy = stats.differential_entropy(values, window_length=1, method='vasicek') - assert_allclose(entropy, 1.122044, rtol=1e-6) + xp_assert_close(entropy, xp.asarray(1.122044177725947)) entropy = stats.differential_entropy(values, window_length=8, method='vasicek') - assert_allclose(entropy, 1.349401, rtol=1e-6) + xp_assert_close(entropy, xp.asarray(1.349401487550325)) - def test_differential_entropy_vasicek_2d_nondefault_axis(self): + 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') - assert_allclose( - entropy, - [1.342551, 1.341826, 1.293775], - rtol=1e-6, - ) + 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') - assert_allclose( - entropy, - [1.122044, 1.102944, 1.129616], - rtol=1e-6, - ) + 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') - assert_allclose( - entropy, - [1.349401, 1.338514, 1.292332], - rtol=1e-6, - ) + ref = xp.asarray([1.349401487550325, 1.338514126301301, 1.292331889365405]) + xp_assert_close(entropy, ref) + - def test_differential_entropy_raises_value_error(self): + 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 " @@ -208,25 +206,32 @@ def test_differential_entropy_raises_value_error(self): axis=1, ) - def test_base_differential_entropy_with_axis_0_is_equal_to_default(self): + @pytest.mark.skip_xp_backends('jax.numpy', + reason=["JAX doesn't support item assignment"]) + 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) - assert_allclose(entropy, default_entropy) + xp_assert_close(entropy, default_entropy) - def test_base_differential_entropy_transposed(self): + @pytest.mark.skip_xp_backends('jax.numpy', + reason=["JAX doesn't support item assignment"]) + 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()) - assert_allclose( - stats.differential_entropy(values.T).T, + xp_assert_close( + stats.differential_entropy(values.T), stats.differential_entropy(values, axis=1), ) - def test_input_validation(self): + def test_input_validation(self, xp): x = np.random.rand(10) + x = xp.asarray(x.tolist()) message = "`base` must be a positive number or `None`." with pytest.raises(ValueError, match=message): @@ -238,13 +243,18 @@ def test_input_validation(self): @pytest.mark.parametrize('method', ['vasicek', 'van es', 'ebrahimi', 'correa']) - def test_consistency(self, method): + def test_consistency(self, method, xp): + if is_jax(xp) and method == 'ebrahimi': + pytest.xfail("Needs array assignment.") + elif is_array_api_strict(xp) and method == 'correa': + pytest.xfail("Needs fancy indexing.") # test that method is a consistent estimator n = 10000 if method == 'correa' else 1000000 rvs = stats.norm.rvs(size=n, random_state=0) - expected = stats.norm.entropy() + rvs = xp.asarray(rvs.tolist()) + expected = xp.asarray(float(stats.norm.entropy())) res = stats.differential_entropy(rvs, method=method) - assert_allclose(res, expected, rtol=0.005) + 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) @@ -254,22 +264,6 @@ def test_consistency(self, method): 'ebrahimi': (0.128, 0.109) } - @pytest.mark.parametrize('method, expected', - list(norm_rmse_std_cases.items())) - def test_norm_rmse_std(self, method, expected): - # 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 - rmse_expected, std_expected = expected - rvs = stats.norm.rvs(size=(reps, n), random_state=0) - true_entropy = stats.norm.entropy() - res = stats.differential_entropy(rvs, window_length=m, - method=method, axis=-1) - assert_allclose(np.sqrt(np.mean((res - true_entropy)**2)), - rmse_expected, atol=0.005) - assert_allclose(np.std(res), std_expected, atol=0.002) - # values from differential_entropy reference [6], table 2, n=50, m=7 expon_rmse_std_cases = { # method: (RMSE, STD) 'vasicek': (0.194, 0.148), @@ -278,27 +272,41 @@ def test_norm_rmse_std(self, method, expected): 'ebrahimi': (0.151, 0.148) } - @pytest.mark.parametrize('method, expected', - list(expon_rmse_std_cases.items())) - def test_expon_rmse_std(self, method, expected): + rmse_std_cases = {norm: norm_rmse_std_cases, + expon: expon_rmse_std_cases} + + @pytest.mark.parametrize('method', ['vasicek', 'van es', 'ebrahimi', 'correa']) + @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. + if is_jax(xp) and method == 'ebrahimi': + pytest.xfail("Needs array assignment.") + elif is_array_api_strict(xp) and method == 'correa': + pytest.xfail("Needs fancy indexing.") + reps, n, m = 10000, 50, 7 - rmse_expected, std_expected = expected - rvs = stats.expon.rvs(size=(reps, n), random_state=0) - true_entropy = stats.expon.entropy() + 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) - assert_allclose(np.sqrt(np.mean((res - true_entropy)**2)), + xp_assert_close(xp.sqrt(xp.mean((res - true_entropy)**2)), rmse_expected, atol=0.005) - assert_allclose(np.std(res), std_expected, atol=0.002) + xp_test = array_namespace(res) + xp_assert_close(xp_test.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): + def test_method_auto(self, n, method, xp): + if is_jax(xp) and method == 'ebrahimi': + pytest.xfail("Needs array assignment.") 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) - assert res1 == res2 + xp_assert_equal(res1, res2) From cf4c090fea7448f74013bbb90cc0b5b50e1c63f4 Mon Sep 17 00:00:00 2001 From: "Tom M. Ragonneau" <33006763+ragonneau@users.noreply.github.com> Date: Mon, 1 Jul 2024 01:31:25 +0200 Subject: [PATCH 478/500] DOC: optimise: Add default options for COBYQA in doc (#21081) --- scipy/optimize/_cobyqa_py.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/scipy/optimize/_cobyqa_py.py b/scipy/optimize/_cobyqa_py.py index 4928fca9c162..927f318d5bf5 100644 --- a/scipy/optimize/_cobyqa_py.py +++ b/scipy/optimize/_cobyqa_py.py @@ -18,28 +18,33 @@ def _minimize_cobyqa(fun, x0, args=(), bounds=None, constraints=(), ------- disp : bool Set to True to print information about the optimization procedure. + Default is ``False``. maxfev : int - Maximum number of function evaluations. + Maximum number of function evaluations. Default is ``500 * n``, where + ``n`` is the number of variables. maxiter : int - Maximum number of iterations. + Maximum number of iterations. Default is ``1000 * n``, where ``n`` is + the number of variables. f_target : float Target value for the objective function. The optimization procedure is terminated when the objective function value of a feasible point (see - `feasibility_tol` below) is less than or equal to this target. + `feasibility_tol` below) is less than or equal to this target. Default + is ``-numpy.inf``. feasibility_tol : float - Absolute tolerance for the constraint violation. + Absolute tolerance for the constraint violation. Default is ``1e-8``. initial_tr_radius : float Initial trust-region radius. Typically, this value should be in the order of one tenth of the greatest expected change to the variables. + Default is ``1.0``. final_tr_radius : float Final trust-region radius. It should indicate the accuracy required in the final values of the variables. If provided, this option overrides - the value of `tol` in the `minimize` function. + the value of `tol` in the `minimize` function. Default is ``1e-6``. scale : bool Set to True to scale the variables according to the bounds. If True and if all the lower and upper bounds are finite, the variables are scaled to be within the range :math:`[-1, 1]`. If any of the lower or upper - bounds is infinite, the variables are not scaled. + bounds is infinite, the variables are not scaled. Default is ``False``. References ---------- From dfc964cfcfa5d158dc4ba7687e13e98b194ce800 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 30 Jun 2024 22:04:53 -0700 Subject: [PATCH 479/500] DOC: optimize: remove inadvertent block quote indentation (#21087) * DOC: optimize: remove inadvertent block quote indentation Many lists in the optimize documentation are indented relative to the surrounding text. The indentation indicates to Sphinx that the text is to be formatted as a block quote, and our theme represents that with a vertical bar. Much of this appears to be unintentional. This commit removes the extra indentation to eliminate these vertical bars. --- scipy/optimize/_basinhopping.py | 11 +- scipy/optimize/_constraints.py | 10 +- scipy/optimize/_differentialevolution.py | 85 ++++--- scipy/optimize/_dual_annealing.py | 10 +- scipy/optimize/_linprog.py | 129 ++++++----- scipy/optimize/_lsq/least_squares.py | 219 +++++++++--------- scipy/optimize/_lsq/lsq_linear.py | 74 +++--- scipy/optimize/_minimize.py | 114 ++++----- scipy/optimize/_minpack_py.py | 44 ++-- scipy/optimize/_nonlin.py | 31 ++- scipy/optimize/_optimize.py | 83 ++++--- scipy/optimize/_qap.py | 14 +- scipy/optimize/_root.py | 97 ++++---- scipy/optimize/_root_scalar.py | 16 +- scipy/optimize/_shgo.py | 76 +++--- scipy/optimize/_slsqp_py.py | 26 +-- scipy/optimize/_tnc.py | 24 +- .../minimize_trustregion_constr.py | 34 +-- scipy/optimize/_zeros_py.py | 32 +-- 19 files changed, 565 insertions(+), 564 deletions(-) diff --git a/scipy/optimize/_basinhopping.py b/scipy/optimize/_basinhopping.py index 333a7af410de..7796c41cc77e 100644 --- a/scipy/optimize/_basinhopping.py +++ b/scipy/optimize/_basinhopping.py @@ -395,11 +395,11 @@ def basinhopping(func, x0, niter=100, T=1.0, stepsize=0.5, Extra keyword arguments to be passed to the local minimizer `scipy.optimize.minimize` Some important options could be: - method : str - The minimization method (e.g. ``"L-BFGS-B"``) - args : tuple - Extra arguments passed to the objective function (`func`) and - its derivatives (Jacobian, Hessian). + method : str + The minimization method (e.g. ``"L-BFGS-B"``) + args : tuple + Extra arguments passed to the objective function (`func`) and + its derivatives (Jacobian, Hessian). take_step : callable ``take_step(x)``, optional Replace the default step-taking routine with this routine. The default @@ -433,7 +433,6 @@ def basinhopping(func, x0, niter=100, T=1.0, stepsize=0.5, Stop the run if the global minimum candidate remains the same for this number of iterations. 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, diff --git a/scipy/optimize/_constraints.py b/scipy/optimize/_constraints.py index 1c7ff5e170b2..3cdb981635af 100644 --- a/scipy/optimize/_constraints.py +++ b/scipy/optimize/_constraints.py @@ -46,8 +46,10 @@ class NonlinearConstraint: where element (i, j) is the partial derivative of f[i] with respect to x[j]). The keywords {'2-point', '3-point', 'cs'} select a finite difference scheme for the numerical estimation. - A callable must have the following signature: - ``jac(x) -> {ndarray, sparse matrix}, shape (m, n)``. + A callable must have the following signature:: + + jac(x) -> {ndarray, sparse matrix}, shape (m, n) + Default is '2-point'. hess : {callable, '2-point', '3-point', 'cs', HessianUpdateStrategy, None}, optional Method for computing the Hessian matrix. The keywords @@ -56,8 +58,8 @@ class NonlinearConstraint: `HessianUpdateStrategy` interface can be used to approximate the Hessian. Currently available implementations are: - - `BFGS` (default option) - - `SR1` + - `BFGS` (default option) + - `SR1` A callable must return the Hessian matrix of ``dot(fun, v)`` and must have the following signature: diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index 8555723d4a5b..df102ceb3d35 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -27,7 +27,7 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', init='latinhypercube', atol=0, updating='immediate', workers=1, constraints=(), x0=None, *, integrality=None, vectorized=False): - """Finds the global minimum of a multivariate function. + r"""Finds the global minimum of a multivariate function. The differential evolution method [1]_ is stochastic in nature. It does not use gradient methods to find the minimum, and can search large areas @@ -47,10 +47,10 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', bounds : sequence or `Bounds` Bounds for variables. There are two ways to specify the bounds: - 1. Instance of `Bounds` class. - 2. ``(min, max)`` pairs for each element in ``x``, defining the - finite lower and upper bounds for the optimizing argument of - `func`. + 1. Instance of `Bounds` class. + 2. ``(min, max)`` pairs for each element in ``x``, defining the + finite lower and upper bounds for the optimizing argument of + `func`. The total number of bounds is used to determine the number of parameters, N. If there are parameters whose bounds are equal the total @@ -62,18 +62,18 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', strategy : {str, callable}, optional The differential evolution strategy to use. Should be one of: - - 'best1bin' - - 'best1exp' - - 'rand1bin' - - 'rand1exp' - - 'rand2bin' - - 'rand2exp' - - 'randtobest1bin' - - 'randtobest1exp' - - 'currenttobest1bin' - - 'currenttobest1exp' - - 'best2exp' - - 'best2bin' + - 'best1bin' + - 'best1exp' + - 'rand1bin' + - 'rand1exp' + - 'rand2bin' + - 'rand2exp' + - 'randtobest1bin' + - 'randtobest1exp' + - 'currenttobest1bin' + - 'currenttobest1exp' + - 'best2exp' + - 'best2bin' The default is 'best1bin'. Strategies that may be implemented are outlined in 'Notes'. @@ -110,7 +110,7 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', respectively. mutation : float or tuple(float, float), optional The mutation constant. In the literature this is also known as - differential weight, being denoted by F. + differential weight, being denoted by :math:`F`. If specified as a float it should be in the range [0, 2]. If specified as a tuple ``(min, max)`` dithering is employed. Dithering randomly changes the mutation constant on a generation by generation @@ -135,9 +135,9 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', disp : bool, optional Prints the evaluated `func` at every iteration. callback : callable, optional - A callable called after each iteration. Has the signature: + A callable called after each iteration. Has the signature:: - ``callback(intermediate_result: OptimizeResult)`` + callback(intermediate_result: OptimizeResult) where ``intermediate_result`` is a keyword parameter containing an `OptimizeResult` with attributes ``x`` and ``fun``, the best solution @@ -145,9 +145,9 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', of the parameter must be ``intermediate_result`` for the callback to be passed an `OptimizeResult`. - The callback also supports a signature like: + The callback also supports a signature like:: - ``callback(x, convergence: float=val)`` + callback(x, convergence: float=val) ``val`` represents the fractional value of the population convergence. When ``val`` is greater than ``1.0``, the function halts. @@ -171,14 +171,15 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', Specify which type of population initialization is performed. Should be one of: - - 'latinhypercube' - - 'sobol' - - 'halton' - - 'random' - - array specifying the initial population. The array should have - shape ``(S, N)``, where S is the total population size and N is - the number of parameters. - `init` is clipped to `bounds` before use. + - 'latinhypercube' + - 'sobol' + - 'halton' + - 'random' + - array specifying the initial population. The array should have + shape ``(S, N)``, where S is the total population size and N is + the number of parameters. + + `init` is clipped to `bounds` before use. The default is 'latinhypercube'. Latin Hypercube sampling tries to maximize coverage of the available parameter space. @@ -302,8 +303,9 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', .. math:: - b' = x_0 + mutation * (x_{r_0} - x_{r_1}) + b' = x_0 + F \cdot (x_{r_0} - x_{r_1}) + where :math:`F` is the `mutation` parameter. A trial vector is then constructed. Starting with a randomly chosen ith parameter the trial is sequentially filled (in modulo) with parameters from ``b'`` or the original candidate. The choice of whether to use ``b'`` @@ -319,22 +321,13 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', The other strategies available are outlined in Qiang and Mitchell (2014) [3]_. - .. math:: - rand1* : b' = x_{r_0} + mutation*(x_{r_1} - x_{r_2}) - - rand2* : b' = x_{r_0} + mutation*(x_{r_1} + x_{r_2} - - x_{r_3} - x_{r_4}) - - best1* : b' = x_0 + mutation*(x_{r_0} - x_{r_1}) - - best2* : b' = x_0 + mutation*(x_{r_0} + x_{r_1} - - x_{r_2} - x_{r_3}) - - currenttobest1* : b' = x_i + mutation*(x_0 - x_i - + x_{r_0} - x_{r_1}) - randtobest1* : b' = x_{r_0} + mutation*(x_0 - x_{r_0} - + x_{r_1} - x_{r_2}) + - ``rand1`` : :math:`b' = x_{r_0} + F \cdot (x_{r_1} - x_{r_2})` + - ``rand2`` : :math:`b' = x_{r_0} + F \cdot (x_{r_1} + x_{r_2} - x_{r_3} - x_{r_4})` + - ``best1`` : :math:`b' = x_0 + F \cdot (x_{r_0} - x_{r_1})` + - ``best2`` : :math:`b' = x_0 + F \cdot (x_{r_0} + x_{r_1} - x_{r_2} - x_{r_3})` + - ``currenttobest1`` : :math:`b' = x_i + F \cdot (x_0 - x_i + x_{r_0} - x_{r_1})` + - ``randtobest1`` : :math:`b' = x_{r_0} + F \cdot (x_0 - x_{r_0} + x_{r_1} - x_{r_2})` where the integers :math:`r_0, r_1, r_2, r_3, r_4` are chosen randomly from the interval [0, NP) with `NP` being the total population size and diff --git a/scipy/optimize/_dual_annealing.py b/scipy/optimize/_dual_annealing.py index 9645c7967b96..5bad88bca362 100644 --- a/scipy/optimize/_dual_annealing.py +++ b/scipy/optimize/_dual_annealing.py @@ -526,12 +526,12 @@ def dual_annealing(func, bounds, args=(), maxiter=1000, A callback function with signature ``callback(x, f, context)``, which will be called for all minima found. ``x`` and ``f`` are the coordinates and function value of the - latest minimum found, and ``context`` has value in [0, 1, 2], with the - following meaning: + latest minimum found, and ``context`` has one of the following + values: - - 0: minimum detected in the annealing process. - - 1: detection occurred in the local search process. - - 2: detection done in the dual annealing process. + - ``0``: minimum detected in the annealing process. + - ``1``: detection occurred in the local search process. + - ``2``: detection done in the dual annealing process. If the callback implementation returns True, the algorithm will stop. x0 : ndarray, shape(n,), optional diff --git a/scipy/optimize/_linprog.py b/scipy/optimize/_linprog.py index 181218217196..bdc1e9cdee5e 100644 --- a/scipy/optimize/_linprog.py +++ b/scipy/optimize/_linprog.py @@ -68,13 +68,17 @@ def linprog_verbose_callback(res): feasible solution is sought and the T has an additional row representing an alternate objective function. status : int - An integer representing the exit status of the optimization:: + An integer representing the exit status of the optimization: - 0 : Optimization terminated successfully - 1 : Iteration limit reached - 2 : Problem appears to be infeasible - 3 : Problem appears to be unbounded - 4 : Serious numerical difficulties encountered + ``0`` : Optimization terminated successfully + + ``1`` : Iteration limit reached + + ``2`` : Problem appears to be infeasible + + ``3`` : Problem appears to be unbounded + + ``4`` : Serious numerical difficulties encountered nit : int The number of iterations performed. @@ -145,13 +149,17 @@ def linprog_terse_callback(res): feasible solution is sought and the T has an additional row representing an alternate objective function. status : int - An integer representing the exit status of the optimization:: + An integer representing the exit status of the optimization: + + ``0`` : Optimization terminated successfully + + ``1`` : Iteration limit reached - 0 : Optimization terminated successfully - 1 : Iteration limit reached - 2 : Problem appears to be infeasible - 3 : Problem appears to be unbounded - 4 : Serious numerical difficulties encountered + ``2`` : Problem appears to be infeasible + + ``3`` : Problem appears to be unbounded + + ``4`` : Serious numerical difficulties encountered nit : int The number of iterations performed. @@ -189,15 +197,15 @@ def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, Alternatively, that's: - - minimize :: + - minimize :: - c @ x + c @ x - - such that :: + - such that :: - A_ub @ x <= b_ub - A_eq @ x == b_eq - lb <= x <= ub + A_ub @ x <= b_ub + A_eq @ x == b_eq + lb <= x <= ub Note that by default ``lb = 0`` and ``ub = None``. Other bounds can be specified with ``bounds``. @@ -229,13 +237,15 @@ def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, i.e. all variables are allowed to be any real. method : str, optional The algorithm used to solve the standard form problem. - :ref:`'highs' ` (default), - :ref:`'highs-ds' `, - :ref:`'highs-ipm' `, - :ref:`'interior-point' ` (legacy), - :ref:`'revised simplex' ` (legacy), - and - :ref:`'simplex' ` (legacy) are supported. + The following are supported. + + - :ref:`'highs' ` (default) + - :ref:`'highs-ds' ` + - :ref:`'highs-ipm' ` + - :ref:`'interior-point' ` (legacy) + - :ref:`'revised simplex' ` (legacy) + - :ref:`'simplex' ` (legacy) + The legacy methods are deprecated and will be removed in SciPy 1.11.0. callback : callable, optional If a callback function is provided, it will be called at least once per @@ -269,10 +279,10 @@ def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, ``4`` : Numerical difficulties encountered. - nit : int - The current iteration number. - message : str - A string descriptor of the algorithm status. + nit : int + The current iteration number. + message : str + A string descriptor of the algorithm status. Callback functions are not currently supported by the HiGHS methods. @@ -308,23 +318,23 @@ def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, equality constraint matrix after presolve. For problems with dense input, the available methods for redundancy removal are: - "SVD": + ``SVD``: Repeatedly performs singular value decomposition on the matrix, detecting redundant rows based on nonzeros in the left singular vectors that correspond with zero singular values. May be fast when the matrix is nearly full rank. - "pivot": + ``pivot``: Uses the algorithm presented in [5]_ to identify redundant rows. - "ID": + ``ID``: Uses a randomized interpolative decomposition. Identifies columns of the matrix transpose not used in a full-rank interpolative decomposition of the matrix. - None: - Uses "svd" if the matrix is nearly full rank, that is, + ``None``: + Uses ``svd`` if the matrix is nearly full rank, that is, the difference between the matrix rank and the number - of rows is less than five. If not, uses "pivot". The + of rows is less than five. If not, uses ``pivot``. The behavior of this default is subject to change without prior notice. @@ -338,8 +348,8 @@ def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, x0 : 1-D array, optional Guess values of the decision variables, which will be refined by the optimization algorithm. This argument is currently used only by the - 'revised simplex' method, and can only be used if `x0` represents a - basic feasible solution. + :ref:`'revised simplex' ` method, + and can only be used if `x0` represents a basic feasible solution. integrality : 1-D array or int, optional Indicates the type of integrality constraint on each decision variable. @@ -357,12 +367,12 @@ def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, By default, all variables are continuous. - For mixed integrality constraints, supply an array of shape `c.shape`. + For mixed integrality constraints, supply an array of shape ``c.shape``. To infer a constraint on each decision variable from shorter inputs, - the argument will be broadcasted to `c.shape` using `np.broadcast_to`. + the argument will be broadcasted to ``c.shape`` using `numpy.broadcast_to`. - This argument is currently used only by the ``'highs'`` method and - ignored otherwise. + This argument is currently used only by the + :ref:`'highs' ` method and is ignored otherwise. Returns ------- @@ -413,27 +423,30 @@ def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, This section describes the available solvers that can be selected by the 'method' parameter. - `'highs-ds'` and - `'highs-ipm'` are interfaces to the + :ref:`'highs-ds' `, and + :ref:`'highs-ipm' ` are interfaces to the HiGHS simplex and interior-point method solvers [13]_, respectively. - `'highs'` (default) chooses between + :ref:`'highs' ` (default) chooses between the two automatically. These are the fastest linear programming solvers in SciPy, especially for large, sparse problems; which of these two is faster is problem-dependent. - The other solvers (`'interior-point'`, `'revised simplex'`, and - `'simplex'`) are legacy methods and will be removed in SciPy 1.11.0. - - Method *highs-ds* is a wrapper of the C++ high performance dual - revised simplex implementation (HSOL) [13]_, [14]_. Method *highs-ipm* - is a wrapper of a C++ implementation of an **i**\ nterior-\ **p**\ oint - **m**\ ethod [13]_; it features a crossover routine, so it is as accurate - as a simplex solver. Method *highs* chooses between the two automatically. + The other solvers are legacy methods and will be removed when `callback` is + supported by the HiGHS methods. + + Method :ref:`'highs-ds' `, is a wrapper of the C++ high + performance dual revised simplex implementation (HSOL) [13]_, [14]_. + Method :ref:`'highs-ipm' ` is a wrapper of a C++ + implementation of an **i**\ nterior-\ **p**\ oint **m**\ ethod [13]_; it + features a crossover routine, so it is as accurate as a simplex solver. + Method :ref:`'highs' ` chooses between the two + automatically. For new code involving `linprog`, we recommend explicitly choosing one of these three method values. .. versionadded:: 1.6.0 - Method *interior-point* uses the primal-dual path following algorithm + Method :ref:`'interior-point' ` + uses the primal-dual path following algorithm as outlined in [4]_. This algorithm supports sparse constraint matrices and is typically faster than the simplex methods, especially for large, sparse problems. Note, however, that the solution returned may be slightly less @@ -442,21 +455,25 @@ def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, .. versionadded:: 1.0.0 - Method *revised simplex* uses the revised simplex method as described in + Method :ref:`'revised simplex' ` + uses the revised simplex method as described in [9]_, except that a factorization [11]_ of the basis matrix, rather than its inverse, is efficiently maintained and used to solve the linear systems at each iteration of the algorithm. .. versionadded:: 1.3.0 - Method *simplex* uses a traditional, full-tableau implementation of + Method :ref:`'simplex' ` uses a traditional, + full-tableau implementation of Dantzig's simplex algorithm [1]_, [2]_ (*not* the Nelder-Mead simplex). This algorithm is included for backwards compatibility and educational purposes. .. versionadded:: 0.15.0 - Before applying *interior-point*, *revised simplex*, or *simplex*, + Before applying :ref:`'interior-point' `, + :ref:`'revised simplex' `, or + :ref:`'simplex' `, a presolve procedure based on [8]_ attempts to identify trivial infeasibilities, trivial unboundedness, and potential problem simplifications. Specifically, it checks for: diff --git a/scipy/optimize/_lsq/least_squares.py b/scipy/optimize/_lsq/least_squares.py index db8bb31c7b15..b34a83911aa2 100644 --- a/scipy/optimize/_lsq/least_squares.py +++ b/scipy/optimize/_lsq/least_squares.py @@ -288,23 +288,24 @@ def least_squares( bounds : 2-tuple of array_like or `Bounds`, optional There are two ways to specify bounds: - 1. Instance of `Bounds` class - 2. Lower and upper bounds on independent variables. Defaults to no - bounds. Each array must match the size of `x0` or be a scalar, - in the latter case a bound will be the same for all variables. - Use ``np.inf`` with an appropriate sign to disable bounds on all - or some variables. + 1. Instance of `Bounds` class + 2. Lower and upper bounds on independent variables. Defaults to no + bounds. Each array must match the size of `x0` or be a scalar, + in the latter case a bound will be the same for all variables. + Use ``np.inf`` with an appropriate sign to disable bounds on all + or some variables. + method : {'trf', 'dogbox', 'lm'}, optional Algorithm to perform minimization. - * 'trf' : Trust Region Reflective algorithm, particularly suitable - for large sparse problems with bounds. Generally robust method. - * 'dogbox' : dogleg algorithm with rectangular trust regions, - typical use case is small problems with bounds. Not recommended - for problems with rank-deficient Jacobian. - * 'lm' : Levenberg-Marquardt algorithm as implemented in MINPACK. - Doesn't handle bounds and sparse Jacobians. Usually the most - efficient method for small unconstrained problems. + * 'trf' : Trust Region Reflective algorithm, particularly suitable + for large sparse problems with bounds. Generally robust method. + * 'dogbox' : dogleg algorithm with rectangular trust regions, + typical use case is small problems with bounds. Not recommended + for problems with rank-deficient Jacobian. + * 'lm' : Levenberg-Marquardt algorithm as implemented in MINPACK. + Doesn't handle bounds and sparse Jacobians. Usually the most + efficient method for small unconstrained problems. Default is 'trf'. See Notes for more information. ftol : float or None, optional @@ -320,10 +321,10 @@ def least_squares( Tolerance for termination by the change of the independent variables. Default is 1e-8. The exact condition depends on the `method` used: - * For 'trf' and 'dogbox' : ``norm(dx) < xtol * (xtol + norm(x))``. - * For 'lm' : ``Delta < xtol * norm(xs)``, where ``Delta`` is - a trust-region radius and ``xs`` is the value of ``x`` - scaled according to `x_scale` parameter (see below). + * For 'trf' and 'dogbox' : ``norm(dx) < xtol * (xtol + norm(x))``. + * For 'lm' : ``Delta < xtol * norm(xs)``, where ``Delta`` is + a trust-region radius and ``xs`` is the value of ``x`` + scaled according to `x_scale` parameter (see below). If None and 'method' is not 'lm', the termination by this condition is disabled. If 'method' is 'lm', this tolerance must be higher than @@ -332,15 +333,15 @@ def least_squares( Tolerance for termination by the norm of the gradient. Default is 1e-8. The exact condition depends on a `method` used: - * For 'trf' : ``norm(g_scaled, ord=np.inf) < gtol``, where - ``g_scaled`` is the value of the gradient scaled to account for - the presence of the bounds [STIR]_. - * For 'dogbox' : ``norm(g_free, ord=np.inf) < gtol``, where - ``g_free`` is the gradient with respect to the variables which - are not in the optimal state on the boundary. - * For 'lm' : the maximum absolute value of the cosine of angles - between columns of the Jacobian and the residual vector is less - than `gtol`, or the residual vector is zero. + * For 'trf' : ``norm(g_scaled, ord=np.inf) < gtol``, where + ``g_scaled`` is the value of the gradient scaled to account for + the presence of the bounds [STIR]_. + * For 'dogbox' : ``norm(g_free, ord=np.inf) < gtol``, where + ``g_free`` is the gradient with respect to the variables which + are not in the optimal state on the boundary. + * For 'lm' : the maximum absolute value of the cosine of angles + between columns of the Jacobian and the residual vector is less + than `gtol`, or the residual vector is zero. If None and 'method' is not 'lm', the termination by this condition is disabled. If 'method' is 'lm', this tolerance must be higher than @@ -358,17 +359,17 @@ def least_squares( loss : str or callable, optional Determines the loss function. The following keyword values are allowed: - * 'linear' (default) : ``rho(z) = z``. Gives a standard - least-squares problem. - * 'soft_l1' : ``rho(z) = 2 * ((1 + z)**0.5 - 1)``. The smooth - approximation of l1 (absolute value) loss. Usually a good - choice for robust least squares. - * 'huber' : ``rho(z) = z if z <= 1 else 2*z**0.5 - 1``. Works - similarly to 'soft_l1'. - * 'cauchy' : ``rho(z) = ln(1 + z)``. Severely weakens outliers - influence, but may cause difficulties in optimization process. - * 'arctan' : ``rho(z) = arctan(z)``. Limits a maximum loss on - a single residual, has properties similar to 'cauchy'. + * 'linear' (default) : ``rho(z) = z``. Gives a standard + least-squares problem. + * 'soft_l1' : ``rho(z) = 2 * ((1 + z)**0.5 - 1)``. The smooth + approximation of l1 (absolute value) loss. Usually a good + choice for robust least squares. + * 'huber' : ``rho(z) = z if z <= 1 else 2*z**0.5 - 1``. Works + similarly to 'soft_l1'. + * 'cauchy' : ``rho(z) = ln(1 + z)``. Severely weakens outliers + influence, but may cause difficulties in optimization process. + * 'arctan' : ``rho(z) = arctan(z)``. Limits a maximum loss on + a single residual, has properties similar to 'cauchy'. If callable, it must take a 1-D ndarray ``z=f**2`` and return an array_like with shape (3, m) where row 0 contains function values, @@ -385,10 +386,10 @@ def least_squares( Maximum number of function evaluations before the termination. If None (default), the value is chosen automatically: - * For 'trf' and 'dogbox' : 100 * n. - * For 'lm' : 100 * n if `jac` is callable and 100 * n * (n + 1) - otherwise (because 'lm' counts function calls in Jacobian - estimation). + * For 'trf' and 'dogbox' : 100 * n. + * For 'lm' : 100 * n if `jac` is callable and 100 * n * (n + 1) + otherwise (because 'lm' counts function calls in Jacobian + estimation). diff_step : None or array_like, optional Determines the relative step size for the finite difference @@ -400,27 +401,27 @@ def least_squares( Method for solving trust-region subproblems, relevant only for 'trf' and 'dogbox' methods. - * 'exact' is suitable for not very large problems with dense - Jacobian matrices. The computational complexity per iteration is - comparable to a singular value decomposition of the Jacobian - matrix. - * 'lsmr' is suitable for problems with sparse and large Jacobian - matrices. It uses the iterative procedure - `scipy.sparse.linalg.lsmr` for finding a solution of a linear - least-squares problem and only requires matrix-vector product - evaluations. + * 'exact' is suitable for not very large problems with dense + Jacobian matrices. The computational complexity per iteration is + comparable to a singular value decomposition of the Jacobian + matrix. + * 'lsmr' is suitable for problems with sparse and large Jacobian + matrices. It uses the iterative procedure + `scipy.sparse.linalg.lsmr` for finding a solution of a linear + least-squares problem and only requires matrix-vector product + evaluations. If None (default), the solver is chosen based on the type of Jacobian returned on the first iteration. tr_options : dict, optional Keyword options passed to trust-region solver. - * ``tr_solver='exact'``: `tr_options` are ignored. - * ``tr_solver='lsmr'``: options for `scipy.sparse.linalg.lsmr`. - Additionally, ``method='trf'`` supports 'regularize' option - (bool, default is True), which adds a regularization term to the - normal equation, which improves convergence if the Jacobian is - rank-deficient [Byrd]_ (eq. 3.4). + * ``tr_solver='exact'``: `tr_options` are ignored. + * ``tr_solver='lsmr'``: options for `scipy.sparse.linalg.lsmr`. + Additionally, ``method='trf'`` supports 'regularize' option + (bool, default is True), which adds a regularization term to the + normal equation, which improves convergence if the Jacobian is + rank-deficient [Byrd]_ (eq. 3.4). jac_sparsity : {None, array_like, sparse matrix}, optional Defines the sparsity structure of the Jacobian matrix for finite @@ -434,10 +435,10 @@ def least_squares( verbose : {0, 1, 2}, optional Level of algorithm's verbosity: - * 0 (default) : work silently. - * 1 : display a termination report. - * 2 : display progress during iterations (not supported by 'lm' - method). + * 0 (default) : work silently. + * 1 : display a termination report. + * 2 : display progress during iterations (not supported by 'lm' + method). args, kwargs : tuple and dict, optional Additional arguments passed to `fun` and `jac`. Both empty by default. @@ -449,54 +450,54 @@ def least_squares( result : OptimizeResult `OptimizeResult` with the following fields defined: - x : ndarray, shape (n,) - Solution found. - cost : float - Value of the cost function at the solution. - fun : ndarray, shape (m,) - Vector of residuals at the solution. - jac : ndarray, sparse matrix or LinearOperator, shape (m, n) - Modified Jacobian matrix at the solution, in the sense that J^T J - is a Gauss-Newton approximation of the Hessian of the cost function. - The type is the same as the one used by the algorithm. - grad : ndarray, shape (m,) - Gradient of the cost function at the solution. - optimality : float - First-order optimality measure. In unconstrained problems, it is - always the uniform norm of the gradient. In constrained problems, - it is the quantity which was compared with `gtol` during iterations. - active_mask : ndarray of int, shape (n,) - Each component shows whether a corresponding constraint is active - (that is, whether a variable is at the bound): - - * 0 : a constraint is not active. - * -1 : a lower bound is active. - * 1 : an upper bound is active. - - Might be somewhat arbitrary for 'trf' method as it generates a - sequence of strictly feasible iterates and `active_mask` is - determined within a tolerance threshold. - nfev : int - Number of function evaluations done. Methods 'trf' and 'dogbox' do - not count function calls for numerical Jacobian approximation, as - opposed to 'lm' method. - njev : int or None - Number of Jacobian evaluations done. If numerical Jacobian - approximation is used in 'lm' method, it is set to None. - status : int - The reason for algorithm termination: - - * -1 : improper input parameters status returned from MINPACK. - * 0 : the maximum number of function evaluations is exceeded. - * 1 : `gtol` termination condition is satisfied. - * 2 : `ftol` termination condition is satisfied. - * 3 : `xtol` termination condition is satisfied. - * 4 : Both `ftol` and `xtol` termination conditions are satisfied. - - message : str - Verbal description of the termination reason. - success : bool - True if one of the convergence criteria is satisfied (`status` > 0). + x : ndarray, shape (n,) + Solution found. + cost : float + Value of the cost function at the solution. + fun : ndarray, shape (m,) + Vector of residuals at the solution. + jac : ndarray, sparse matrix or LinearOperator, shape (m, n) + Modified Jacobian matrix at the solution, in the sense that J^T J + is a Gauss-Newton approximation of the Hessian of the cost function. + The type is the same as the one used by the algorithm. + grad : ndarray, shape (m,) + Gradient of the cost function at the solution. + optimality : float + First-order optimality measure. In unconstrained problems, it is + always the uniform norm of the gradient. In constrained problems, + it is the quantity which was compared with `gtol` during iterations. + active_mask : ndarray of int, shape (n,) + Each component shows whether a corresponding constraint is active + (that is, whether a variable is at the bound): + + * 0 : a constraint is not active. + * -1 : a lower bound is active. + * 1 : an upper bound is active. + + Might be somewhat arbitrary for 'trf' method as it generates a + sequence of strictly feasible iterates and `active_mask` is + determined within a tolerance threshold. + nfev : int + Number of function evaluations done. Methods 'trf' and 'dogbox' do + not count function calls for numerical Jacobian approximation, as + opposed to 'lm' method. + njev : int or None + Number of Jacobian evaluations done. If numerical Jacobian + approximation is used in 'lm' method, it is set to None. + status : int + The reason for algorithm termination: + + * -1 : improper input parameters status returned from MINPACK. + * 0 : the maximum number of function evaluations is exceeded. + * 1 : `gtol` termination condition is satisfied. + * 2 : `ftol` termination condition is satisfied. + * 3 : `xtol` termination condition is satisfied. + * 4 : Both `ftol` and `xtol` termination conditions are satisfied. + + message : str + Verbal description of the termination reason. + success : bool + True if one of the convergence criteria is satisfied (`status` > 0). See Also -------- diff --git a/scipy/optimize/_lsq/lsq_linear.py b/scipy/optimize/_lsq/lsq_linear.py index fdf4d2602010..6ab632a0bab6 100644 --- a/scipy/optimize/_lsq/lsq_linear.py +++ b/scipy/optimize/_lsq/lsq_linear.py @@ -58,25 +58,24 @@ def lsq_linear(A, b, bounds=(-np.inf, np.inf), method='trf', tol=1e-10, Lower and upper bounds on parameters. Defaults to no bounds. There are two ways to specify the bounds: - - Instance of `Bounds` class. - - - 2-tuple of array_like: Each element of the tuple must be either - an array with the length equal to the number of parameters, or a - scalar (in which case the bound is taken to be the same for all - parameters). Use ``np.inf`` with an appropriate sign to disable - bounds on all or some parameters. + - Instance of `Bounds` class. + - 2-tuple of array_like: Each element of the tuple must be either + an array with the length equal to the number of parameters, or a + scalar (in which case the bound is taken to be the same for all + parameters). Use ``np.inf`` with an appropriate sign to disable + bounds on all or some parameters. method : 'trf' or 'bvls', optional Method to perform minimization. - * 'trf' : Trust Region Reflective algorithm adapted for a linear - least-squares problem. This is an interior-point-like method - and the required number of iterations is weakly correlated with - the number of variables. - * 'bvls' : Bounded-variable least-squares algorithm. This is - an active set method, which requires the number of iterations - comparable to the number of variables. Can't be used when `A` is - sparse or LinearOperator. + * 'trf' : Trust Region Reflective algorithm adapted for a linear + least-squares problem. This is an interior-point-like method + and the required number of iterations is weakly correlated with + the number of variables. + * 'bvls' : Bounded-variable least-squares algorithm. This is + an active set method, which requires the number of iterations + comparable to the number of variables. Can't be used when `A` is + sparse or LinearOperator. Default is 'trf'. tol : float, optional @@ -84,21 +83,21 @@ def lsq_linear(A, b, bounds=(-np.inf, np.inf), method='trf', tol=1e-10, of the cost function is less than `tol` on the last iteration. Additionally, the first-order optimality measure is considered: - * ``method='trf'`` terminates if the uniform norm of the gradient, - scaled to account for the presence of the bounds, is less than - `tol`. - * ``method='bvls'`` terminates if Karush-Kuhn-Tucker conditions - are satisfied within `tol` tolerance. + * ``method='trf'`` terminates if the uniform norm of the gradient, + scaled to account for the presence of the bounds, is less than + `tol`. + * ``method='bvls'`` terminates if Karush-Kuhn-Tucker conditions + are satisfied within `tol` tolerance. lsq_solver : {None, 'exact', 'lsmr'}, optional Method of solving unbounded least-squares problems throughout iterations: - * 'exact' : Use dense QR or SVD decomposition approach. Can't be - used when `A` is sparse or LinearOperator. - * 'lsmr' : Use `scipy.sparse.linalg.lsmr` iterative procedure - which requires only matrix-vector product evaluations. Can't - be used with ``method='bvls'``. + * 'exact' : Use dense QR or SVD decomposition approach. Can't be + used when `A` is sparse or LinearOperator. + * 'lsmr' : Use `scipy.sparse.linalg.lsmr` iterative procedure + which requires only matrix-vector product evaluations. Can't + be used with ``method='bvls'``. If None (default), the solver is chosen based on type of `A`. lsmr_tol : None, float or 'auto', optional @@ -114,9 +113,10 @@ def lsq_linear(A, b, bounds=(-np.inf, np.inf), method='trf', tol=1e-10, verbose : {0, 1, 2}, optional Level of algorithm's verbosity: - * 0 : work silently (default). - * 1 : display a termination report. - * 2 : display progress during iterations. + * 0 : work silently (default). + * 1 : display a termination report. + * 2 : display progress during iterations. + lsmr_maxiter : None or int, optional Maximum number of iterations for the lsmr least squares solver, if it is used (by setting ``lsq_solver='lsmr'``). If None (default), it @@ -140,9 +140,9 @@ def lsq_linear(A, b, bounds=(-np.inf, np.inf), method='trf', tol=1e-10, Each component shows whether a corresponding constraint is active (that is, whether a variable is at the bound): - * 0 : a constraint is not active. - * -1 : a lower bound is active. - * 1 : an upper bound is active. + * 0 : a constraint is not active. + * -1 : a lower bound is active. + * 1 : an upper bound is active. Might be somewhat arbitrary for the `trf` method as it generates a sequence of strictly feasible iterates and active_mask is determined @@ -167,12 +167,12 @@ def lsq_linear(A, b, bounds=(-np.inf, np.inf), method='trf', tol=1e-10, status : int Reason for algorithm termination: - * -1 : the algorithm was not able to make progress on the last - iteration. - * 0 : the maximum number of iterations is exceeded. - * 1 : the first-order optimality measure is less than `tol`. - * 2 : the relative change of the cost function is less than `tol`. - * 3 : the unconstrained solution is optimal. + * -1 : the algorithm was not able to make progress on the last + iteration. + * 0 : the maximum number of iterations is exceeded. + * 1 : the first-order optimality measure is less than `tol`. + * 2 : the relative change of the cost function is less than `tol`. + * 3 : the unconstrained solution is optimal. message : str Verbal description of the termination reason. diff --git a/scipy/optimize/_minimize.py b/scipy/optimize/_minimize.py index 195e31f23e22..de1d5e44253c 100644 --- a/scipy/optimize/_minimize.py +++ b/scipy/optimize/_minimize.py @@ -58,9 +58,9 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, Parameters ---------- fun : callable - The objective function to be minimized. + The objective function to be minimized:: - ``fun(x, *args) -> float`` + fun(x, *args) -> float where ``x`` is a 1-D array with shape (n,) and ``args`` is a tuple of the fixed parameters needed to completely @@ -74,22 +74,22 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, method : str or callable, optional Type of solver. Should be one of - - 'Nelder-Mead' :ref:`(see here) ` - - 'Powell' :ref:`(see here) ` - - 'CG' :ref:`(see here) ` - - 'BFGS' :ref:`(see here) ` - - 'Newton-CG' :ref:`(see here) ` - - 'L-BFGS-B' :ref:`(see here) ` - - 'TNC' :ref:`(see here) ` - - 'COBYLA' :ref:`(see here) ` - - 'COBYQA' :ref:`(see here) ` - - 'SLSQP' :ref:`(see here) ` - - 'trust-constr':ref:`(see here) ` - - 'dogleg' :ref:`(see here) ` - - 'trust-ncg' :ref:`(see here) ` - - 'trust-exact' :ref:`(see here) ` - - 'trust-krylov' :ref:`(see here) ` - - custom - a callable object, see below for description. + - 'Nelder-Mead' :ref:`(see here) ` + - 'Powell' :ref:`(see here) ` + - 'CG' :ref:`(see here) ` + - 'BFGS' :ref:`(see here) ` + - 'Newton-CG' :ref:`(see here) ` + - 'L-BFGS-B' :ref:`(see here) ` + - 'TNC' :ref:`(see here) ` + - 'COBYLA' :ref:`(see here) ` + - 'COBYQA' :ref:`(see here) ` + - 'SLSQP' :ref:`(see here) ` + - 'trust-constr':ref:`(see here) ` + - 'dogleg' :ref:`(see here) ` + - 'trust-ncg' :ref:`(see here) ` + - 'trust-exact' :ref:`(see here) ` + - 'trust-krylov' :ref:`(see here) ` + - custom - a callable object, see below for description. If not given, chosen to be one of ``BFGS``, ``L-BFGS-B``, ``SLSQP``, depending on whether or not the problem has constraints or bounds. @@ -98,9 +98,9 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, Newton-CG, L-BFGS-B, TNC, SLSQP, dogleg, trust-ncg, trust-krylov, trust-exact and trust-constr. If it is a callable, it should be a function that returns the gradient - vector: + vector:: - ``jac(x, *args) -> array_like, shape (n,)`` + jac(x, *args) -> array_like, shape (n,) where ``x`` is an array with shape (n,) and ``args`` is a tuple with the fixed parameters. If `jac` is a Boolean and is True, `fun` is @@ -118,9 +118,9 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, hess : {callable, '2-point', '3-point', 'cs', HessianUpdateStrategy}, optional Method for computing the Hessian matrix. Only for Newton-CG, dogleg, trust-ncg, trust-krylov, trust-exact and trust-constr. - If it is callable, it should return the Hessian matrix: + If it is callable, it should return the Hessian matrix:: - ``hess(x, *args) -> {LinearOperator, spmatrix, array}, (n, n)`` + hess(x, *args) -> {LinearOperator, spmatrix, array}, (n, n) where ``x`` is a (n,) ndarray and ``args`` is a tuple with the fixed parameters. @@ -130,8 +130,8 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, interface can be used to approximate the Hessian. Available quasi-Newton methods implementing this interface are: - - `BFGS`; - - `SR1`. + - `BFGS` + - `SR1` Not all of the options are available for each of the methods; for availability refer to the notes. @@ -140,9 +140,9 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, Newton-CG, trust-ncg, trust-krylov, trust-constr. Only one of `hessp` or `hess` needs to be given. If `hess` is provided, then `hessp` will be ignored. `hessp` must compute the - Hessian times an arbitrary vector: + Hessian times an arbitrary vector:: - ``hessp(x, p, *args) -> ndarray shape (n,)`` + hessp(x, p, *args) -> ndarray shape (n,) where ``x`` is a (n,) ndarray, ``p`` is an arbitrary vector with dimension (n,) and ``args`` is a tuple with the fixed @@ -152,9 +152,9 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, trust-constr, COBYLA, and COBYQA methods. There are two ways to specify the bounds: - 1. Instance of `Bounds` class. - 2. Sequence of ``(min, max)`` pairs for each element in `x`. None - is used to specify no bound. + 1. Instance of `Bounds` class. + 2. Sequence of ``(min, max)`` pairs for each element in `x`. None + is used to specify no bound. constraints : {Constraint, dict} or List of {Constraint, dict}, optional Constraints definition. Only for COBYLA, COBYQA, SLSQP and trust-constr. @@ -163,20 +163,20 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, or a list of objects specifying constraints to the optimization problem. Available constraints are: - - `LinearConstraint` - - `NonlinearConstraint` + - `LinearConstraint` + - `NonlinearConstraint` Constraints for COBYLA, SLSQP are defined as a list of dictionaries. Each dictionary with fields: - type : str - Constraint type: 'eq' for equality, 'ineq' for inequality. - fun : callable - The function defining the constraint. - jac : callable, optional - The Jacobian of `fun` (only for SLSQP). - args : sequence, optional - Extra arguments to be passed to the function and Jacobian. + type : str + Constraint type: 'eq' for equality, 'ineq' for inequality. + fun : callable + The function defining the constraint. + jac : callable, optional + The Jacobian of `fun` (only for SLSQP). + args : sequence, optional + Extra arguments to be passed to the function and Jacobian. Equality constraint means that the constraint function result is to be zero whereas inequality means that it is to be non-negative. @@ -191,22 +191,22 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, A dictionary of solver options. All methods except `TNC` accept the following generic options: - maxiter : int - Maximum number of iterations to perform. Depending on the - method each iteration may use several function evaluations. + maxiter : int + Maximum number of iterations to perform. Depending on the + method each iteration may use several function evaluations. - For `TNC` use `maxfun` instead of `maxiter`. - disp : bool - Set to True to print convergence messages. + For `TNC` use `maxfun` instead of `maxiter`. + disp : bool + Set to True to print convergence messages. For method-specific options, see :func:`show_options()`. callback : callable, optional A callable called after each iteration. All methods except TNC, SLSQP, and COBYLA support a callable with - the signature: + the signature:: - ``callback(intermediate_result: OptimizeResult)`` + callback(intermediate_result: OptimizeResult) where ``intermediate_result`` is a keyword parameter containing an `OptimizeResult` with attributes ``x`` and ``fun``, the present values @@ -215,9 +215,9 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, to be passed an `OptimizeResult`. These methods will also terminate if the callback raises ``StopIteration``. - All methods except trust-constr (also) support a signature like: + All methods except trust-constr (also) support a signature like:: - ``callback(xk)`` + callback(xk) where ``xk`` is the current parameter vector. @@ -801,10 +801,10 @@ def minimize_scalar(fun, bracket=None, bounds=None, args=(), method : str or callable, optional Type of solver. Should be one of: - - :ref:`Brent ` - - :ref:`Bounded ` - - :ref:`Golden ` - - custom - a callable object (added in version 0.14.0), see below + - :ref:`Brent ` + - :ref:`Bounded ` + - :ref:`Golden ` + - custom - a callable object (added in version 0.14.0), see below Default is "Bounded" if bounds are provided and "Brent" otherwise. See the 'Notes' section for details of each solver. @@ -815,10 +815,10 @@ def minimize_scalar(fun, bracket=None, bounds=None, args=(), options : dict, optional A dictionary of solver options. - maxiter : int - Maximum number of iterations to perform. - disp : bool - Set to True to print convergence messages. + maxiter : int + Maximum number of iterations to perform. + disp : bool + Set to True to print convergence messages. See :func:`show_options()` for solver-specific options. diff --git a/scipy/optimize/_minpack_py.py b/scipy/optimize/_minpack_py.py index b67a17ae41b4..dc8165c5dd20 100644 --- a/scipy/optimize/_minpack_py.py +++ b/scipy/optimize/_minpack_py.py @@ -620,15 +620,15 @@ def curve_fit(f, xdata, ydata, p0=None, sigma=None, absolute_sigma=False, ``r = ydata - f(xdata, *popt)``, then the interpretation of `sigma` depends on its number of dimensions: - - A scalar or 1-D `sigma` should contain values of standard deviations of - errors in `ydata`. In this case, the optimized function is - ``chisq = sum((r / sigma) ** 2)``. + - A scalar or 1-D `sigma` should contain values of standard deviations of + errors in `ydata`. In this case, the optimized function is + ``chisq = sum((r / sigma) ** 2)``. - - A 2-D `sigma` should contain the covariance matrix of - errors in `ydata`. In this case, the optimized function is - ``chisq = r.T @ inv(sigma) @ r``. + - A 2-D `sigma` should contain the covariance matrix of + errors in `ydata`. In this case, the optimized function is + ``chisq = r.T @ inv(sigma) @ r``. - .. versionadded:: 0.19 + .. versionadded:: 0.19 None (default) is equivalent of 1-D `sigma` filled with ones. absolute_sigma : bool, optional @@ -653,13 +653,13 @@ def curve_fit(f, xdata, ydata, p0=None, sigma=None, absolute_sigma=False, Lower and upper bounds on parameters. Defaults to no bounds. There are two ways to specify the bounds: - - Instance of `Bounds` class. + - Instance of `Bounds` class. - - 2-tuple of array_like: Each element of the tuple must be either - an array with the length equal to the number of parameters, or a - scalar (in which case the bound is taken to be the same for all - parameters). Use ``np.inf`` with an appropriate sign to disable - bounds on all or some parameters. + - 2-tuple of array_like: Each element of the tuple must be either + an array with the length equal to the number of parameters, or a + scalar (in which case the bound is taken to be the same for all + parameters). Use ``np.inf`` with an appropriate sign to disable + bounds on all or some parameters. method : {'lm', 'trf', 'dogbox'}, optional Method to use for optimization. See `least_squares` for more details. @@ -687,11 +687,11 @@ def curve_fit(f, xdata, ydata, p0=None, sigma=None, absolute_sigma=False, Defines how to handle when input contains nan. The following options are available (default is None): - * 'raise': throws an error - * 'omit': performs the calculations ignoring nan values - * None: no special handling of NaNs is performed - (except what is done by check_finite); the behavior when NaNs - are present is implementation-dependent and may change. + * 'raise': throws an error + * 'omit': performs the calculations ignoring nan values + * None: no special handling of NaNs is performed + (except what is done by check_finite); the behavior when NaNs + are present is implementation-dependent and may change. Note that if this value is specified explicitly (not None), `check_finite` will be set as False. @@ -712,7 +712,7 @@ def curve_fit(f, xdata, ydata, p0=None, sigma=None, absolute_sigma=False, deviation errors on the parameters, use ``perr = np.sqrt(np.diag(pcov))``. Note that the relationship between `cov` and parameter error estimates is derived based on a linear - approximation to the model function around the optimum [1]. + approximation to the model function around the optimum [1]_. When this approximation becomes inaccurate, `cov` may not provide an accurate measure of uncertainty. @@ -803,9 +803,9 @@ def curve_fit(f, xdata, ydata, p0=None, sigma=None, absolute_sigma=False, References ---------- - [1] K. Vugrin et al. Confidence region estimation techniques for nonlinear - regression in groundwater flow: Three case studies. Water Resources - Research, Vol. 43, W03423, :doi:`10.1029/2005WR004804` + .. [1] K. Vugrin et al. Confidence region estimation techniques for nonlinear + regression in groundwater flow: Three case studies. Water Resources + Research, Vol. 43, W03423, :doi:`10.1029/2005WR004804` Examples -------- diff --git a/scipy/optimize/_nonlin.py b/scipy/optimize/_nonlin.py index cbaa3d4ced44..516f35bef5fb 100644 --- a/scipy/optimize/_nonlin.py +++ b/scipy/optimize/_nonlin.py @@ -806,12 +806,12 @@ def svd_reduce(self, max_rank, to_retain=None): Methods available: - - ``restart``: drop all matrix columns. Has no extra parameters. - - ``simple``: drop oldest matrix column. Has no extra parameters. - - ``svd``: keep only the most significant SVD components. - Takes an extra parameter, ``to_retain``, which determines the - number of SVD components to retain when rank reduction is done. - Default is ``max_rank - 2``. + - ``restart``: drop all matrix columns. Has no extra parameters. + - ``simple``: drop oldest matrix column. Has no extra parameters. + - ``svd``: keep only the most significant SVD components. + Takes an extra parameter, ``to_retain``, which determines the + number of SVD components to retain when rank reduction is done. + Default is ``max_rank - 2``. max_rank : int, optional Maximum rank for the Broyden matrix. @@ -820,10 +820,10 @@ def svd_reduce(self, max_rank, to_retain=None): class BroydenFirst(GenericBroyden): - r""" + """ Find a root of a function, using Broyden's first Jacobian approximation. - This method is also known as \"Broyden's good method\". + This method is also known as "Broyden's good method". Parameters ---------- @@ -840,21 +840,20 @@ class BroydenFirst(GenericBroyden): ----- This algorithm implements the inverse Jacobian Quasi-Newton update - .. math:: H_+ = H + (dx - H df) dx^\dagger H / ( dx^\dagger H df) + .. math:: H_+ = H + (dx - H df) dx^\\dagger H / ( dx^\\dagger H df) which corresponds to Broyden's first Jacobian update - .. math:: J_+ = J + (df - J dx) dx^\dagger / dx^\dagger dx + .. math:: J_+ = J + (df - J dx) dx^\\dagger / dx^\\dagger dx References ---------- .. [1] B.A. van der Rotten, PhD thesis, - \"A limited memory Broyden method to solve high-dimensional - systems of nonlinear equations\". Mathematisch Instituut, + "A limited memory Broyden method to solve high-dimensional + systems of nonlinear equations". Mathematisch Instituut, Universiteit Leiden, The Netherlands (2003). - - https://web.archive.org/web/20161022015821/http://www.math.leidenuniv.nl/scripties/Rotten.pdf + https://math.leidenuniv.nl/scripties/Rotten.pdf Examples -------- @@ -1331,7 +1330,7 @@ def _update(self, x, f, dx, df, dx_norm, df_norm): #------------------------------------------------------------------------------ class KrylovJacobian(Jacobian): - r""" + """ Find a root of a function, using Krylov approximation for inverse Jacobian. This method is suitable for solving large-scale problems. @@ -1390,7 +1389,7 @@ class KrylovJacobian(Jacobian): method. These methods require only evaluating the Jacobian-vector products, which are conveniently approximated by a finite difference: - .. math:: J v \approx (f(x + \omega*v/|v|) - f(x)) / \omega + .. math:: J v \\approx (f(x + \\omega*v/|v|) - f(x)) / \\omega Due to the use of iterative matrix inverses, these methods can deal with large nonlinear problems. diff --git a/scipy/optimize/_optimize.py b/scipy/optimize/_optimize.py index a1cc6ab84a0f..bd6758e8feb9 100644 --- a/scipy/optimize/_optimize.py +++ b/scipy/optimize/_optimize.py @@ -702,9 +702,9 @@ def _minimize_neldermead(func, x0, args=(), callback=None, bounds : sequence or `Bounds`, optional Bounds on variables. There are two ways to specify the bounds: - 1. Instance of `Bounds` class. - 2. Sequence of ``(min, max)`` pairs for each element in `x`. None - is used to specify no bound. + 1. Instance of `Bounds` class. + 2. Sequence of ``(min, max)`` pairs for each element in `x`. None + is used to specify no bound. Note that this just clips all vertices in simplex based on the bounds. @@ -2150,13 +2150,16 @@ def fminbound(func, x1, x2, args=(), xtol=1e-5, maxfun=500, Maximum number of function evaluations allowed. full_output : bool, optional If True, return optional outputs. - disp : int, optional + disp: int, optional If non-zero, print messages. - 0 : no message printing. - 1 : non-convergence notification messages only. - 2 : print a message on convergence too. - 3 : print iteration results. + ``0`` : no message printing. + + ``1`` : non-convergence notification messages only. + + ``2`` : print a message on convergence too. + + ``3`` : print iteration results. Returns ------- @@ -2233,10 +2236,15 @@ def _minimize_scalar_bounded(func, bounds, args=(), Maximum number of iterations to perform. disp: int, optional If non-zero, print messages. - 0 : no message printing. - 1 : non-convergence notification messages only. - 2 : print a message on convergence too. - 3 : print iteration results. + + ``0`` : no message printing. + + ``1`` : non-convergence notification messages only. + + ``2`` : print a message on convergence too. + + ``3`` : print iteration results. + xatol : float Absolute error in solution `xopt` acceptable for convergence. @@ -2639,12 +2647,17 @@ def _minimize_scalar_brent(func, brack=None, args=(), xtol=1.48e-8, Maximum number of iterations to perform. xtol : float Relative error in solution `xopt` acceptable for convergence. - disp: int, optional + disp : int, optional If non-zero, print messages. - 0 : no message printing. - 1 : non-convergence notification messages only. - 2 : print a message on convergence too. - 3 : print iteration results. + + ``0`` : no message printing. + + ``1`` : non-convergence notification messages only. + + ``2`` : print a message on convergence too. + + ``3`` : print iteration results. + Notes ----- Uses inverse parabolic interpolation when possible to speed up @@ -2770,10 +2783,14 @@ def _minimize_scalar_golden(func, brack=None, args=(), Maximum number of iterations to perform. disp: int, optional If non-zero, print messages. - 0 : no message printing. - 1 : non-convergence notification messages only. - 2 : print a message on convergence too. - 3 : print iteration results. + + ``0`` : no message printing. + + ``1`` : non-convergence notification messages only. + + ``2`` : print a message on convergence too. + + ``3`` : print iteration results. """ _check_unknown_options(unknown_options) tol = xtol @@ -3344,9 +3361,9 @@ def _minimize_powell(func, x0, args=(), callback=None, bounds=None, Parameters ---------- fun : callable - The objective function to be minimized. + The objective function to be minimized:: - ``fun(x, *args) -> float`` + fun(x, *args) -> float where ``x`` is a 1-D array with shape (n,) and ``args`` is a tuple of the fixed parameters needed to completely @@ -3363,9 +3380,9 @@ def _minimize_powell(func, x0, args=(), callback=None, bounds=None, bounds : sequence or `Bounds`, optional Bounds on decision variables. There are two ways to specify the bounds: - 1. Instance of `Bounds` class. - 2. Sequence of ``(min, max)`` pairs for each element in `x`. None - is used to specify no bound. + 1. Instance of `Bounds` class. + 2. Sequence of ``(min, max)`` pairs for each element in `x`. None + is used to specify no bound. If bounds are not provided, then an unbounded line search will be used. If bounds are provided and the initial guess is within the bounds, then @@ -3382,17 +3399,17 @@ def _minimize_powell(func, x0, args=(), callback=None, bounds=None, A dictionary of solver options. All methods accept the following generic options: - maxiter : int - Maximum number of iterations to perform. Depending on the - method each iteration may use several function evaluations. - disp : bool - Set to True to print convergence messages. + maxiter : int + Maximum number of iterations to perform. Depending on the + method each iteration may use several function evaluations. + disp : bool + Set to True to print convergence messages. See method-specific options for ``method='powell'`` below. callback : callable, optional - Called after each iteration. The signature is: + Called after each iteration. The signature is:: - ``callback(xk)`` + callback(xk) where ``xk`` is the current parameter vector. diff --git a/scipy/optimize/_qap.py b/scipy/optimize/_qap.py index 094119c0ad6c..8a843b68f1d6 100644 --- a/scipy/optimize/_qap.py +++ b/scipy/optimize/_qap.py @@ -60,9 +60,7 @@ def quadratic_assignment(A, B, method="faq", options=None): ``partial_match[i, 1]`` of `B`. The array has shape ``(m, 2)``, where ``m`` is not greater than the number of nodes, :math:`n`. - rng : {None, int, `numpy.random.Generator`, - `numpy.random.RandomState`}, optional - + rng : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`} 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, @@ -286,9 +284,7 @@ def _quadratic_assignment_faq(A, B, ``partial_match[i, 1]`` of `B`. The array has shape ``(m, 2)``, where ``m`` is not greater than the number of nodes, :math:`n`. - rng : {None, int, `numpy.random.Generator`, - `numpy.random.RandomState`}, optional - + rng : {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, @@ -582,9 +578,7 @@ def _quadratic_assignment_2opt(A, B, maximize=False, rng=None, ------- maximize : bool (default: False) Maximizes the objective function if ``True``. - rng : {None, int, `numpy.random.Generator`, - `numpy.random.RandomState`}, optional - + rng : {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, @@ -612,7 +606,7 @@ def _quadratic_assignment_2opt(A, B, maximize=False, rng=None, ``partial_guess[i, 1]`` of `B`. The array has shape ``(m, 2)``, where ``m`` is not greater than the number of nodes, :math:`n`. - .. note:: + .. note:: `partial_guess` must be sorted by the first column. Returns diff --git a/scipy/optimize/_root.py b/scipy/optimize/_root.py index 2847619bb803..6710d9d97383 100644 --- a/scipy/optimize/_root.py +++ b/scipy/optimize/_root.py @@ -38,16 +38,16 @@ def root(fun, x0, args=(), method='hybr', jac=None, tol=None, callback=None, method : str, optional Type of solver. Should be one of - - 'hybr' :ref:`(see here) ` - - 'lm' :ref:`(see here) ` - - 'broyden1' :ref:`(see here) ` - - 'broyden2' :ref:`(see here) ` - - 'anderson' :ref:`(see here) ` - - 'linearmixing' :ref:`(see here) ` - - 'diagbroyden' :ref:`(see here) ` - - 'excitingmixing' :ref:`(see here) ` - - 'krylov' :ref:`(see here) ` - - 'df-sane' :ref:`(see here) ` + - 'hybr' :ref:`(see here) ` + - 'lm' :ref:`(see here) ` + - 'broyden1' :ref:`(see here) ` + - 'broyden2' :ref:`(see here) ` + - 'anderson' :ref:`(see here) ` + - 'linearmixing' :ref:`(see here) ` + - 'diagbroyden' :ref:`(see here) ` + - 'excitingmixing' :ref:`(see here) ` + - 'krylov' :ref:`(see here) ` + - 'df-sane' :ref:`(see here) ` jac : bool or callable, optional If `jac` is a Boolean and is True, `fun` is assumed to return the @@ -404,36 +404,28 @@ def _root_broyden1_doc(): 'armijo'. jac_options : dict, optional Options for the respective Jacobian approximation. - alpha : float, optional - Initial guess for the Jacobian is (-1/alpha). - reduction_method : str or tuple, optional - Method used in ensuring that the rank of the Broyden - matrix stays low. Can either be a string giving the - name of the method, or a tuple of the form ``(method, - param1, param2, ...)`` that gives the name of the - method and values for additional parameters. - - Methods available: - - - ``restart`` - Drop all matrix columns. Has no - extra parameters. - - ``simple`` - Drop oldest matrix column. Has no - extra parameters. - - ``svd`` - Keep only the most significant SVD - components. - - Extra parameters: - - - ``to_retain`` - Number of SVD components to - retain when rank reduction is done. - Default is ``max_rank - 2``. - max_rank : int, optional - Maximum rank for the Broyden matrix. - Default is infinity (i.e., no rank reduction). + + alpha : float, optional + Initial guess for the Jacobian is (-1/alpha). + reduction_method : str or tuple, optional + Method used in ensuring that the rank of the Broyden + matrix stays low. Can either be a string giving the + name of the method, or a tuple of the form ``(method, + param1, param2, ...)`` that gives the name of the + method and values for additional parameters. + + Methods available: + + - ``restart``: drop all matrix columns. Has no extra parameters. + - ``simple``: drop oldest matrix column. Has no extra parameters. + - ``svd``: keep only the most significant SVD components. + Takes an extra parameter, ``to_retain``, which determines the + number of SVD components to retain when rank reduction is done. + Default is ``max_rank - 2``. + + max_rank : int, optional + Maximum rank for the Broyden matrix. + Default is infinity (i.e., no rank reduction). Examples -------- @@ -451,6 +443,7 @@ def _root_broyden1_doc(): """ pass + def _root_broyden2_doc(): """ Options @@ -493,28 +486,20 @@ def _root_broyden2_doc(): Methods available: - - ``restart`` - Drop all matrix columns. Has no - extra parameters. - - ``simple`` - Drop oldest matrix column. Has no - extra parameters. - - ``svd`` - Keep only the most significant SVD - components. - - Extra parameters: - - - ``to_retain`` - Number of SVD components to - retain when rank reduction is done. - Default is ``max_rank - 2``. + - ``restart``: drop all matrix columns. Has no extra parameters. + - ``simple``: drop oldest matrix column. Has no extra parameters. + - ``svd``: keep only the most significant SVD components. + Takes an extra parameter, ``to_retain``, which determines the + number of SVD components to retain when rank reduction is done. + Default is ``max_rank - 2``. + max_rank : int, optional Maximum rank for the Broyden matrix. Default is infinity (i.e., no rank reduction). """ pass + def _root_anderson_doc(): """ Options diff --git a/scipy/optimize/_root_scalar.py b/scipy/optimize/_root_scalar.py index a1d9fde0af0a..e02d39f3968f 100644 --- a/scipy/optimize/_root_scalar.py +++ b/scipy/optimize/_root_scalar.py @@ -76,14 +76,14 @@ def root_scalar(f, args=(), method=None, bracket=None, method : str, optional Type of solver. Should be one of - - 'bisect' :ref:`(see here) ` - - 'brentq' :ref:`(see here) ` - - 'brenth' :ref:`(see here) ` - - 'ridder' :ref:`(see here) ` - - 'toms748' :ref:`(see here) ` - - 'newton' :ref:`(see here) ` - - 'secant' :ref:`(see here) ` - - 'halley' :ref:`(see here) ` + - 'bisect' :ref:`(see here) ` + - 'brentq' :ref:`(see here) ` + - 'brenth' :ref:`(see here) ` + - 'ridder' :ref:`(see here) ` + - 'toms748' :ref:`(see here) ` + - 'newton' :ref:`(see here) ` + - 'secant' :ref:`(see here) ` + - 'halley' :ref:`(see here) ` bracket: A sequence of 2 floats, optional An interval bracketing a root. ``f(x, *args)`` must have different diff --git a/scipy/optimize/_shgo.py b/scipy/optimize/_shgo.py index 9c8f36a777f8..4d9426242903 100644 --- a/scipy/optimize/_shgo.py +++ b/scipy/optimize/_shgo.py @@ -82,49 +82,49 @@ def shgo( current parameter vector. minimizer_kwargs : dict, optional Extra keyword arguments to be passed to the minimizer - ``scipy.optimize.minimize`` Some important options could be: - - * method : str - The minimization method. If not given, chosen to be one of - BFGS, L-BFGS-B, SLSQP, depending on whether or not the - problem has constraints or bounds. - * args : tuple - Extra arguments passed to the objective function (``func``) and - its derivatives (Jacobian, Hessian). - * options : dict, optional - Note that by default the tolerance is specified as - ``{ftol: 1e-12}`` + ``scipy.optimize.minimize``. Some important options could be: + + method : str + The minimization method. If not given, chosen to be one of + BFGS, L-BFGS-B, SLSQP, depending on whether or not the + problem has constraints or bounds. + args : tuple + Extra arguments passed to the objective function (``func``) and + its derivatives (Jacobian, Hessian). + options : dict, optional + Note that by default the tolerance is specified as + ``{ftol: 1e-12}`` options : dict, optional A dictionary of solver options. Many of the options specified for the - global routine are also passed to the scipy.optimize.minimize routine. - The options that are also passed to the local routine are marked with - "(L)". + global routine are also passed to the ``scipy.optimize.minimize`` + routine. The options that are also passed to the local routine are + marked with "(L)". Stopping criteria, the algorithm will terminate if any of the specified criteria are met. However, the default algorithm does not require any to be specified: - * maxfev : int (L) + maxfev : int (L) Maximum number of function evaluations in the feasible domain. (Note only methods that support this option will terminate the routine at precisely exact specified value. Otherwise the criterion will only terminate during a global iteration) - * f_min + f_min : float Specify the minimum objective function value, if it is known. - * f_tol : float + f_tol : float Precision goal for the value of f in the stopping criterion. Note that the global routine will also terminate if a sampling point in the global routine is within this tolerance. - * maxiter : int + maxiter : int Maximum number of iterations to perform. - * maxev : int + maxev : int Maximum number of sampling evaluations to perform (includes searching in infeasible points). - * maxtime : float + maxtime : float Maximum processing runtime allowed - * minhgrd : int + minhgrd : int Minimum homology group rank differential. The homology group of the objective function is calculated (approximately) during every iteration. The rank of this group has a one-to-one correspondence @@ -136,7 +136,7 @@ def shgo( Objective function knowledge: - * symmetry : list or bool + symmetry : list or bool Specify if the objective function contains symmetric variables. The search space (and therefore performance) is decreased by up to O(n!) times in the fully symmetric case. If `True` is specified @@ -147,17 +147,17 @@ def shgo( E.g. f(x) = (x_1 + x_2 + x_3) + (x_4)**2 + (x_5)**2 + (x_6)**2 In this equation x_2 and x_3 are symmetric to x_1, while x_5 and - x_6 are symmetric to x_4, this can be specified to the solver as: + x_6 are symmetric to x_4, this can be specified to the solver as:: - symmetry = [0, # Variable 1 - 0, # symmetric to variable 1 - 0, # symmetric to variable 1 - 3, # Variable 4 - 3, # symmetric to variable 4 - 3, # symmetric to variable 4 - ] + symmetry = [0, # Variable 1 + 0, # symmetric to variable 1 + 0, # symmetric to variable 1 + 3, # Variable 4 + 3, # symmetric to variable 4 + 3, # symmetric to variable 4 + ] - * jac : bool or callable, optional + jac : bool or callable, optional Jacobian (gradient) of objective function. Only for CG, BFGS, Newton-CG, L-BFGS-B, TNC, SLSQP, dogleg, trust-ncg. If ``jac`` is a boolean and is True, ``fun`` is assumed to return the gradient @@ -167,7 +167,7 @@ def shgo( arguments as ``fun``. (Passed to `scipy.optimize.minimize` automatically) - * hess, hessp : callable, optional + hess, hessp : callable, optional Hessian (matrix of second-order derivatives) of objective function or Hessian of objective function times an arbitrary vector p. Only for Newton-CG, dogleg, trust-ncg. Only one of ``hessp`` or @@ -180,15 +180,17 @@ def shgo( Algorithm settings: - * minimize_every_iter : bool + minimize_every_iter : bool If True then promising global sampling points will be passed to a local minimization routine every iteration. If True then only the final minimizer pool will be run. Defaults to True. - * local_iter : int + + local_iter : int Only evaluate a few of the best minimizer pool candidates every iteration. If False all potential points are passed to the local minimization routine. - * infty_constraints : bool + + infty_constraints : bool If True then any sampling points generated which are outside will the feasible domain will be saved and given an objective function value of ``inf``. If False then these points will be discarded. @@ -199,7 +201,7 @@ def shgo( Feedback: - * disp : bool (L) + disp : bool (L) Set to True to print convergence messages. sampling_method : str or function, optional diff --git a/scipy/optimize/_slsqp_py.py b/scipy/optimize/_slsqp_py.py index 19b5541c9cbf..ab01d0b1d4c8 100644 --- a/scipy/optimize/_slsqp_py.py +++ b/scipy/optimize/_slsqp_py.py @@ -159,19 +159,19 @@ def fmin_slsqp(func, x0, eqcons=(), f_eqcons=None, ieqcons=(), f_ieqcons=None, Notes ----- - Exit modes are defined as follows :: - - -1 : Gradient evaluation required (g & a) - 0 : Optimization terminated successfully - 1 : Function evaluation required (f & c) - 2 : More equality constraints than independent variables - 3 : More than 3*n iterations in LSQ subproblem - 4 : Inequality constraints incompatible - 5 : Singular matrix E in LSQ subproblem - 6 : Singular matrix C in LSQ subproblem - 7 : Rank-deficient equality constraint subproblem HFTI - 8 : Positive directional derivative for linesearch - 9 : Iteration limit reached + Exit modes are defined as follows: + + - ``-1`` : Gradient evaluation required (g & a) + - ``0`` : Optimization terminated successfully + - ``1`` : Function evaluation required (f & c) + - ``2`` : More equality constraints than independent variables + - ``3`` : More than 3*n iterations in LSQ subproblem + - ``4`` : Inequality constraints incompatible + - ``5`` : Singular matrix E in LSQ subproblem + - ``6`` : Singular matrix C in LSQ subproblem + - ``7`` : Rank-deficient equality constraint subproblem HFTI + - ``8`` : Positive directional derivative for linesearch + - ``9`` : Iteration limit reached Examples -------- diff --git a/scipy/optimize/_tnc.py b/scipy/optimize/_tnc.py index cdd461829a5f..4e0f2c6429b6 100644 --- a/scipy/optimize/_tnc.py +++ b/scipy/optimize/_tnc.py @@ -90,7 +90,7 @@ def fmin_tnc(func, x0, fprime=None, args=(), approx_grad=0, messages=MSG_ALL, maxCGit=-1, maxfun=None, eta=-1, stepmx=0, accuracy=0, fmin=0, ftol=-1, xtol=-1, pgtol=-1, rescale=-1, disp=None, callback=None): - """ + r""" Minimize a function with variables subject to bounds, using gradient information in a truncated Newton algorithm. This method wraps a C implementation of the algorithm. @@ -223,17 +223,17 @@ def fmin_tnc(func, x0, fprime=None, args=(), approx_grad=0, associated with the variable of largest index whose constraint is no longer active. - Return codes are defined as follows:: - - -1 : Infeasible (lower bound > upper bound) - 0 : Local minimum reached (|pg| ~= 0) - 1 : Converged (|f_n-f_(n-1)| ~= 0) - 2 : Converged (|x_n-x_(n-1)| ~= 0) - 3 : Max. number of function evaluations reached - 4 : Linear search failed - 5 : All lower bounds are equal to the upper bounds - 6 : Unable to progress - 7 : User requested end of minimization + Return codes are defined as follows: + + - ``-1`` : Infeasible (lower bound > upper bound) + - ``0`` : Local minimum reached (:math:`|pg| \approx 0`) + - ``1`` : Converged (:math:`|f_n-f_(n-1)| \approx 0`) + - ``2`` : Converged (:math:`|x_n-x_(n-1)| \approx 0`) + - ``3`` : Max. number of function evaluations reached + - ``4`` : Linear search failed + - ``5`` : All lower bounds are equal to the upper bounds + - ``6`` : Unable to progress + - ``7`` : User requested end of minimization References ---------- diff --git a/scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py b/scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py index 2835ea5445c0..9b7f9394d13a 100644 --- a/scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py +++ b/scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py @@ -186,10 +186,10 @@ def _minimize_trustregion_constr(fun, x0, args, grad, Method to factorize the Jacobian of the constraints. Use None (default) for the auto selection or one of: - - 'NormalEquation' (requires scikit-sparse) - - 'AugmentedSystem' - - 'QRFactorization' - - 'SVDFactorization' + - 'NormalEquation' (requires scikit-sparse) + - 'AugmentedSystem' + - 'QRFactorization' + - 'SVDFactorization' The methods 'NormalEquation' and 'AugmentedSystem' can be used only with sparse constraints. The projections required by the algorithm @@ -214,10 +214,10 @@ def _minimize_trustregion_constr(fun, x0, args, grad, verbose : {0, 1, 2}, optional Level of algorithm's verbosity: - * 0 (default) : work silently. - * 1 : display a termination report. - * 2 : display progress during iterations. - * 3 : display progress during iterations (more complete report). + * 0 (default) : work silently. + * 1 : display a termination report. + * 2 : display progress during iterations. + * 3 : display progress during iterations (more complete report). disp : bool, optional If True (default), then `verbose` will be set to 1 if it was 0. @@ -293,19 +293,19 @@ def _minimize_trustregion_constr(fun, x0, args, grad, status : {0, 1, 2, 3} Termination status: - * 0 : The maximum number of function evaluations is exceeded. - * 1 : `gtol` termination condition is satisfied. - * 2 : `xtol` termination condition is satisfied. - * 3 : `callback` function requested termination. + * 0 : The maximum number of function evaluations is exceeded. + * 1 : `gtol` termination condition is satisfied. + * 2 : `xtol` termination condition is satisfied. + * 3 : `callback` function requested termination. cg_stop_cond : int Reason for CG subproblem termination at the last iteration: - * 0 : CG subproblem not evaluated. - * 1 : Iteration limit was reached. - * 2 : Reached the trust-region boundary. - * 3 : Negative curvature detected. - * 4 : Tolerance was satisfied. + * 0 : CG subproblem not evaluated. + * 1 : Iteration limit was reached. + * 2 : Reached the trust-region boundary. + * 3 : Negative curvature detected. + * 4 : Tolerance was satisfied. References ---------- diff --git a/scipy/optimize/_zeros_py.py b/scipy/optimize/_zeros_py.py index ccffcb207d6c..468e45d3feb1 100644 --- a/scipy/optimize/_zeros_py.py +++ b/scipy/optimize/_zeros_py.py @@ -744,29 +744,21 @@ def brentq(f, a, b, args=(), Object containing information about the convergence. In particular, ``r.converged`` is True if the routine converged. + See Also + -------- + fmin, fmin_powell, fmin_cg, fmin_bfgs, fmin_ncg : multivariate local optimizers + leastsq : nonlinear least squares minimizer + fmin_l_bfgs_b, fmin_tnc, fmin_cobyla : constrained multivariate optimizers + basinhopping, differential_evolution, brute : global optimizers + fminbound, brent, golden, bracket : local scalar minimizers + fsolve : N-D root-finding + brenth, ridder, bisect, newton : 1-D root-finding + fixed_point : scalar fixed-point finder + Notes ----- `f` must be continuous. f(a) and f(b) must have opposite signs. - Related functions fall into several classes: - - multivariate local optimizers - `fmin`, `fmin_powell`, `fmin_cg`, `fmin_bfgs`, `fmin_ncg` - nonlinear least squares minimizer - `leastsq` - constrained multivariate optimizers - `fmin_l_bfgs_b`, `fmin_tnc`, `fmin_cobyla` - global optimizers - `basinhopping`, `brute`, `differential_evolution` - local scalar minimizers - `fminbound`, `brent`, `golden`, `bracket` - N-D root-finding - `fsolve` - 1-D root-finding - `brenth`, `ridder`, `bisect`, `newton` - scalar fixed-point finder - `fixed_point` - References ---------- .. [Brent1973] @@ -878,7 +870,7 @@ def brenth(f, a, b, args=(), basinhopping, differential_evolution, brute : global optimizers fminbound, brent, golden, bracket : local scalar minimizers fsolve : N-D root-finding - brentq, brenth, ridder, bisect, newton : 1-D root-finding + brentq, ridder, bisect, newton : 1-D root-finding fixed_point : scalar fixed-point finder References From 54aebaefdefcb696726e7e129f923375655154f7 Mon Sep 17 00:00:00 2001 From: anushkasuyal <126159239+anushkasuyal@users.noreply.github.com> Date: Mon, 1 Jul 2024 23:22:40 +0100 Subject: [PATCH 480/500] DOC: sparse.linalg.gcrotmk: add `maxiter` default (#21094) also fix backticks [docs only] Co-authored-by: Lucas Colley Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/sparse/linalg/_isolve/_gcrotmk.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scipy/sparse/linalg/_isolve/_gcrotmk.py b/scipy/sparse/linalg/_isolve/_gcrotmk.py index f3a35d4110bd..e3ab88d97f44 100644 --- a/scipy/sparse/linalg/_isolve/_gcrotmk.py +++ b/scipy/sparse/linalg/_isolve/_gcrotmk.py @@ -190,7 +190,7 @@ def gcrotmk(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=1000, M=None, callback ---------- A : {sparse matrix, ndarray, LinearOperator} The real or complex N-by-N matrix of the linear system. - Alternatively, ``A`` can be a linear operator which can + Alternatively, `A` can be a linear operator which can produce ``Ax`` using, e.g., ``scipy.sparse.linalg.LinearOperator``. b : ndarray @@ -200,26 +200,26 @@ def gcrotmk(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=1000, M=None, callback 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``. + 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. + steps even if the specified tolerance has not been achieved. The default is ``1000``. M : {sparse matrix, ndarray, LinearOperator}, optional - Preconditioner for A. The preconditioner should approximate the - inverse of A. gcrotmk is a 'flexible' algorithm and the preconditioner + 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. + 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 + 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]_. From 0f184ba6e384d9f2174f4f99f94d4cf113b2a515 Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Tue, 2 Jul 2024 12:48:25 +0100 Subject: [PATCH 481/500] DEV: don't add `sparse` label for submodules [lint only] --- .github/label-globs.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/label-globs.yml b/.github/label-globs.yml index 5478a20cb65f..146adaa8dd9a 100644 --- a/.github/label-globs.yml +++ b/.github/label-globs.yml @@ -73,8 +73,11 @@ scipy.signal: scipy.sparse: - changed-files: - - any-glob-to-any-file: + - all-globs-to-any-file: - scipy/sparse/** + # don't match the `csgraph` or `linalg` submodules + - '!scipy/sparse/csgraph/**' + - '!scipy/sparse/linalg/**' scipy.sparse.csgraph: - changed-files: From 0185ef602c78f92c098028843ecf7a04f75ddaf4 Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Tue, 2 Jul 2024 18:18:19 +0530 Subject: [PATCH 482/500] CI: Added Windows CI job for MSVC + ifx + OpenBLAS --- .github/workflows/windows_intel_oneAPI.yml | 102 +++++++++++++++++++++ tools/install_intel_oneAPI_windows.bat | 22 +++++ 2 files changed, 124 insertions(+) create mode 100644 .github/workflows/windows_intel_oneAPI.yml create mode 100644 tools/install_intel_oneAPI_windows.bat diff --git a/.github/workflows/windows_intel_oneAPI.yml b/.github/workflows/windows_intel_oneAPI.yml new file mode 100644 index 000000000000..c0d971b4d0c1 --- /dev/null +++ b/.github/workflows/windows_intel_oneAPI.yml @@ -0,0 +1,102 @@ +name: Windows tests (MSVC + ifx + OpenBLAS) +# TODO: replace OpenBLAS with MKL. This is harder to get to build, so we merged with OpenBLAS first. + +on: + push: + branches: + - maintenance/** + pull_request: + branches: + - main + - maintenance/** + +permissions: + contents: read # to fetch code (actions/checkout) + +# The environment variable WINDOWS_BASEKIT_URL and WINDOWS_HPC_URL +# store the URL for downloading Intel oneAPI. +# Reference - https://github.com/oneapi-src/oneapi-ci/blob/b4a96bd1888c130fcb73524d2b77b90f43681cbc/.github/workflows/build_all.yml#L11-L12 +env: + WINDOWS_BASEKIT_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/7dff44ba-e3af-4448-841c-0d616c8da6e7/w_BaseKit_p_2024.1.0.595_offline.exe + WINDOWS_HPC_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/c95a3b26-fc45-496c-833b-df08b10297b9/w_HPCKit_p_2024.1.0.561_offline.exe + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + get_commit_message: + name: Get commit message + uses: ./.github/workflows/commit_message.yml + + msvc_ifx_blas: + name: py3.11, dev.py + needs: get_commit_message + # Ensure (a) this doesn't run on forks by default, and + # (b) it does run with Act locally (`github` doesn't exist there) + if: > + needs.get_commit_message.outputs.message == 1 + && (github.repository == 'scipy/scipy' || github.repository == '') + runs-on: windows-2022 + defaults: + run: + shell: powershell + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + with: + submodules: recursive + + - name: Setup conda + uses: s-weigand/setup-conda@678f22c807cb6fde6a290be6f3546877c98ec66f # v1.2.2 + with: + update-conda: true + python-version: 3.11 + - run: conda --version + - run: which python + + - name: Install packages from conda + run: | + conda install -c conda-forge pkg-config meson=1.4.0 meson-python=0.16.0 ninja openblas libblas=*=*openblas numpy==2.0 cython pybind11 pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich-click click doit pydevtool hypothesis + + - name: cache install + id: cache-install + uses: actions/cache@v3 + with: + path: | + C:\Program Files (x86)\Intel\oneAPI\compiler + C:\Program Files (x86)\Intel\oneAPI\setvars.bat + C:\Program Files (x86)\Intel\oneAPI\setvars-vcvarsall.bat + key: install-${{ env.WINDOWS_HPC_URL }}-${{ env.WINDOWS_BASEKIT_URL }}-compiler + - name: Install oneAPI Base kit + if: steps.cache-install.outputs.cache-hit != 'true' + run: | + echo %WINDOWS_BASEKIT_URL% + tools/install_intel_oneAPI_windows.bat %WINDOWS_BASEKIT_URL% + - name: Install oneAPI HPC kit + if: steps.cache-install.outputs.cache-hit != 'true' + run: | + echo %WINDOWS_HPC_URL% + tools/install_intel_oneAPI_windows.bat %WINDOWS_HPC_URL% + + # MSVC is unable to compile Pythran code, therefore we need to use + # -C-Duse-pythran=false while building SciPy. + # Reference - https://github.com/serge-sans-paille/pythran/issues/2215 + - name: Initialise Intel oneAPI, MSVC and Build SciPy + run: | + call "C:\Program Files (x86)\Intel\oneAPI\setvars.bat" + set FC=ifx + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + python dev.py build -C-Duse-pythran=false -C--vsenv + shell: cmd + + # "import scipy; scipy.test();" fails because + # scipy/sparse/linalg/_eigen/arpack crashes. + # Reference - https://github.com/scipy/scipy/issues/20728 + - name: Test scipy.datasets + run: python dev.py test -s datasets + shell: cmd + + - name: Test scipy.misc + run: python dev.py test -s misc + shell: cmd diff --git a/tools/install_intel_oneAPI_windows.bat b/tools/install_intel_oneAPI_windows.bat new file mode 100644 index 000000000000..d918af34aea6 --- /dev/null +++ b/tools/install_intel_oneAPI_windows.bat @@ -0,0 +1,22 @@ +REM Reference - https://github.com/oneapi-src/oneapi-ci/blob/master/scripts/install_windows.bat +REM SPDX-FileCopyrightText: 2022 Intel Corporation +REM +REM SPDX-License-Identifier: MIT + + +set URL=%1 +set COMPONENTS=%2 + + +:: download installer from intel +curl.exe --output %TEMP%\webimage.exe --url %URL% --retry 5 --retry-delay 5 +start /b /wait %TEMP%\webimage.exe -s -x -f webimage_extracted +del %TEMP%\webimage.exe +if "%COMPONENTS%"=="" ( + webimage_extracted\bootstrapper.exe -s --action install --eula=accept -p=NEED_VS2017_INTEGRATION=0 -p=NEED_VS2019_INTEGRATION=0 -p=NEED_VS2022_INTEGRATION=0 +) else ( + webimage_extracted\bootstrapper.exe -s --action install --components=%COMPONENTS% --eula=accept -p=NEED_VS2017_INTEGRATION=0 -p=NEED_VS2019_INTEGRATION=0 -p=NEED_VS2022_INTEGRATION=0 +) +set installer_exit_code=%ERRORLEVEL% +rd /s/q "webimage_extracted" +exit /b %installer_exit_code% From c79a73b975487c15081fb75cebf5925ea4cba65c Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Tue, 2 Jul 2024 18:18:32 +0530 Subject: [PATCH 483/500] MISC: Trim trailing whitespace --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index bd27819346bc..455a951e67f3 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -157,7 +157,7 @@ jobs: wheel_name=$(ls dist/*.whl) openblas_dir=$(python -c"import scipy_openblas32 as sop; print(sop.get_lib_dir())") delvewheel repair --add-path $openblas_dir --no-dll libsf_error_state.dll -w wheelhouse $wheel_name - + python -m pip install wheelhouse/* - name: Test From e51cfe469230e806e5d218916e4ff5ccbf641b0e Mon Sep 17 00:00:00 2001 From: anushkasuyal <126159239+anushkasuyal@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:26:28 +0100 Subject: [PATCH 484/500] DOC: sparse.linalg.gcrotmk: improve docstring (#21096) add missing backticks and correct order of sections [docs only] --- scipy/sparse/linalg/_isolve/_gcrotmk.py | 27 +++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/scipy/sparse/linalg/_isolve/_gcrotmk.py b/scipy/sparse/linalg/_isolve/_gcrotmk.py index e3ab88d97f44..55c15dd12d15 100644 --- a/scipy/sparse/linalg/_isolve/_gcrotmk.py +++ b/scipy/sparse/linalg/_isolve/_gcrotmk.py @@ -192,7 +192,7 @@ def gcrotmk(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=1000, M=None, callback 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``. + `LinearOperator`. b : ndarray Right hand side of the linear system. Has shape (N,) or (N,1). x0 : ndarray @@ -203,7 +203,8 @@ def gcrotmk(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=1000, M=None, callback 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``. + steps even if the specified tolerance has not been achieved. The + default is ``1000``. M : {sparse matrix, ndarray, LinearOperator}, optional Preconditioner for `A`. The preconditioner should approximate the inverse of `A`. gcrotmk is a 'flexible' algorithm and the preconditioner @@ -246,6 +247,17 @@ def gcrotmk(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=1000, M=None, callback * 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 @@ -260,17 +272,6 @@ def gcrotmk(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=1000, M=None, callback >>> np.allclose(A.dot(x), b) True - 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). - """ A,M,x,b,postprocess = make_system(A,M,x0,b) From 60e6adf54051d776bd7ea72a0064f01cb48fae84 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Tue, 2 Jul 2024 22:51:14 +0100 Subject: [PATCH 485/500] DOC: special.ellipj: fix order of parameters in docstring (#21101) * DOC: special.ellipj: fix order of parameters in docstring [docs only] * DOC: special.ellipj: fix incorrect backticks [docs only] * Update scipy/special/_add_newdocs.py [docs only] Co-authored-by: Lucas Colley --------- Co-authored-by: Lucas Colley --- scipy/special/_add_newdocs.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scipy/special/_add_newdocs.py b/scipy/special/_add_newdocs.py index 74d98b7abfe8..ae77a98c6b0f 100644 --- a/scipy/special/_add_newdocs.py +++ b/scipy/special/_add_newdocs.py @@ -2292,10 +2292,10 @@ def add_newdoc(name, doc): Parameters ---------- - m : array_like - Parameter. u : array_like Argument. + m : array_like + Parameter. out : tuple of ndarray, optional Optional output arrays for the function values @@ -2306,8 +2306,8 @@ def add_newdoc(name, doc): sn(u|m), cn(u|m), dn(u|m) - The value `ph` is such that if `u = ellipkinc(ph, m)`, - then `sn(u|m) = sin(ph)` and `cn(u|m) = cos(ph)`. + The value `ph` is such that if ``u = ellipkinc(ph, m)``, + then ``sn(u|m) = sin(ph)`` and ``cn(u|m) = cos(ph)``. See Also -------- @@ -2316,18 +2316,18 @@ def add_newdoc(name, doc): Notes ----- - Wrapper for the Cephes [1]_ routine `ellpj`. + Wrapper for the Cephes [1]_ routine ``ellpj``. These functions are periodic, with quarter-period on the real axis - equal to the complete elliptic integral `ellipk(m)`. + equal to the complete elliptic integral ``ellipk(m)``. - Relation to incomplete elliptic integral: If `u = ellipkinc(phi,m)`, then - `sn(u|m) = sin(phi)`, and `cn(u|m) = cos(phi)`. The `phi` is called + Relation to incomplete elliptic integral: If ``u = ellipkinc(phi,m)``, then + ``sn(u|m) = sin(phi)``, and ``cn(u|m) = cos(phi)``. The ``phi`` is called the amplitude of `u`. Computation is by means of the arithmetic-geometric mean algorithm, except when `m` is within 1e-9 of 0 or 1. In the latter case with `m` - close to 1, the approximation applies only for `phi < pi/2`. + close to 1, the approximation applies only for ``phi < pi/2``. References ---------- From b89eca0514f9e2594958dcc97c9b4d34722dd212 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Tue, 2 Jul 2024 17:57:38 -0600 Subject: [PATCH 486/500] MAINT: itemsize pybind cleanup * Related to some of the complex discussion at: https://github.com/pybind/pybind11/issues/5009 * It seems we are lower bounded by a recent enough `pybind11` in `pyproject.toml` that it should now be safe to clean this up. Full testsuite passed locally with NumPy 1.x and 2.x from clean build slate. [skip circle] [skip cirrus] --- scipy/spatial/src/distance_pybind.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scipy/spatial/src/distance_pybind.cpp b/scipy/spatial/src/distance_pybind.cpp index a09e2193c5c2..0c40139cbc2d 100644 --- a/scipy/spatial/src/distance_pybind.cpp +++ b/scipy/spatial/src/distance_pybind.cpp @@ -221,8 +221,7 @@ ArrayDescriptor get_descriptor(const py::array& arr) { const auto arr_shape = arr.shape(); desc.shape.assign(arr_shape, arr_shape + ndim); - // TODO: Replace the following with `arr.itemsize()` this is a temporary workaround: - desc.element_size = PyArray_ITEMSIZE(reinterpret_cast(arr.ptr())); + desc.element_size = arr.itemsize(); const auto arr_strides = arr.strides(); desc.strides.assign(arr_strides, arr_strides + ndim); for (intptr_t i = 0; i < ndim; ++i) { From 93b74311a530bc6ab40f7ea30a59b1f7a4b82c40 Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Wed, 3 Jul 2024 12:50:41 -0400 Subject: [PATCH 487/500] add release note for 1.14 re: sparse array iteration [docs only] --- doc/source/release/1.14.0-notes.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/release/1.14.0-notes.rst b/doc/source/release/1.14.0-notes.rst index 971f7eb35eb1..0667c12f6a7c 100644 --- a/doc/source/release/1.14.0-notes.rst +++ b/doc/source/release/1.14.0-notes.rst @@ -88,6 +88,7 @@ New features - Sparse array methods min/nanmin/argmin and max analogs now return 1D arrays. Results are still COO format sparse arrays for min/nanmin and dense ``np.ndarray`` for argmin. +- Iterating over ``csr_array`` or ``csc_array`` yields 1D (CSC) arrays. - Sparse matrix and array objects improve their ``repr`` and ``str`` output. - A special case has been added to handle multiplying a ``dia_array`` by a scalar, which avoids a potentially costly conversion to CSR format. From 793232ac5d852e493243a0498434128af8328f89 Mon Sep 17 00:00:00 2001 From: Pamphile Roy <23188539+tupui@users.noreply.github.com> Date: Wed, 3 Jul 2024 20:47:31 +0200 Subject: [PATCH 488/500] Adjustment on find_minimum doc [skip ci] Co-authored-by: Matt Haberland --- scipy/optimize/_elementwise.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/_elementwise.py b/scipy/optimize/_elementwise.py index 0f1ce297057b..05fc2e2bbb26 100644 --- a/scipy/optimize/_elementwise.py +++ b/scipy/optimize/_elementwise.py @@ -232,7 +232,7 @@ def _callback(res): def find_minimum(f, init, /, *, args=(), tolerances=None, maxiter=100, callback=None): - """Find the minimizer of an unimodal, real-valued function of a real variable. + """Find the minimum of an unimodal, real-valued function of a real variable. For each element of the output of `f`, `find_minimum` seeks the scalar minimizer that minimizes the element. This function currently uses Chandrupatla's From 89343895084604803060b92549013f4e96ba4971 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 3 Jul 2024 14:17:08 -0700 Subject: [PATCH 489/500] MAINT: stats.combine_pvalues: fix native axis support for method='stouffer' (#21109) --- scipy/stats/_stats_py.py | 5 ++++- scipy/stats/tests/test_axis_nan_policy.py | 6 ++++++ scipy/stats/tests/test_stats.py | 8 ++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index c9253082f15b..bd1a647e196e 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -9375,7 +9375,10 @@ def combine_pvalues(pvalues, method='fisher', weights=None, *, axis=0): norm = _SimpleNormal() Zi = norm.isf(pvalues) - statistic = weights @ Zi / xp.linalg.vector_norm(weights, axis=axis) + # could use `einsum` or clever `matmul` for performance, + # but this is the most readable + statistic = (xp.sum(weights * Zi, axis=axis) + / xp.linalg.vector_norm(weights, axis=axis)) pval = _get_pvalue(statistic, norm, alternative="greater", xp=xp) else: diff --git a/scipy/stats/tests/test_axis_nan_policy.py b/scipy/stats/tests/test_axis_nan_policy.py index 90912102949c..0e0088f5c8b4 100644 --- a/scipy/stats/tests/test_axis_nan_policy.py +++ b/scipy/stats/tests/test_axis_nan_policy.py @@ -56,6 +56,11 @@ def xp_var(*args, **kwargs): return stats._stats_py._xp_var(*args, **kwargs) +def combine_pvalues_weighted(*args, **kwargs): + return stats.combine_pvalues(args[0], *args[2:], weights=args[1], + method='stouffer', **kwargs) + + axis_nan_policy_cases = [ # function, args, kwds, number of samples, number of outputs, # ... paired, unpacker function @@ -131,6 +136,7 @@ def xp_var(*args, **kwargs): (stats.alexandergovern, tuple(), {}, 2, 2, False, lambda res: (res.statistic, res.pvalue)), (stats.combine_pvalues, tuple(), {}, 1, 2, False, None), + (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,)), diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 920c88d3b85d..9df8c7a09abd 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -8099,19 +8099,19 @@ 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) + res = stats.combine_pvalues(x, axis=axis, method=method) if axis is None: x = xp.reshape(x, (-1,)) - ref = stats.combine_pvalues(x) + 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) - ref1 = stats.combine_pvalues(x1) + 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) From c98929cd0d08c4466b940f305323af1c0bc8498c Mon Sep 17 00:00:00 2001 From: Nick ODell Date: Wed, 3 Jul 2024 21:04:13 +0000 Subject: [PATCH 490/500] BUG: ndimage.binary_erosion: avoid divide by zero Cap the maximum value of block_size at size, even when we have zero dimensions. Avoids a crash for 0 dimension inputs with iteration=-1. Add test for this case. --- scipy/ndimage/src/ni_morphology.c | 6 +++++- scipy/ndimage/tests/test_morphology.py | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/scipy/ndimage/src/ni_morphology.c b/scipy/ndimage/src/ni_morphology.c index 625b35359b06..2e53e59123d9 100644 --- a/scipy/ndimage/src/ni_morphology.c +++ b/scipy/ndimage/src/ni_morphology.c @@ -146,7 +146,11 @@ int NI_BinaryErosion(PyArrayObject* input, PyArrayObject* strct, _false = 0; } if (coordinate_list) { - block_size = LIST_SIZE / PyArray_NDIM(input) / sizeof(int); + if(PyArray_NDIM(input) != 0) { + block_size = LIST_SIZE / PyArray_NDIM(input) / sizeof(int); + } else { + block_size = size; + } if (block_size < 1) block_size = 1; if (block_size > size) diff --git a/scipy/ndimage/tests/test_morphology.py b/scipy/ndimage/tests/test_morphology.py index 29094d430edb..67ae1c59cd72 100644 --- a/scipy/ndimage/tests/test_morphology.py +++ b/scipy/ndimage/tests/test_morphology.py @@ -1694,6 +1694,12 @@ def test_binary_dilation35(self, dtype): origin=(1, 1), border_value=1) assert_array_almost_equal(out, expected) + def test_binary_dilation36(self): + # gh-21009 + data = np.zeros([], bool) + out = ndimage.binary_dilation(data, iterations=-1) + assert_array_almost_equal(out, 0) + def test_binary_propagation01(self): struct = [[0, 1, 0], [1, 1, 1], @@ -1752,6 +1758,13 @@ def test_binary_propagation02(self): mask=mask, border_value=1) assert_array_almost_equal(out, expected) + def test_binary_propagation03(self): + # gh-21009 + data = np.zeros([], bool) + expected = np.zeros([], bool) + out = ndimage.binary_propagation(data) + assert_array_almost_equal(out, expected) + @pytest.mark.parametrize('dtype', types) def test_binary_opening01(self, dtype): expected = [[0, 1, 0, 0, 0, 0, 0, 0], From f2a2e99eee06948644868b9b04e78169a6654951 Mon Sep 17 00:00:00 2001 From: Sina Saber <72632585+Sinamahani@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:02:33 -0500 Subject: [PATCH 491/500] Update _differentialevolution.py --- scipy/optimize/_differentialevolution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index df102ceb3d35..3fd6864bfd29 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -111,7 +111,7 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', mutation : float or tuple(float, float), optional The mutation constant. In the literature this is also known as differential weight, being denoted by :math:`F`. - If specified as a float it should be in the range [0, 2]. + If specified as a float it should be in the range [0, 2). If specified as a tuple ``(min, max)`` dithering is employed. Dithering randomly changes the mutation constant on a generation by generation basis. The mutation constant for that generation is taken from From 3eb1ac4111200330b3caaca4664f1921984d1e5b Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Wed, 3 Jul 2024 23:27:11 +0200 Subject: [PATCH 492/500] ENH:sparse.linalg: Update vendored ARPACK version to 3.9.1 --- .../linalg/_eigen/arpack/ARPACK/CHANGES | 278 ++++++++++++- .../sparse/linalg/_eigen/arpack/ARPACK/README | 96 ----- .../linalg/_eigen/arpack/ARPACK/README.md | 376 ++++++++++++++++++ .../linalg/_eigen/arpack/ARPACK/README.scipy | 6 +- .../linalg/_eigen/arpack/ARPACK/SRC/ccdotc.f | 36 ++ .../linalg/_eigen/arpack/ARPACK/SRC/cgetv0.f | 92 ++--- .../linalg/_eigen/arpack/ARPACK/SRC/cnaitr.f | 204 +++++----- .../linalg/_eigen/arpack/ARPACK/SRC/cnapps.f | 76 ++-- .../linalg/_eigen/arpack/ARPACK/SRC/cnaup2.f | 188 ++++----- .../linalg/_eigen/arpack/ARPACK/SRC/cnaupd.f | 158 ++++---- .../linalg/_eigen/arpack/ARPACK/SRC/cneigh.f | 50 +-- .../linalg/_eigen/arpack/ARPACK/SRC/cneupd.f | 172 ++++---- .../linalg/_eigen/arpack/ARPACK/SRC/cngets.f | 40 +- .../linalg/_eigen/arpack/ARPACK/SRC/csortc.f | 38 +- .../linalg/_eigen/arpack/ARPACK/SRC/cstatn.f | 8 +- .../linalg/_eigen/arpack/ARPACK/SRC/dgetv0.f | 78 ++-- .../linalg/_eigen/arpack/ARPACK/SRC/dnaitr.f | 180 ++++----- .../linalg/_eigen/arpack/ARPACK/SRC/dnapps.f | 98 ++--- .../linalg/_eigen/arpack/ARPACK/SRC/dnaup2.f | 20 +- .../linalg/_eigen/arpack/ARPACK/SRC/dnaupd.f | 8 +- .../linalg/_eigen/arpack/ARPACK/SRC/dnconv.f | 18 +- .../linalg/_eigen/arpack/ARPACK/SRC/dneigh.f | 46 +-- .../linalg/_eigen/arpack/ARPACK/SRC/dneupd.f | 357 ++++++++--------- .../linalg/_eigen/arpack/ARPACK/SRC/dngets.f | 48 +-- .../linalg/_eigen/arpack/ARPACK/SRC/dsaitr.f | 172 ++++---- .../linalg/_eigen/arpack/ARPACK/SRC/dsapps.f | 98 ++--- .../linalg/_eigen/arpack/ARPACK/SRC/dsaup2.f | 172 ++++---- .../linalg/_eigen/arpack/ARPACK/SRC/dsaupd.f | 12 +- .../linalg/_eigen/arpack/ARPACK/SRC/dsconv.f | 22 +- .../linalg/_eigen/arpack/ARPACK/SRC/dseigt.f | 26 +- .../linalg/_eigen/arpack/ARPACK/SRC/dsesrt.f | 22 +- .../linalg/_eigen/arpack/ARPACK/SRC/dseupd.f | 132 +++--- .../linalg/_eigen/arpack/ARPACK/SRC/dsgets.f | 44 +- .../linalg/_eigen/arpack/ARPACK/SRC/dsortc.f | 36 +- .../linalg/_eigen/arpack/ARPACK/SRC/dsortr.f | 18 +- .../linalg/_eigen/arpack/ARPACK/SRC/dstatn.f | 14 +- .../linalg/_eigen/arpack/ARPACK/SRC/dstats.f | 12 +- .../linalg/_eigen/arpack/ARPACK/SRC/dstqrb.f | 36 +- .../linalg/_eigen/arpack/ARPACK/SRC/sgetv0.f | 78 ++-- .../linalg/_eigen/arpack/ARPACK/SRC/snaitr.f | 180 ++++----- .../linalg/_eigen/arpack/ARPACK/SRC/snapps.f | 94 ++--- .../linalg/_eigen/arpack/ARPACK/SRC/snaup2.f | 20 +- .../linalg/_eigen/arpack/ARPACK/SRC/snaupd.f | 188 ++++----- .../linalg/_eigen/arpack/ARPACK/SRC/snconv.f | 18 +- .../linalg/_eigen/arpack/ARPACK/SRC/sneigh.f | 46 +-- .../linalg/_eigen/arpack/ARPACK/SRC/sneupd.f | 347 ++++++++-------- .../linalg/_eigen/arpack/ARPACK/SRC/sngets.f | 48 +-- .../linalg/_eigen/arpack/ARPACK/SRC/ssaitr.f | 172 ++++---- .../linalg/_eigen/arpack/ARPACK/SRC/ssapps.f | 94 ++--- .../linalg/_eigen/arpack/ARPACK/SRC/ssaup2.f | 172 ++++---- .../linalg/_eigen/arpack/ARPACK/SRC/ssaupd.f | 194 ++++----- .../linalg/_eigen/arpack/ARPACK/SRC/ssconv.f | 22 +- .../linalg/_eigen/arpack/ARPACK/SRC/sseigt.f | 26 +- .../linalg/_eigen/arpack/ARPACK/SRC/ssesrt.f | 22 +- .../linalg/_eigen/arpack/ARPACK/SRC/sseupd.f | 124 +++--- .../linalg/_eigen/arpack/ARPACK/SRC/ssgets.f | 44 +- .../linalg/_eigen/arpack/ARPACK/SRC/ssortc.f | 36 +- .../linalg/_eigen/arpack/ARPACK/SRC/ssortr.f | 18 +- .../linalg/_eigen/arpack/ARPACK/SRC/sstatn.f | 14 +- .../linalg/_eigen/arpack/ARPACK/SRC/sstats.f | 12 +- .../linalg/_eigen/arpack/ARPACK/SRC/sstqrb.f | 36 +- .../linalg/_eigen/arpack/ARPACK/SRC/zgetv0.f | 96 ++--- .../linalg/_eigen/arpack/ARPACK/SRC/znaitr.f | 216 +++++----- .../linalg/_eigen/arpack/ARPACK/SRC/znapps.f | 86 ++-- .../linalg/_eigen/arpack/ARPACK/SRC/znaup2.f | 38 +- .../linalg/_eigen/arpack/ARPACK/SRC/znaupd.f | 4 +- .../linalg/_eigen/arpack/ARPACK/SRC/zneigh.f | 50 +-- .../linalg/_eigen/arpack/ARPACK/SRC/zneupd.f | 176 ++++---- .../linalg/_eigen/arpack/ARPACK/SRC/zngets.f | 40 +- .../linalg/_eigen/arpack/ARPACK/SRC/zsortc.f | 52 +-- .../linalg/_eigen/arpack/ARPACK/SRC/zstatn.f | 8 +- .../linalg/_eigen/arpack/ARPACK/SRC/zzdotc.f | 36 ++ .../linalg/_eigen/arpack/ARPACK/UTIL/cmout.f | 24 +- .../linalg/_eigen/arpack/ARPACK/UTIL/cvout.f | 54 +-- .../_eigen/arpack/ARPACK/UTIL/second_NONE.f | 2 + .../linalg/_eigen/arpack/ARPACK/UTIL/zmout.f | 24 +- .../linalg/_eigen/arpack/ARPACK/UTIL/zvout.f | 54 +-- 77 files changed, 3545 insertions(+), 2907 deletions(-) delete mode 100644 scipy/sparse/linalg/_eigen/arpack/ARPACK/README create mode 100644 scipy/sparse/linalg/_eigen/arpack/ARPACK/README.md create mode 100644 scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ccdotc.f create mode 100644 scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zzdotc.f diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/CHANGES b/scipy/sparse/linalg/_eigen/arpack/ARPACK/CHANGES index 4efad70b734d..4ad3bd160208 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/CHANGES +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/CHANGES @@ -1,4 +1,270 @@ +arpack-ng - 3.9.1 + +[ Fabien Péan ] + * pyarpack: Ensure that the matrix properties (symmetric/hermitian) fit the solver (CG/LDL) with which they are used in the tests + * [BUG FIX] Tests for PARPACK with C/C++ bindings icb_parpack_c and icb_parpack_cpp are now really parallel and split the problem across MPI processes. + * Update arpackmm test suite: enable solving non-symmetric tests with BiCG solver + * README: Add details on Windows installation. + +[ Szabolcs Horvát ] + * [BUG FIX] Ensure that LAPACK RNG state is propagated (regression in 3.9.0). + * [BUG FIX] Ensure that separate random seeds are used on different parallel thread in D and S versions of functions (issue from original ARPACK). + +[ Ruoyu Feng ] + * ICB(arpackdef.h): distinct intel llvm compiler (icx with clang-cl) from msvc on windows + * ICB(arpackdef.h): Undef macro I if complex.h from msvc version is loaded, which is an usual name and causes issues on arpackSolver. + +[ Franck Houssen ] + * [BUG FIX] Fix install: headers in /path/to/local/include/arpack for ICB samples + * [BUG FIX] Fix install: headers in /path/to/local/include/arpack + * arpackmm: allow for using LA/SA magnitudes. + * Rename icbexmm option into eigen option. + * README: document how to use ICB. + * [BUG FIX] arpackmm: fix restart. + * README: document where to find arpack user's guide. + + -- Franck Houssen Sat, 14 Oct 2023 13:37:37 +0200 + +arpack-ng - 3.9.0 + +[ Vikas Sharma ] + * Improve README. + +[ Fabien Péan ] + * CI: Enable job `windows_latest_cmake` to run all tests + * CMake: Fix BLAS and LAPACK static library order needed to consume the library on Windows with static linkage + * Fix using ARPACK on Windows with MSVC compiler from C++17 onwards + +[ Zhentao Wang ] + * [BUG FIX] parpack.h and parpack.hpp: type of rwork should be real instead of complex. + * Allow ritz_option {"LR", "SR", "LI", "SI"} for complex eigenvalue problems in ICB. + +[ Jose E. Roman ] + * Avoid using isnan() in tests, since is GNU-specific + +[ Tom Payerle ] + * Change the continuation line format for stat.h, debug.h + +[ John Doe ] + * Avoid calling [c|z]dotc for better portability on macOS + +[ Dima Pasechnik ] + * [BUG FIX] autotools: replace obsolete AC_TRY_COMPILE macros. + * Support for NAG's nagfor Fortran compiler + +[ Franck Houssen ] + * Create one .cmake file per arpack-ng flavor (32-bits, 64-bits, ILP64). + * Test autotools pkg-config (*.pc files) with/without LIBSUFFIX/ITF64SUFFIX. + * Test CMake find_package (*.cmake files) with/without LIBSUFFIX/ITF64SUFFIX. + * [BUG FIX] autotools: ICB must be checked first (MPI changes compilers). + * [BUG FIX] BLAS/LAPACK: allow suffixes in case BLAS/LAPACK can not provide ICB. + * [BUG FIX] Compile C programs with ICB. + * arpackmm: command line bug fix. + * arpackmm: restart bug fix. + * pyarpack: fix compilation warning, test on macos and latest boost-python (1.79). + * arpackSolver: fix error messages. + * [BUG FIX] Make sure iseed is always initialized to values allowed by lapack ?larnv. + * [BUG FIX] According to lapack doc of ?larnv, iseed(4) must be odd. + * [BUG FIX] Use MPI ICB types (mpi_f08) instead of integer(kind=i_int). + * parpack: no ILP64 support. + +[ Haoyang Liu ] + * CMake: minimum required version changed to 3.0 + * CMake: add C99 standard checking + * CI: Support for centos7 added. + * CI: Add `scripts/travis_centos.sh` for centos builds + +[ Robert Schütz ] + * use CMAKE_INSTALL_FULL_ in arpack.pc + +[ Markus Mützel ] + * CMake: Handle libraries without "lib" prefix. + * CMake: Don't override BLAS/LAPACK/MPI flags. Directly use results from the Find* modules instead. + +[ Juan José García-Ripoll ] + * Adapt the C/C++ interface to accept also MSVC's non-standard complex types. + * Propagate dependencies to CMake targets that use arpack-ng: + - Create CMake-generated targets and configuration files that keep track of + arpack's dependencies (libraries, directories) and expose them to users. + - Install those files under ${prefix}/lib/cmake/arpackng* so that arpack can be + found using 'find_package(arpackng)' from CMake files. + - Add code to the arpackng-config.cmake to find required dependencies when this + module is loaded by find_package(arpackng). + + -- Sylvestre Ledru Mon, 07 Dec 2020 11:37:40 +0100 + +arpack-ng - 3.8.0 + +[ Myron Oikonomakis ] + * [BUG FIX]: bmat return "G" instead of "B" for generalized matrix in arpack.hpp + * [BUG FIX]: pass arrays of chars as scalar in fortran calls in order not to crash + * when calling subroutines through icb interface + +[ Izaak "Zaak" Beekman ] + * [BUG FIX]: fix 'Unknown CMake command "check_symbol_exists".' when ICB=ON. + +[ Franck Houssen ] + * CI: Support for Mac OS X added in automation (GNU + "-ff2c -fno-second-underscore" options). + * CI: Support for centos added in automation. + * CI: Support for opensuse added in automation. + * arpackSolver/arpackmm: switch eigen version to 3.3. + * [BUG FIX] fix arpackdef.h (resp. arpackicb.h) must be included only by C/C++ (resp. F77/F90). + * [BUG FIX] iparam/ipntr sizes may change depending on cases. + * pyarpack: python binding based on Boost.Python.Numpy exposing C++ API. + * [CLEAN] arpackSolver API: more convenient, suppress template parameters when possible. + * [BUG FIX] ICB using rvec/select: rvec/select turned to integer + bool should be, but, is not always supported (depend on compiler, options). + * extract arpackSolver.hpp from arpakmm.cpp. + * arpackmm: add --slvItrPC option (PC: Jacobi, ILU). + * arpackmm: add --slv LLT LDLT (for SPD matrices). + * arpackmm: add --simplePrec option (to enable use of s*upd). + * arpackmm: add --dense option. + * autotools: provide *.cmake files (in addition to *.pc file). + * [BUG FIX] ILP64 support: using debug_c and stat_c. + * [BUG FIX] fix check precision which may fail with some ATLAS versions. + +[ Kyle Guinn ] + * [BUG FIX]: fix 'eval: Syntax error: "(" unexpected' error at build time. + * Only build shared libraries by default. To build static libraries, use + --enable-static (autotools) or -DBUILD_SHARED_LIBS=OFF (cmake). + * Add parpack.pc and arpackSolver.pc. + +[ David Schwörer ] + * Support of gfortran 10 + + -- Sylvestre Ledru Mon, 07 Dec 2020 11:35:57 +0100 + +arpack-ng - 3.7.0 + +[ Franck Houssen ] + * [BUG FIX] ICB: missing workev for *[ds]neupd (real+not-sym) => API/ABI change for *[ds]neupd_c. + * [BUG FIX] autotools - make distcheck: fix circular dependencies. + * arpackmm: utility to test arpack with matrix market files. + * ICB: add ILP64 support. + The idea is: + - autoheader/cmake generates arpackdef.h/arpackicb.h from arpackdef.h.in/arpackicb.h.in + - in C/C++ files: arpackdef.h defines a_int according to architecture. + - in F77/F90 files: arpackicb.h defines i_int to architecture. + - MPI does not support ILP64: integer*4 must be imposed in all + calls involving MPI (f90 example/test code). + To enable ILP64 users to compile/link, arpackdef.h/arpackicb.h is added in + the arpack installation (make install). + + [ Kyle Guinn ] + * Autoconf/Automake simplifications and fixes. + * Simplify the generation of arpackdef.h. + + -- Sylvestre Ledru Sat, 12 Jan 2019 16:24:00 +0100 + +arpack-ng - 3.6.3 + +[ Franck Houssen ] + * Add Fortran common initialization (block data). + + [ Marco Caliari ] + * Give up forcing the initial residual to be in the range of the operator OP after a restart (Closes: #142). + + -- Sylvestre Ledru Wed, 19 Sep 2018 09:59:59 +0200 + +arpack-ng - 3.6.2 + + * Remove all trailing whitespaces + + [ Franck Houssen ] + * Install: move headers into a dedicated directory (local/include/arpack). + (Closes #126) + * Add configuration summary. + * Improve the flag detection. Hopefully fix the ppc64el and other archs + issues in Debian + + -- Sylvestre Ledru Sat, 23 Jun 2018 14:56:54 +0200 + +arpack-ng - 3.6.1 + + [ Ruslan Kabatsayev ] + * Fix a regression on i386 and other archs (Closes #123) + + -- Sylvestre Ledru Thu Jun 7 21:41:16 2018 +0200 + +arpack-ng - 3.6.0 + + [ Franck Houssen ] + * Add support for ISO_C_BINDING (Fortran 2003) for ARPACK, PARPACK (Fortran <-> C/C++). + ARPACK: example of C/Fortran binding can be found in the TESTS/icb_arpack_c.c file. + ARPACK: example of C++/Fortran binding can be found in the TESTS/icb_arpack_cpp.cpp file. + PARPACK: example of C/Fortran binding can be found in the PARPACK/TESTS/MPI/icb_parpack_c.c file. + PARPACK: example of C++/Fortran binding can be found in the PARPACK/TESTS/MPI/icb_parpack_cpp.cpp file. + DEBUG: add support for debug. + STAT: add support for statistics (timers, nb operations, ...). + * Provide tarball generation using cmake (cpack). + * Provide find_package for (cmake) users to find arpack-ng. + + [ Denis Davydov ] + * Rename pslamch to pslamch10 to avoid symbol collision with Scalapack 2.0.2 in MPI context. + + [ Kyle Guinn ] + * Autoconf cleanup; move generated files to the build-aux subdirectory. + + [ Marco Caliari ] + * Force the initial residual to be in the range of the operator OP in the standard case, too (Closes: #79). + + [ Sylvestre Ledru ] + * Add coverage information to improve testing: https://coveralls.io/github/opencollab/arpack-ng + + [ Darcy Beurle] + * Add C++11 interface through arpack.hpp and parpack.hpp + * Rewrite C++ examples / tests demonstrating new C++11 interface + * Pre-C++11 interface available through arpack.h and parpack.h + + -- Sylvestre Ledru Mon, 30 Oct 2017 14:21:48 +0200 + +arpack-ng - 3.5.0 + + [ Julien Schueller ] + * Improve cmake build system: disable C++ detection, set default build type. + + [ Marco Atzeri] + * Use AC_PROG_FC instead of AC_PROG_F77 for proper inizialization + for the usage of AC_FC_LINE_LENGTH. Noted on Cygwin. + + [ Denis Davydov ] + * Improve cmake build system: add make install and fix shared libraries. + + [ Zhang Z ] + * fix usages of DLACPY to not alias inputs + (patch from https://software.intel.com/en-us/articles/how-to-resolve-arpack-issues-with-intel-mkl-110-update-3) + + [ Iskakov Sergei ] + * Fix possible deadlock when PARPACK call uses communicator with a larger + number of CPUs than previous call + + [ Kyle Guinn ] + * Portability improvements to the autotools build system. + * Let cmake guess the default installation directories. Can be + overridden by changing CMAKE_INSTALL_LIBDIR and CMAKE_INSTALL_BINDIR. + * Shared libraries built by cmake now have their SONAME set identical to + those built by autotools. + + [ Marco Caliari ] + * Avoid purification stage in [d,s]neupd.f if it requires division + by zero (Closes: #58) + + -- Sylvestre Ledru Mon, 15 May 2017 14:21:48 +0200 + +arpack-ng - 3.4.0 + + [ Milan Bouchet-Valat ] + * Allow adding suffixes to symbols and library names to build ILP64 version + based on ILP64 BLAS/LAPACK with suffixes. This avoids conflicts when loading + libraries with different integer sizes in the same program. + + [ Martin Reuter ] + * Add the support of cmake build system + + -- Sylvestre Ledru Sat, 02 Jul 2016 21:51:52 +0200 + arpack-ng - 3.3.0 + [ Denis Davydov ] * Rename pdlamch to pdlamch10 to avoid symbol collision with Scalapack 2.0.2 in MPI context. @@ -6,7 +272,7 @@ arpack-ng - 3.3.0 * General improvements on the build system * libparpack links against libarpack (instead of doing a static link) - [Guillaume Horel] + [ Guillaume Horel ] * reverts using {d,s}lahqr from lapack 2 * use dlahqr from lapack 3 instead of dlaqrb (credit to Marco Caliari) @@ -40,7 +306,7 @@ arpack-ng - 3.1.5 arpack-ng - 3.1.4 * libparpack2: missing dependency on MPI: - https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=718790 + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=718790 * Replace LAPACK second function with ARPACK's own arscnd in PARPACK @@ -76,7 +342,7 @@ arpack-ng - 3.1.3 * Replace arpack.pc with proper autotooled arpack.pc.in * Add debug.h to TESTS/Makefile.am sources - * "make dist" is functionnal + * "make dist" is functional * Also build the library "libparpacksrcblacs" (PARPACK/UTIL/BLACS/) -- Sylvestre Ledru Tue, 02 Apr 2013 10:53:08 +0200 @@ -85,11 +351,11 @@ arpack-ng - 3.1.2 * Wrong call to pdlamch was causing segfaults Thanks to Kyrre Sjøbæk for finding the bug and the fix. - * Get rid of the mpif.h occurences in the source code (Closes: #782) + * Get rid of the mpif.h occurrences in the source code (Closes: #782) * Compile also PARPACK / MPI example (Closes: #783) * Configure detected built-in LAPACK and BLAS, but refused to use them (Closes: #784) - * Fixed division by zero in smlnum by usind p[d,s]lamch instead of the + * Fixed division by zero in smlnum by using p[d,s]lamch instead of the serial. Thanks to Umberto De Giovannini. -- Sylvestre Ledru Fri, 22 Jun 2012 22:05:41 +0200 @@ -110,7 +376,7 @@ arpack-ng - 3.1.0 * Change the bug report from arpack@caam.rice.edu to http://forge.scilab.org/index.php/p/arpack-ng/issues/ * Provide a M4 macro (detect_arpack_bug.m4) to check if the underlying - arpack is buggy (ie not arpack-ng). This allows developper applications + arpack is buggy (ie not arpack-ng). This allows developer applications to perform the check in their autotools build system (configure). * Fixed a lack of appropriate bounds check in DNAUP2. Thanks to Pauli Virtanen for the patch (Closes: #632) diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/README b/scipy/sparse/linalg/_eigen/arpack/ARPACK/README deleted file mode 100644 index 5359e6f5b871..000000000000 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/README +++ /dev/null @@ -1,96 +0,0 @@ -ARPACK-NG is a collection of Fortran77 subroutines designed to solve large scale -eigenvalue problems. - -Important Features: - -* Reverse Communication Interface. -* Single and Double Precision Real Arithmetic Versions for Symmetric, - Non-symmetric, Standard or Generalized Problems. -* Single and Double Precision Complex Arithmetic Versions for Standard or - Generalized Problems. -* Routines for Banded Matrices - Standard or Generalized Problems. -* Routines for The Singular Value Decomposition. -* Example driver routines that may be used as templates to implement numerous - Shift-Invert strategies for all problem types, data types and precision. - -This project is a joint project between Debian, Octave and Scilab in order to -provide a common and maintained version of arpack. - -Indeed, no single release has been published by Rice university for the last -few years and since many software (Octave, Scilab, R, Matlab...) forked it and -implemented their own modifications, arpack-ng aims to tackle this by providing -a common repository and maintained versions. - -arpack-ng is replacing arpack almost everywhere. - - -1. You have successfully unbundled ARPACK-NG and are now in the ARPACK-NG - directory that was created for you. - -2. - The directory SRC contains the top level routines including - the highest level reverse communication interface routines - - ssaupd, dsaupd - symmetric single and double precision - snaupd, dnaupd - non-symmetric single and double precision - cnaupd, znaupd - complex non-symmetric single and double precision - - The headers of these routines contain full documentation of calling - sequence and usage. Additional information is in the DOCUMENTS directory. - - The directory PARPACK contains the Parallel ARPACK routines. - - -3. Example driver programs that illustrate all the computational modes, - data types and precisions may be found in the EXAMPLES directory. - Upon executing the 'ls EXAMPLES' command you should see - - BAND - COMPLEX - NONSYM - README - SIMPLE - SVD - SYM - - Example programs for banded, complex, nonsymmetric, symmetric, - and singular value decomposition may be found in the directories - BAND, COMPLEX, NONSYM, SYM, SVD respectively. Look at the README - file for further information. To get started, get into the SIMPLE - directory to see example programs that illustrate the use of ARPACK in - the simplest modes of operation for the most commonly posed - standard eigenvalue problems. - - - Example programs for Parallel ARPACK may be found in the directory - PARPACK/EXAMPLES. Look at the README file for further information. - - The following instructions explain how to make the ARPACK library. - -4. Unlike ARPACK, ARPACK-NG is providing autotools based build system. - Therefor, the classical: - $ sh bootstrap - $ ./configure - $ make - $ make install - should work as expected. - -5. Within DOCUMENTS directory there are three files - - ex-sym.doc - ex-nonsym.doc and - ex-complex.doc - - for templates on how to invoke the computational modes of ARPACK. - Also look in the README file for explanations concerning the - other documents. - - - Danny Sorensen at sorensen@caam.rice.edu - Richard Lehoucq at rblehou@sandia.gov - Chao Yang at cyang@lbl.gov - Kristi Maschhoff at kristyn@tera.com - Sylvestre Ledru at sylvestre@debian.org - Allan Cornet at allan.cornet@scilab.org - - Good luck and enjoy. diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/README.md b/scipy/sparse/linalg/_eigen/arpack/ARPACK/README.md new file mode 100644 index 000000000000..a38fa76fd0d0 --- /dev/null +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/README.md @@ -0,0 +1,376 @@ +# arpack-ng [![arpack-ng CI/CD](https://github.com/opencollab/arpack-ng/actions/workflows/jobs.yml/badge.svg)](https://github.com/opencollab/arpack-ng/actions/workflows/jobs.yml) + +ARPACK-NG is a collection of Fortran77 subroutines designed to solve large scale eigenvalue problems. +| mandatory dependencies | optional dependencies | category | +|------------------------|---------------------------|---------------| +| BLAS, LAPACK | MPI, Eigen3, Boost.Python | LinearAlgebra | + +## About the project + +This project started as a joint project between Debian, Octave and Scilab in order to provide a common and maintained version of arpack. +This is now a community project maintained by a few volunteers. +Indeed, no single release has been published by Rice university for the last few years and since many software (Octave, Scilab, R, Matlab...) +forked it and implemented their own modifications, arpack-ng aims to tackle this by providing a common repository, maintained versions with a testsuite. +`arpack-ng` is replacing arpack almost everywhere. + +## Important Features + +- Reverse Communication Interface (RCI). +- Single and Double Precision Real Arithmetic Versions for Symmetric, Non-symmetric, Standard or Generalized Problems. +- Single and Double Precision Complex Arithmetic Versions for Standard or Generalized Problems. +- Routines for Banded Matrices - Standard or Generalized Problems. +- Routines for The Singular Value Decomposition. +- Example driver routines that may be used as templates to implement numerous +- Shift-Invert strategies for all problem types, data types and precision. +- `arpackmm`: utility to test arpack with matrix market files. Note: to run this utility, you need the eigen library (to handle RCI). + +## Documentation + +Within DOCUMENTS directory there are three files for templates on how to invoke the computational modes of ARPACK. + +- ex-sym.doc +- ex-nonsym.doc and +- ex-complex.doc + +Also look in the README.MD file for explanations concerning the +other documents. + +## ILP64 support + +About ILP64 support: + +- Sequential arpack supports [ILP64](https://www.intel.com/content/www/us/en/develop/documentation/onemkl-linux-developer-guide/top/linking-your-application-with-onemkl/linking-in-detail/linking-with-interface-libraries/using-the-ilp64-interface-vs-lp64-interface.html), but, parallel arpack doesn't. +- Reminder: you can NOT mix `ILP64` with `LP64`. If you compile `arpack-ng` with `ILP64` (resp. `LP64`) support, you MUST insure your BLAS/LAPACK is compliant with `ILP64` (resp. `LP64`). +- Set `INTERFACE64` at configure time. + +Note for F77/F90 developers: + +- All files which needs `ILP64` support must include `"arpackicb.h"`. +- When coding, use `i_int` (defined in `arpackicb.h`) instead of `c_int`. `i_int` stands for ISO_C_BINDING integer: it's `#defined` to `c_int` or `c_int64_t` according to the architecture. + +Note for C/C++ developers: + +- All files which needs `ILP64` support must include `"arpackdef.h"`. +- When coding, use `a_int` (defined in `arpackdef.h`) instead of `int`. Here, `a_int` stands for "architecture int": it's `#defined` to `int` or `int64_t` according to the architecture. + +**Example**: to test arpack with sequential `ILP64` MKL assuming you use gnu compilers + +```bash +$ ./bootstrap +$ export FFLAGS='-DMKL_ILP64 -I/usr/include/mkl' +$ export FCFLAGS='-DMKL_ILP64 -I/usr/include/mkl' +$ export LIBS='-Wl,--no-as-needed -L/usr/lib/x86_64-linux-gnu -lmkl_sequential -lmkl_core -lpthread -lm -ldl' +$ export INTERFACE64=1 +$ ./configure --with-blas=mkl_gf_ilp64 --with-lapack=mkl_gf_ilp64 +$ make all check +``` + +## ISO_C_BINDING support + +About ISO_C_BINDING support: + +- The install will now provide `arpack.h/hpp`, `parpack.h/hpp` and friends. +- Examples of use can be found in `./TESTS` and` ./PARPACK/TESTS/MPI`. + +ISO_C_BINDING is a feature of modern Fortran meant to handle safely interoperability between Fortran and C (in practice, no more need to use ugly tricks to link F77 functions to C code using "underscored" symbols). Basically, ISO_C_BINDING make sure all fortran variables are typed (which may not always be the case when using `implicit` keyword in fortran): this way, C compilers can link properly. For more informations on ISO_C_BINDING, you can checkout the following links: + +- +- + +Using ICB is seamless: + +- Compile `arpack-ng` with ISO_C_BINDING: you'll get both old-fashion fortran symbols and new ISO_C_BINDING symbols available for linking. +- Add `#include "arpack.h"` in your C code. +- Replace all [sdcz][ae]upd calls by [sdcz][ae]upd_c: functions suffixed with _c are ISO_C_BINDING compliant (exposing same arguments than original fortran functions). + +**Example**: to test arpack with ISO_C_BINDING + +```bash +$ ./configure --enable-icb +$ cmake -D ICB=ON +``` + +## Eigen support + +`arpack-ng` provides C++ eigensolver based on both ISO_C_BINDING and `eigen`. + +Check out `./EXAMPLES/MATRIX_MARKET/README` for more details. + +**Example**: to test arpack with `eigen` + +```bash +$ mkdir build +$ cd build +$ cmake -D EXAMPLES=ON -D ICB=ON -D EIGEN=ON .. +$ make all check +``` + +## Python support + +`pyarpack`: python support based on `Boost.Python.Numpy` exposing C++ API. +`pyarpack` exposes in python the `arpack-ng` C++ eigensolver (based on `eigen`). + +Check out `./EXAMPLES/PYARPACK/README` for more details. + +**Example**: to test arpack with python3 + +```bash +$ mkdir build +$ cd build +$ cmake -D EXAMPLES=ON -D ICB=ON -D EIGEN=ON -D PYTHON3=ON .. +$ make all check +``` + +## 📁 Directory structure + +- You have successfully unbundled ARPACK-NG` and are now in the ARPACK-NG directory that was created for you. + +- The directory SRC contains the top level routines including the highest level **reverse communication interface** routines + + - `ssaupd`, `dsaupd`: symmetric single and double precision + - `snaupd`, `dnaupd`: non-symmetric single and double precision + - `cnaupd`, `znaupd`: complex non-symmetric single and double precision + - The headers of these routines contain full documentation of calling sequence and usage. + - Additional information is given in the `/DOCUMENTS` directory. + +- The directory `PARPACK` contains the Parallel ARPACK routines. + +- Example driver programs that illustrate all the computational modes, data types and precisions may be found in the EXAMPLES directory. Upon executing the `ls EXAMPLES` command you should see the following directories + + ```bash + ├── BAND + ├── COMPLEX + ├── Makefile.am + ├── MATRIX_MARKET + ├── NONSYM + ├── PYARPACK + ├── README + ├── SIMPLE + ├── SVD + └── SYM + ``` + + - Example programs for banded, complex, nonsymmetric, symmetric, and singular value decomposition may be found in the directories BAND, COMPLEX, NONSYM, SYM, SVD respectively. + - Look at the README file for further information. + - To get started, get into the SIMPLE directory to see example programs that illustrate the use of ARPACK in the simplest modes of operation for the most commonly posed standard eigenvalue problems. + +> Example programs for Parallel ARPACK may be found in the directory `PARPACK/EXAMPLES`. Look at the README file for further information. + +## Install 🚀 + +### Getting arpack-ng + +Unlike ARPACK, ARPACK-NG is providing autotools and cmake based build system. In addition, `ARPACK-NG` also provides +ISO_C_BINDING support, which enables to call fortran subroutines natively from C or C++. + +First, obtain the source code 📥 from github: + +```bash +$ git clone https://github.com/opencollab/arpack-ng.git +$ cd ./arpack-ng +``` + +If you prefer the ssh to obtain the source code, then use: + +```bash +$ git clone git@github.com:opencollab/arpack-ng.git +$ cd ./arpack-ng +``` + +> Note, It is recommended to install `arpack` at standard location on your system by using your root privilege. + +### Using autotools + +In the source directory, use the following commands to configure, build and install `arpack-ng`. + +```bash +$ sh bootstrap +$ ./configure --enable-mpi +$ make +$ make check +$ sudo make install +``` + +Congratulations 🎉, you have installed `arpack` lib using autotools (caution: you need `sudo` to install in your system). + +The above-mentioned process will build everything including the examples and parallel support using MPI. + +### Using cmake + +You can install `ARPACK-NG` by using cmake. If you do not have cmake, then please download the binary from `pip` using: + +```bash +$ python3 -m pip install cmake +$ which cmake && cmake --version +``` + +After installing cmake, follow the instruction given below. + +Caution: Make sure you are in source directory of ARPACK-NG. + +```bash +$ mkdir build +$ cd build +$ cmake -D EXAMPLES=ON -D MPI=ON -D BUILD_SHARED_LIBS=ON .. +$ make +$ sudo make install +``` + +✨ Congratulations, you have installed `arpack` lib using cmake (caution: you need `sudo` to install in your system). + +The above-mentioned process will build everything including the examples and parallel support using MPI. + +### Customize build / install + +You can also customize the installation of `arpack` using the autotools. + +To customize the install directories: + +```bash +$ LIBSUFFIX="64" ./configure +$ make all install +``` + +To enable ILP64 support: + +```bash +$ INTERFACE64="1" ITF64SUFFIX="ILP64" ./configure +$ make all install +``` + +To enable ISO_C_BINDING support: + +```bash +$ ./configure --enable-icb +``` + +You can customize the build by declaring the cmake options during configuration. + +To customize the install directories: + +```bash +$ cmake -D LIBSUFFIX="64" .. +$ make all install +``` + +To enable ILP64 support: + +```bash +$ cmake -D INTERFACE64=ON -D ITF64SUFFIX="ILP64" .. +$ make all install +``` + +To enable ISO_C_BINDING support: + +```bash +$ cmake -D ICB=ON +``` + +## Supported Operating Systems: + +### Linux support + +`arpack-ng` runs on debian-based distros. + +### Mac OS support + +On mac OS, with GNU compilers, you may need to customize options: + +```bash +$ LIBS="-framework Accelerate" FFLAGS="-ff2c -fno-second-underscore" FCFLAGS="-ff2c -fno-second-underscore" ./configure +``` + +### Windows support + +`arpack-ng` can be installed on Windows as a MinGW-w64 package via various distribution, for example through [MSYS2](https://packages.msys2.org/package/mingw-w64-x86_64-arpack) with `pacman -S mingw-w64-x86_64-arpack`. It can also be built and installed through [vcpkg](https://github.com/microsoft/vcpkg) with `vcpkg install arpack-ng`. + +## Using arpack-ng from your own codebase + +The `*.pc` and `*.cmake` files provided by `arpack-ng` are only pointing to arpack libraries. +If you need other libraries (like MPI), you must add them alongside arpack (see CMake example below). + +Typically, if you need + +- ARPACK: at compile/link time, you'll need to provide BLAS and LAPACK. + +- ARPACK with eigen support (arpackSolver): at compile/link time, you'll need to provide BLAS, LAPACK and Eigen. + +- PARPACK: at compile/link time, you'll need to provide BLAS, LAPACK and MPI. + +Examples are provided in `tstCMakeInstall.sh` and `tstAutotoolsInstall.sh` generated after running cmake/configure. + +### With autotools + +First, set `PKG_CONFIG_PATH` to the location in the installation directory where `arpack.pc` lies. + +Then, insert the following lines in your `configure.ac`: +``` +PKG_CHECK_MODULES([ARPACK], [arpack]) +AC_SUBST([ARPACK_CFLAGS]) +AC_SUBST([ARPACK_LIBS]) +``` + +Note: make sure you have installed `pkg-config`. + +### With CMake + +You can use arpack in your CMake builds by using `ARPACK::ARPACK` target. For example, + +```cmake +FIND_PACKAGE(arpackng) +ADD_EXECUTABLE(main main.f) +TARGET_INCLUDE_DIRECTORIES(main PUBLIC ARPACK::ARPACK) +TARGET_LINK_LIBRARIES(main ARPACK::ARPACK) +``` + +To use PARPACK in your Cmake builds, use `PARPACK::PARPACK` target: + +```cmake +FIND_PACKAGE(arpackng) +FIND_PACKAGE(MPI REQUIRED COMPONENTS Fortran) +ADD_EXECUTABLE(main main.f) +TARGET_INCLUDE_DIRECTORIES(main PUBLIC PARPACK::PARPACK) +TARGET_LINK_LIBRARIES(main PARPACK::PARPACK) +TARGET_INCLUDE_DIRECTORIES(main PUBLIC MPI::MPI_Fortran) +TARGET_LINK_LIBRARIES(main MPI::MPI_Fortran) +``` + +Note: Make sure to update `CMAKE_MODULE_PATH` env variable (otheriwse, `find_package` won't find arpack-ng cmake file). + +### FAQ + +- Where can I find ARPACK user's guide? + + http://li.mit.edu/Archive/Activities/Archive/CourseWork/Ju_Li/MITCourses/18.335/Doc/ARPACK/Lehoucq97.pdf + +- Calling arpack's aupd methods returns `info = -9 - Starting vector is zero.`: why? + + Residuals are null. Try to set `resid` to small values (like epsilon machine magnitude) but *not exactly* zero. + Residuals `resid = A*v - lamdba*v` target *exactly* the zero vector. + When `resid` is close enough to zero, the iterative procedure stops. + +- Say I have an estimate of an eigen value, how to give this information to arpack? + + You need to shift of an amount of about this estimate of `lambda`. Grep `backTransform` in `arpackSolver.hpp` to see an example. + For more informations, checkout "NUMERICAL METHODS FOR LARGE EIGENVALUE PROBLEMS" by Yousef Saad: https://www-users.cse.umn.edu/~saad/eig_book_2ndEd.pdf (paragraph 4.1.2. and section 4.1.). + +- Say I have an estimate of an eigen vector, how to give this information to arpack? + + You need to copy this eigen vector estimate in `v` (not `resid`) and set `info` to 1 before calling aupd methods. + The `v` vector targets a non-null vector such that `resid = 0`, that is, such that `A*v = lambda*v`. + +- Using PARPACK, I get incorrect eigen values. + + Make sure each MPI processor handles a subpart of the eigen system (matrices) only. + ARPACK handles and solves the whole eigen problem (matrices) at once. + PARPACK doesn't: each MPI processor must handle and solve a subpart of the eigen system (matrices) only (independently from the other processors). + See examples for Fortran in folder `PARPACK/EXAMPLES/MPI`, and for C/C++ examples in `PARPACK/TESTS/MPI/icb_parpack_c.c` and `PARPACK/TESTS/MPI/icb_parpack_cpp.cpp` + +## Using MKL instead of BLAS / LAPACK + +How to use arpack-ng with Intel MKL: + +- Let autotools/cmake find MKL for you based on pkg-config files (setting `PKG_CONFIG_PATH`) or cmake options (`BLA_VENDOR=Intel10_64lp` for lp64, `BLA_VENDOR=Intel10_64ilp` for ilp64). +- Refers to the Intel Link Advisor: . + +## Good luck and enjoy 🎊 diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/README.scipy b/scipy/sparse/linalg/_eigen/arpack/ARPACK/README.scipy index 40882b1d3ca1..5cdb5a96c86f 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/README.scipy +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/README.scipy @@ -1,4 +1,4 @@ -This directory contains a bundled version of ARPACK-NG 3.3.0, +This directory contains a bundled version of ARPACK-NG 3.9.1, https://github.com/opencollab/arpack-ng NOTE FOR VENDORS: it is in general safe to use a system version of ARPACK @@ -18,5 +18,5 @@ s@\bcdotu\b@wcdotu@g; s@\bzdotu\b@wzdotu@g; s@\bcladiv\b@wcladiv@g; s@\bzladiv\b@wzladiv@g; -s@\bSLAMCH\b@slamch@g;' \ -SRC/*.f UTIL/*.f LAPACK/*.f +s@\bslamch\b@slamch@g;' \ +SRC/*.f UTIL/*.f diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ccdotc.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ccdotc.f new file mode 100644 index 000000000000..f0f94f4223a2 --- /dev/null +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ccdotc.f @@ -0,0 +1,36 @@ + complex function ccdotc(n,zx,incx,zy,incy) +c +c forms the dot product of a vector. +c jack dongarra, 3/11/78. +c modified 12/3/93, array(1) declarations changed to array(*) +c + complex zx(*),zy(*),ztemp + integer i,incx,incy,ix,iy,n + ztemp = (0.0d0,0.0d0) + ccdotc = (0.0d0,0.0d0) + if(n.le.0)return + if(incx.eq.1.and.incy.eq.1)go to 20 +c +c code for unequal increments or equal increments +c not equal to 1 +c + ix = 1 + iy = 1 + if(incx.lt.0)ix = (-n+1)*incx + 1 + if(incy.lt.0)iy = (-n+1)*incy + 1 + do 10 i = 1,n + ztemp = ztemp + conjg(zx(ix))*zy(iy) + ix = ix + incx + iy = iy + incy + 10 continue + ccdotc = ztemp + return +c +c code for both increments equal to 1 +c + 20 do 30 i = 1,n + ztemp = ztemp + conjg(zx(i))*zy(i) + 30 continue + ccdotc = ztemp + return + end diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cgetv0.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cgetv0.f index 1d97ee6641e5..c231eadcb4b4 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cgetv0.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cgetv0.f @@ -2,13 +2,13 @@ c c\Name: cgetv0 c -c\Description: +c\Description: c Generate a random initial residual vector for the Arnoldi process. -c Force the residual vector to be in the range of the operator OP. +c Force the residual vector to be in the range of the operator OP. c c\Usage: c call cgetv0 -c ( IDO, BMAT, ITRY, INITV, N, J, V, LDV, RESID, RNORM, +c ( IDO, BMAT, ITRY, INITV, N, J, V, LDV, RESID, RNORM, c IPNTR, WORKD, IERR ) c c\Arguments @@ -35,7 +35,7 @@ c B = 'G' -> generalized eigenvalue problem A*x = lambda*B*x c c ITRY Integer. (INPUT) -c ITRY counts the number of times that cgetv0 is called. +c ITRY counts the number of times that cgetv0 is called. c It should be set to 1 on the initial call to cgetv0. c c INITV Logical variable. (INPUT) @@ -54,11 +54,11 @@ c if this is a "restart". c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c RESID Complex array of length N. (INPUT/OUTPUT) -c Initial residual vector to be generated. If RESID is +c Initial residual vector to be generated. If RESID is c provided, force RESID into the range of the operator OP. c c RNORM Real scalar. (OUTPUT) @@ -91,19 +91,19 @@ c\Routines called: c arscnd ARPACK utility routine for timing. c cvout ARPACK utility routine that prints vectors. -c clarnv LAPACK routine for generating a random vector. +c clarnv LAPACK routine for generating a random vector. c cgemv Level 2 BLAS routine for matrix vector multiplication. c ccopy Level 1 BLAS that copies one vector to another. -c wcdotc Level 1 BLAS that computes the scalar product of two vectors. -c scnrm2 Level 1 BLAS that computes the norm of a vector. +c cdotc Level 1 BLAS that computes the scalar product of two vectors. +c scnrm2 Level 1 BLAS that computes the norm of a vector. c c\Author c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c\SCCS Information: @(#) c FILE: getv0.F SID: 2.3 DATE OF SID: 08/27/96 RELEASE: 2 @@ -112,10 +112,10 @@ c c----------------------------------------------------------------------- c - subroutine cgetv0 - & ( ido, bmat, itry, initv, n, j, v, ldv, resid, rnorm, + subroutine cgetv0 + & ( ido, bmat, itry, initv, n, j, v, ldv, resid, rnorm, & ipntr, workd, ierr ) -c +c c %----------------------------------------------------% c | Include files for debugging and timing information | c %----------------------------------------------------% @@ -174,11 +174,11 @@ subroutine cgetv0 c | External Functions | c %--------------------% c - Real + Real & scnrm2, slapy2 Complex - & wcdotc - external wcdotc, scnrm2, slapy2 + & ccdotc + external ccdotc, scnrm2, slapy2 c c %-----------------% c | Data Statements | @@ -205,7 +205,7 @@ subroutine cgetv0 end if c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -213,7 +213,7 @@ subroutine cgetv0 c call arscnd (t0) msglvl = mgetv0 -c +c ierr = 0 iter = 0 first = .FALSE. @@ -232,38 +232,40 @@ subroutine cgetv0 idist = 2 call clarnv (idist, iseed, n, resid) end if -c +c c %----------------------------------------------------------% c | Force the starting vector into the range of OP to handle | c | the generalized problem when B is possibly (singular). | c %----------------------------------------------------------% c call arscnd (t2) - if (bmat .eq. 'G') then + if (itry .eq. 1) then nopx = nopx + 1 ipntr(1) = 1 ipntr(2) = n + 1 call ccopy (n, resid, 1, workd, 1) ido = -1 go to 9000 + else if (itry .gt. 1 .and. bmat .eq. 'G') then + call ccopy (n, resid, 1, workd(n + 1), 1) end if end if -c +c c %----------------------------------------% -c | Back from computing B*(initial-vector) | +c | Back from computing OP*(initial-vector) | c %----------------------------------------% c if (first) go to 20 c c %-----------------------------------------------% -c | Back from computing B*(orthogonalized-vector) | +c | Back from computing OP*(orthogonalized-vector) | c %-----------------------------------------------% c if (orth) go to 40 -c +c call arscnd (t3) tmvopx = tmvopx + (t3 - t2) -c +c c %------------------------------------------------------% c | Starting vector is now in the range of OP; r = OP*r; | c | Compute B-norm of starting vector. | @@ -271,9 +273,9 @@ subroutine cgetv0 c call arscnd (t2) first = .TRUE. + if (itry .eq. 1) call ccopy (n, workd(n + 1), 1, resid, 1) if (bmat .eq. 'G') then nbx = nbx + 1 - call ccopy (n, workd(n+1), 1, resid, 1) ipntr(1) = n + 1 ipntr(2) = 1 ido = 2 @@ -281,17 +283,17 @@ subroutine cgetv0 else if (bmat .eq. 'I') then call ccopy (n, resid, 1, workd, 1) end if -c +c 20 continue c if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c first = .FALSE. if (bmat .eq. 'G') then - cnorm = wcdotc (n, resid, 1, workd, 1) + cnorm = ccdotc (n, resid, 1, workd, 1) rnorm0 = sqrt(slapy2(real(cnorm),aimag(cnorm))) else if (bmat .eq. 'I') then rnorm0 = scnrm2(n, resid, 1) @@ -303,7 +305,7 @@ subroutine cgetv0 c %---------------------------------------------% c if (j .eq. 1) go to 50 -c +c c %---------------------------------------------------------------- c | Otherwise need to B-orthogonalize the starting vector against | c | the current Arnoldi basis using Gram-Schmidt with iter. ref. | @@ -319,11 +321,11 @@ subroutine cgetv0 orth = .TRUE. 30 continue c - call cgemv ('C', n, j-1, one, v, ldv, workd, 1, + call cgemv ('C', n, j-1, one, v, ldv, workd, 1, & zero, workd(n+1), 1) - call cgemv ('N', n, j-1, -one, v, ldv, workd(n+1), 1, + call cgemv ('N', n, j-1, -one, v, ldv, workd(n+1), 1, & one, resid, 1) -c +c c %----------------------------------------------------------% c | Compute the B-norm of the orthogonalized starting vector | c %----------------------------------------------------------% @@ -339,16 +341,16 @@ subroutine cgetv0 else if (bmat .eq. 'I') then call ccopy (n, resid, 1, workd, 1) end if -c +c 40 continue c if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c if (bmat .eq. 'G') then - cnorm = wcdotc (n, resid, 1, workd, 1) + cnorm = ccdotc (n, resid, 1, workd, 1) rnorm = sqrt(slapy2(real(cnorm),aimag(cnorm))) else if (bmat .eq. 'I') then rnorm = scnrm2(n, resid, 1) @@ -359,14 +361,14 @@ subroutine cgetv0 c %--------------------------------------% c if (msglvl .gt. 2) then - call svout (logfil, 1, rnorm0, ndigit, + call svout (logfil, 1, [rnorm0], ndigit, & '_getv0: re-orthonalization ; rnorm0 is') - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_getv0: re-orthonalization ; rnorm is') end if c if (rnorm .gt. 0.717*rnorm0) go to 50 -c +c iter = iter + 1 if (iter .le. 1) then c @@ -388,11 +390,11 @@ subroutine cgetv0 rnorm = rzero ierr = -1 end if -c +c 50 continue c if (msglvl .gt. 0) then - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_getv0: B-norm of initial / restarted starting vector') end if if (msglvl .gt. 2) then @@ -400,10 +402,10 @@ subroutine cgetv0 & '_getv0: initial / restarted starting vector') end if ido = 99 -c +c call arscnd (t1) tgetv0 = tgetv0 + (t1 - t0) -c +c 9000 continue return c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaitr.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaitr.f index d7791557a343..3759760dfbde 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaitr.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaitr.f @@ -2,8 +2,8 @@ c c\Name: cnaitr c -c\Description: -c Reverse communication interface for applying NP additional steps to +c\Description: +c Reverse communication interface for applying NP additional steps to c a K step nonsymmetric Arnoldi factorization. c c Input: OP*V_{k} - V_{k}*H = r_{k}*e_{k}^T @@ -19,7 +19,7 @@ c c\Usage: c call cnaitr -c ( IDO, BMAT, N, K, NP, NB, RESID, RNORM, V, LDV, H, LDH, +c ( IDO, BMAT, N, K, NP, NB, RESID, RNORM, V, LDV, H, LDH, c IPNTR, WORKD, INFO ) c c\Arguments @@ -61,8 +61,8 @@ c Number of additional Arnoldi steps to take. c c NB Integer. (INPUT) -c Blocksize to be used in the recurrence. -c Only work for NB = 1 right now. The goal is to have a +c Blocksize to be used in the recurrence. +c Only work for NB = 1 right now. The goal is to have a c program that implement both the block and non-block method. c c RESID Complex array of length N. (INPUT/OUTPUT) @@ -74,37 +74,37 @@ c B-norm of the updated residual r_{k+p} on output. c c V Complex N by K+NP array. (INPUT/OUTPUT) -c On INPUT: V contains the Arnoldi vectors in the first K +c On INPUT: V contains the Arnoldi vectors in the first K c columns. c On OUTPUT: V contains the new NP Arnoldi vectors in the next c NP columns. The first K columns are unchanged. c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c H Complex (K+NP) by (K+NP) array. (INPUT/OUTPUT) c H is used to store the generated upper Hessenberg matrix. c c LDH Integer. (INPUT) -c Leading dimension of H exactly as declared in the calling +c Leading dimension of H exactly as declared in the calling c program. c c IPNTR Integer array of length 3. (OUTPUT) -c Pointer to mark the starting locations in the WORK for +c Pointer to mark the starting locations in the WORK for c vectors used by the Arnoldi iteration. c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X. c IPNTR(2): pointer to the current result vector Y. -c IPNTR(3): pointer to the vector B * X when used in the +c IPNTR(3): pointer to the vector B * X when used in the c shift-and-invert mode. X is the current operand. c ------------------------------------------------------------- -c +c c WORKD Complex work array of length 3*N. (REVERSE COMMUNICATION) c Distributed array to be used in the basic Arnoldi iteration -c for reverse communication. The calling program should not +c for reverse communication. The calling program should not c use WORKD as temporary workspace during the iteration !!!!!! -c On input, WORKD(1:N) = B*RESID and is used to save some +c On input, WORKD(1:N) = B*RESID and is used to save some c computation at the first step. c c INFO Integer. (OUTPUT) @@ -124,7 +124,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c @@ -143,29 +143,29 @@ c cgemv Level 2 BLAS routine for matrix vector multiplication. c caxpy Level 1 BLAS that computes a vector triad. c ccopy Level 1 BLAS that copies one vector to another . -c wcdotc Level 1 BLAS that computes the scalar product of two vectors. +c cdotc Level 1 BLAS that computes the scalar product of two vectors. c cscal Level 1 BLAS that scales a vector. -c csscal Level 1 BLAS that scales a complex vector by a real number. +c csscal Level 1 BLAS that scales a complex vector by a real number. c scnrm2 Level 1 BLAS that computes the norm of a vector. c c\Author c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University -c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas -c +c Dept. of Computational & Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas +c c\SCCS Information: @(#) c FILE: naitr.F SID: 2.3 DATE OF SID: 8/27/96 RELEASE: 2 c c\Remarks c The algorithm implemented is: -c +c c restart = .false. -c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; +c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; c r_{k} contains the initial residual vector even for k = 0; -c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already +c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already c computed by the calling program. c c betaj = rnorm ; p_{k+1} = B*r_{k} ; @@ -173,7 +173,7 @@ c 1) if ( betaj < tol ) stop or restart depending on j. c ( At present tol is zero ) c if ( restart ) generate a new starting vector. -c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; +c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; c p_{j} = p_{j}/betaj c 3) r_{j} = OP*v_{j} where OP is defined as in cnaupd c For shift-invert mode p_{j} = B*v_{j} is already available. @@ -188,7 +188,7 @@ c 5) Re-orthogonalization step: c s = V_{j}'*B*r_{j} c r_{j} = r_{j} - V_{j}*s; rnorm1 = || r_{j} || -c alphaj = alphaj + s_{j}; +c alphaj = alphaj + s_{j}; c 6) Iterative refinement step: c If (rnorm1 > 0.717*rnorm) then c rnorm = rnorm1 @@ -198,7 +198,7 @@ c If this is the first time in step 6), go to 5) c Else r_{j} lies in the span of V_{j} numerically. c Set r_{j} = 0 and rnorm = 0; go to 1) -c EndIf +c EndIf c End Do c c\EndLib @@ -206,7 +206,7 @@ c----------------------------------------------------------------------- c subroutine cnaitr - & (ido, bmat, n, k, np, nb, resid, rnorm, v, ldv, h, ldh, + & (ido, bmat, n, k, np, nb, resid, rnorm, v, ldv, h, ldh, & ipntr, workd, info) c c %----------------------------------------------------% @@ -241,7 +241,7 @@ subroutine cnaitr & one, zero Real & rone, rzero - parameter (one = (1.0E+0, 0.0E+0), zero = (0.0E+0, 0.0E+0), + parameter (one = (1.0E+0, 0.0E+0), zero = (0.0E+0, 0.0E+0), & rone = 1.0E+0, rzero = 0.0E+0) c c %--------------% @@ -258,7 +258,7 @@ subroutine cnaitr logical first, orth1, orth2, rstart, step3, step4 integer ierr, i, infol, ipj, irj, ivj, iter, itry, j, msglvl, & jj - Real + Real & ovfl, smlnum, tst1, ulp, unfl, betaj, & temp1, rnorm1, wnorm Complex @@ -272,7 +272,7 @@ subroutine cnaitr c | External Subroutines | c %----------------------% c - external caxpy, ccopy, cscal, csscal, cgemv, cgetv0, + external caxpy, ccopy, cscal, csscal, cgemv, cgetv0, & slabad, cvout, cmout, ivout, arscnd c c %--------------------% @@ -280,16 +280,16 @@ subroutine cnaitr c %--------------------% c Complex - & wcdotc - Real + & ccdotc + Real & slamch, scnrm2, clanhs, slapy2 - external wcdotc, scnrm2, clanhs, slamch, slapy2 + external ccdotc, scnrm2, clanhs, slamch, slapy2 c c %---------------------% c | Intrinsic Functions | c %---------------------% c - intrinsic aimag, real, max, sqrt + intrinsic aimag, real, max, sqrt c c %-----------------% c | Data statements | @@ -320,7 +320,7 @@ subroutine cnaitr end if c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -328,7 +328,7 @@ subroutine cnaitr c call arscnd (t0) msglvl = mcaitr -c +c c %------------------------------% c | Initial call to this routine | c %------------------------------% @@ -344,7 +344,7 @@ subroutine cnaitr irj = ipj + n ivj = irj + n end if -c +c c %-------------------------------------------------% c | When in reverse communication mode one of: | c | STEP3, STEP4, ORTH1, ORTH2, RSTART | @@ -374,16 +374,16 @@ subroutine cnaitr c | | c | Note: B*r_{j-1} is already in WORKD(1:N)=WORKD(IPJ:IPJ+N-1) | c %--------------------------------------------------------------% - + 1000 continue c if (msglvl .gt. 1) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: generating Arnoldi vector number') - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_naitr: B-norm of the current residual is') end if -c +c c %---------------------------------------------------% c | STEP 1: Check if the B norm of j-th residual | c | vector is zero. Equivalent to determine whether | @@ -400,16 +400,16 @@ subroutine cnaitr c %---------------------------------------------------% c if (msglvl .gt. 0) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: ****** RESTART AT STEP ******') end if -c +c c %---------------------------------------------% c | ITRY is the loop variable that controls the | c | maximum amount of times that a restart is | c | attempted. NRSTRT is used by stat.h | c %---------------------------------------------% -c +c betaj = rzero nrstrt = nrstrt + 1 itry = 1 @@ -423,7 +423,7 @@ subroutine cnaitr c | RSTART = .true. flow returns here. | c %--------------------------------------% c - call cgetv0 (ido, bmat, itry, .false., n, j, v, ldv, + call cgetv0 (ido, bmat, itry, .false., n, j, v, ldv, & resid, rnorm, ipntr, workd, ierr) if (ido .ne. 99) go to 9000 if (ierr .lt. 0) then @@ -442,7 +442,7 @@ subroutine cnaitr ido = 99 go to 9000 end if -c +c 40 continue c c %---------------------------------------------------------% @@ -466,7 +466,7 @@ subroutine cnaitr c call clascl ('General', i, i, rnorm, rone, & n, 1, v(1,j), n, infol) - call clascl ('General', i, i, rnorm, rone, + call clascl ('General', i, i, rnorm, rone, & n, 1, workd(ipj), n, infol) end if c @@ -483,14 +483,14 @@ subroutine cnaitr ipntr(2) = irj ipntr(3) = ipj ido = 1 -c +c c %-----------------------------------% c | Exit in order to compute OP*v_{j} | c %-----------------------------------% -c - go to 9000 +c + go to 9000 50 continue -c +c c %----------------------------------% c | Back from reverse communication; | c | WORKD(IRJ:IRJ+N-1) := OP*v_{j} | @@ -499,7 +499,7 @@ subroutine cnaitr c call arscnd (t3) tmvopx = tmvopx + (t3 - t2) - + step3 = .false. c c %------------------------------------------% @@ -507,7 +507,7 @@ subroutine cnaitr c %------------------------------------------% c call ccopy (n, workd(irj), 1, resid, 1) -c +c c %---------------------------------------% c | STEP 4: Finish extending the Arnoldi | c | factorization to length j. | @@ -520,17 +520,17 @@ subroutine cnaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-------------------------------------% c | Exit in order to compute B*OP*v_{j} | c %-------------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call ccopy (n, resid, 1, workd(ipj), 1) end if 60 continue -c +c c %----------------------------------% c | Back from reverse communication; | c | WORKD(IPJ:IPJ+N-1) := B*OP*v_{j} | @@ -541,7 +541,7 @@ subroutine cnaitr call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c step4 = .false. c c %-------------------------------------% @@ -549,8 +549,8 @@ subroutine cnaitr c | Compute the B-norm of OP*v_{j}. | c %-------------------------------------% c - if (bmat .eq. 'G') then - cnorm = wcdotc (n, resid, 1, workd(ipj), 1) + if (bmat .eq. 'G') then + cnorm = ccdotc (n, resid, 1, workd(ipj), 1) wnorm = sqrt( slapy2(real(cnorm),aimag(cnorm)) ) else if (bmat .eq. 'I') then wnorm = scnrm2(n, resid, 1) @@ -569,13 +569,13 @@ subroutine cnaitr c | Compute the j Fourier coefficients w_{j} | c | WORKD(IPJ:IPJ+N-1) contains B*OP*v_{j}. | c %------------------------------------------% -c +c call cgemv ('C', n, j, one, v, ldv, workd(ipj), 1, & zero, h(1,j), 1) c c %--------------------------------------% c | Orthogonalize r_{j} against V_{j}. | -c | RESID contains OP*v_{j}. See STEP 3. | +c | RESID contains OP*v_{j}. See STEP 3. | c %--------------------------------------% c call cgemv ('N', n, j, -one, v, ldv, h(1,j), 1, @@ -584,9 +584,9 @@ subroutine cnaitr if (j .gt. 1) h(j,j-1) = cmplx(betaj, rzero) c call arscnd (t4) -c +c orth1 = .true. -c +c call arscnd (t2) if (bmat .eq. 'G') then nbx = nbx + 1 @@ -594,17 +594,17 @@ subroutine cnaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %----------------------------------% c | Exit in order to compute B*r_{j} | c %----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call ccopy (n, resid, 1, workd(ipj), 1) - end if + end if 70 continue -c +c c %---------------------------------------------------% c | Back from reverse communication if ORTH1 = .true. | c | WORKD(IPJ:IPJ+N-1) := B*r_{j}. | @@ -614,20 +614,20 @@ subroutine cnaitr call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c orth1 = .false. c c %------------------------------% c | Compute the B-norm of r_{j}. | c %------------------------------% c - if (bmat .eq. 'G') then - cnorm = wcdotc (n, resid, 1, workd(ipj), 1) + if (bmat .eq. 'G') then + cnorm = ccdotc (n, resid, 1, workd(ipj), 1) rnorm = sqrt( slapy2(real(cnorm),aimag(cnorm)) ) else if (bmat .eq. 'I') then rnorm = scnrm2(n, resid, 1) end if -c +c c %-----------------------------------------------------------% c | STEP 5: Re-orthogonalization / Iterative refinement phase | c | Maximum NITER_ITREF tries. | @@ -650,20 +650,20 @@ subroutine cnaitr c iter = 0 nrorth = nrorth + 1 -c +c c %---------------------------------------------------% c | Enter the Iterative refinement phase. If further | c | refinement is necessary, loop back here. The loop | c | variable is ITER. Perform a step of Classical | c | Gram-Schmidt using all the Arnoldi vectors V_{j} | c %---------------------------------------------------% -c +c 80 continue c if (msglvl .gt. 2) then rtemp(1) = wnorm rtemp(2) = rnorm - call svout (logfil, 2, rtemp, ndigit, + call svout (logfil, 2, rtemp, ndigit, & '_naitr: re-orthogonalization; wnorm and rnorm are') call cvout (logfil, j, h(1,j), ndigit, & '_naitr: j-th column of H') @@ -674,7 +674,7 @@ subroutine cnaitr c | WORKD(IRJ:IRJ+J-1) = v(:,1:J)'*WORKD(IPJ:IPJ+N-1). | c %----------------------------------------------------% c - call cgemv ('C', n, j, one, v, ldv, workd(ipj), 1, + call cgemv ('C', n, j, one, v, ldv, workd(ipj), 1, & zero, workd(irj), 1) c c %---------------------------------------------% @@ -684,10 +684,10 @@ subroutine cnaitr c | + v(:,1:J)*WORKD(IRJ:IRJ+J-1)*e'_j. | c %---------------------------------------------% c - call cgemv ('N', n, j, -one, v, ldv, workd(irj), 1, + call cgemv ('N', n, j, -one, v, ldv, workd(irj), 1, & one, resid, 1) call caxpy (j, one, workd(irj), 1, h(1,j), 1) -c +c orth2 = .true. call arscnd (t2) if (bmat .eq. 'G') then @@ -696,16 +696,16 @@ subroutine cnaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-----------------------------------% c | Exit in order to compute B*r_{j}. | c | r_{j} is the corrected residual. | c %-----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call ccopy (n, resid, 1, workd(ipj), 1) - end if + end if 90 continue c c %---------------------------------------------------% @@ -715,21 +715,21 @@ subroutine cnaitr if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) - end if + end if c c %-----------------------------------------------------% c | Compute the B-norm of the corrected residual r_{j}. | c %-----------------------------------------------------% -c - if (bmat .eq. 'G') then - cnorm = wcdotc (n, resid, 1, workd(ipj), 1) +c + if (bmat .eq. 'G') then + cnorm = ccdotc (n, resid, 1, workd(ipj), 1) rnorm1 = sqrt( slapy2(real(cnorm),aimag(cnorm)) ) else if (bmat .eq. 'I') then rnorm1 = scnrm2(n, resid, 1) end if -c +c if (msglvl .gt. 0 .and. iter .gt. 0 ) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: Iterative refinement for Arnoldi residual') if (msglvl .gt. 2) then rtemp(1) = rnorm @@ -757,7 +757,7 @@ subroutine cnaitr c %---------------------------------------% c rnorm = rnorm1 -c +c else c c %-------------------------------------------% @@ -776,24 +776,24 @@ subroutine cnaitr c do 95 jj = 1, n resid(jj) = zero - 95 continue + 95 continue rnorm = rzero end if -c +c c %----------------------------------------------% c | Branch here directly if iterative refinement | c | wasn't necessary or after at most NITER_REF | c | steps of iterative refinement. | c %----------------------------------------------% -c +c 100 continue -c +c rstart = .false. orth2 = .false. -c +c call arscnd (t5) titref = titref + (t5 - t4) -c +c c %------------------------------------% c | STEP 6: Update j = j+1; Continue | c %------------------------------------% @@ -804,27 +804,27 @@ subroutine cnaitr tcaitr = tcaitr + (t1 - t0) ido = 99 do 110 i = max(1,k), k+np-1 -c +c c %--------------------------------------------% c | Check for splitting and deflation. | c | Use a standard test as in the QR algorithm | c | REFERENCE: LAPACK subroutine clahqr | c %--------------------------------------------% -c +c tst1 = slapy2(real(h(i,i)),aimag(h(i,i))) & + slapy2(real(h(i+1,i+1)), aimag(h(i+1,i+1))) if( tst1.eq.real(zero) ) & tst1 = clanhs( '1', k+np, h, ldh, workd(n+1) ) - if( slapy2(real(h(i+1,i)),aimag(h(i+1,i))) .le. - & max( ulp*tst1, smlnum ) ) + if( slapy2(real(h(i+1,i)),aimag(h(i+1,i))) .le. + & max( ulp*tst1, smlnum ) ) & h(i+1,i) = zero 110 continue -c +c if (msglvl .gt. 2) then - call cmout (logfil, k+np, k+np, h, ldh, ndigit, + call cmout (logfil, k+np, k+np, h, ldh, ndigit, & '_naitr: Final upper Hessenberg matrix H of order K+NP') end if -c +c go to 9000 end if c @@ -833,7 +833,7 @@ subroutine cnaitr c %--------------------------------------------------------% c go to 1000 -c +c c %---------------------------------------------------------------% c | | c | E N D O F M A I N I T E R A T I O N L O O P | diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnapps.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnapps.f index 2d633c1dfdf6..c3a55623f828 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnapps.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnapps.f @@ -19,7 +19,7 @@ c c\Usage: c call cnapps -c ( N, KEV, NP, SHIFT, V, LDV, H, LDH, RESID, Q, LDQ, +c ( N, KEV, NP, SHIFT, V, LDV, H, LDH, RESID, Q, LDQ, c WORKL, WORKD ) c c\Arguments @@ -28,7 +28,7 @@ c c KEV Integer. (INPUT/OUTPUT) c KEV+NP is the size of the input matrix H. -c KEV is the size of the updated matrix HNEW. +c KEV is the size of the updated matrix HNEW. c c NP Integer. (INPUT) c Number of implicit shifts to be applied. @@ -46,7 +46,7 @@ c program. c c H Complex (KEV+NP) by (KEV+NP) array. (INPUT/OUTPUT) -c On INPUT, H contains the current KEV+NP by KEV+NP upper +c On INPUT, H contains the current KEV+NP by KEV+NP upper c Hessenberg matrix of the Arnoldi factorization. c On OUTPUT, H contains the updated KEV by KEV upper Hessenberg c matrix in the KEV leading submatrix. @@ -57,7 +57,7 @@ c c RESID Complex array of length N. (INPUT/OUTPUT) c On INPUT, RESID contains the the residual vector r_{k+p}. -c On OUTPUT, RESID is the update residual vector rnew_{k} +c On OUTPUT, RESID is the update residual vector rnew_{k} c in the first KEV locations. c c Q Complex KEV+NP by KEV+NP work array. (WORKSPACE) @@ -112,9 +112,9 @@ c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c\SCCS Information: @(#) c FILE: napps.F SID: 2.3 DATE OF SID: 3/28/97 RELEASE: 2 @@ -132,7 +132,7 @@ c----------------------------------------------------------------------- c subroutine cnapps - & ( n, kev, np, shift, v, ldv, h, ldh, resid, q, ldq, + & ( n, kev, np, shift, v, ldv, h, ldh, resid, q, ldq, & workl, workd ) c c %----------------------------------------------------% @@ -153,7 +153,7 @@ subroutine cnapps c %-----------------% c Complex - & h(ldh,kev+np), resid(n), shift(np), + & h(ldh,kev+np), resid(n), shift(np), & v(ldv,kev+np), q(ldq,kev+np), workd(2*n), workl(kev+np) c c %------------% @@ -175,22 +175,22 @@ subroutine cnapps logical first Complex & cdum, f, g, h11, h21, r, s, sigma, t - Real + Real & c, ovfl, smlnum, ulp, unfl, tst1 - save first, ovfl, smlnum, ulp, unfl + save first, ovfl, smlnum, ulp, unfl c c %----------------------% c | External Subroutines | c %----------------------% c - external caxpy, ccopy, cgemv, cscal, clacpy, clartg, + external caxpy, ccopy, cgemv, cscal, clacpy, clartg, & cvout, claset, slabad, cmout, arscnd, ivout c c %--------------------% c | External Functions | c %--------------------% c - Real + Real & clanhs, slamch, slapy2 external clanhs, slamch, slapy2 c @@ -204,12 +204,12 @@ subroutine cnapps c | Statement Functions | c %---------------------% c - Real + Real & cabs1 cabs1( cdum ) = abs( real( cdum ) ) + abs( aimag( cdum ) ) c c %----------------% -c | Data statments | +c | Data statements | c %----------------% c data first / .true. / @@ -242,9 +242,9 @@ subroutine cnapps c call arscnd (t0) msglvl = mcapps -c - kplusp = kev + np -c +c + kplusp = kev + np +c c %--------------------------------------------% c | Initialize Q to the identity to accumulate | c | the rotations and reflections | @@ -268,9 +268,9 @@ subroutine cnapps sigma = shift(jj) c if (msglvl .gt. 2 ) then - call ivout (logfil, 1, jj, ndigit, + call ivout (logfil, 1, [jj], ndigit, & '_napps: shift number.') - call cvout (logfil, 1, sigma, ndigit, + call cvout (logfil, 1, [sigma], ndigit, & '_napps: Value of the shift ') end if c @@ -288,14 +288,14 @@ subroutine cnapps tst1 = cabs1( h( i, i ) ) + cabs1( h( i+1, i+1 ) ) if( tst1.eq.rzero ) & tst1 = clanhs( '1', kplusp-jj+1, h, ldh, workl ) - if ( abs(real(h(i+1,i))) + if ( abs(real(h(i+1,i))) & .le. max(ulp*tst1, smlnum) ) then if (msglvl .gt. 0) then - call ivout (logfil, 1, i, ndigit, + call ivout (logfil, 1, [i], ndigit, & '_napps: matrix splitting at row/column no.') - call ivout (logfil, 1, jj, ndigit, + call ivout (logfil, 1, [jj], ndigit, & '_napps: matrix splitting with shift number.') - call cvout (logfil, 1, h(i+1,i), ndigit, + call cvout (logfil, 1, h(i+1,i), ndigit, & '_napps: off diagonal element.') end if iend = i @@ -307,9 +307,9 @@ subroutine cnapps 40 continue c if (msglvl .gt. 2) then - call ivout (logfil, 1, istart, ndigit, + call ivout (logfil, 1, [istart], ndigit, & '_napps: Start of current block ') - call ivout (logfil, 1, iend, ndigit, + call ivout (logfil, 1, [iend], ndigit, & '_napps: End of current block ') end if c @@ -325,7 +325,7 @@ subroutine cnapps h21 = h(istart+1,istart) f = h11 - sigma g = h21 -c +c do 80 i = istart, iend-1 c c %------------------------------------------------------% @@ -345,7 +345,7 @@ subroutine cnapps do 50 j = i, kplusp t = c*h(i,j) + s*h(i+1,j) h(i+1,j) = -conjg(s)*h(i,j) + c*h(i+1,j) - h(i,j) = t + h(i,j) = t 50 continue c c %---------------------------------------------% @@ -355,7 +355,7 @@ subroutine cnapps do 60 j = 1, min(i+2,iend) t = c*h(j,i) + conjg(s)*h(j,i+1) h(j,i+1) = -s*h(j,i) + c*h(j,i+1) - h(j,i) = t + h(j,i) = t 60 continue c c %-----------------------------------------------------% @@ -365,7 +365,7 @@ subroutine cnapps do 70 j = 1, min(i+jj, kplusp) t = c*q(j,i) + conjg(s)*q(j,i+1) q(j,i+1) = - s*q(j,i) + c*q(j,i+1) - q(j,i) = t + q(j,i) = t 70 continue c c %---------------------------% @@ -381,7 +381,7 @@ subroutine cnapps c %-------------------------------% c | Finished applying the shift. | c %-------------------------------% -c +c 100 continue c c %---------------------------------------------------------% @@ -428,7 +428,7 @@ subroutine cnapps tst1 = cabs1( h( i, i ) ) + cabs1( h( i+1, i+1 ) ) if( tst1 .eq. rzero ) & tst1 = clanhs( '1', kev, h, ldh, workl ) - if( real( h( i+1,i ) ) .le. max( ulp*tst1, smlnum ) ) + if( real( h( i+1,i ) ) .le. max( ulp*tst1, smlnum ) ) & h(i+1,i) = zero 130 continue c @@ -441,9 +441,9 @@ subroutine cnapps c %-------------------------------------------------% c if ( real( h(kev+1,kev) ) .gt. rzero ) - & call cgemv ('N', n, kplusp, one, v, ldv, q(1,kev+1), 1, zero, + & call cgemv ('N', n, kplusp, one, v, ldv, q(1,kev+1), 1, zero, & workd(n+1), 1) -c +c c %----------------------------------------------------------% c | Compute column 1 to kev of (V*Q) in backward order | c | taking advantage of the upper Hessenberg structure of Q. | @@ -460,14 +460,14 @@ subroutine cnapps c %-------------------------------------------------% c call clacpy ('A', n, kev, v(1,kplusp-kev+1), ldv, v, ldv) -c +c c %--------------------------------------------------------------% c | Copy the (kev+1)-st column of (V*Q) in the appropriate place | c %--------------------------------------------------------------% c if ( real( h(kev+1,kev) ) .gt. rzero ) & call ccopy (n, workd(n+1), 1, v(1,kev+1), 1) -c +c c %-------------------------------------% c | Update the residual vector: | c | r <- sigmak*r + betak*v(:,kev+1) | @@ -485,7 +485,7 @@ subroutine cnapps & '_napps: sigmak = (e_{kev+p}^T*Q)*e_{kev}') call cvout (logfil, 1, h(kev+1,kev), ndigit, & '_napps: betak = e_{kev+1}^T*H*e_{kev}') - call ivout (logfil, 1, kev, ndigit, + call ivout (logfil, 1, [kev], ndigit, & '_napps: Order of the final Hessenberg matrix ') if (msglvl .gt. 2) then call cmout (logfil, kev, kev, h, ldh, ndigit, @@ -497,7 +497,7 @@ subroutine cnapps 9000 continue call arscnd (t1) tcapps = tcapps + (t1 - t0) -c +c return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaup2.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaup2.f index d5fad0fc8d81..e3615424728b 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaup2.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaup2.f @@ -2,13 +2,13 @@ c c\Name: cnaup2 c -c\Description: +c\Description: c Intermediate level interface called by cnaupd. c c\Usage: c call cnaup2 c ( IDO, BMAT, N, WHICH, NEV, NP, TOL, RESID, MODE, IUPD, -c ISHIFT, MXITER, V, LDV, H, LDH, RITZ, BOUNDS, +c ISHIFT, MXITER, V, LDV, H, LDH, RITZ, BOUNDS, c Q, LDQ, WORKL, IPNTR, WORKD, RWORK, INFO ) c c\Arguments @@ -26,7 +26,7 @@ c The logic for adjusting is contained within the current c subroutine. c If ISHIFT=0, NP is the number of shifts the user needs -c to provide via reverse comunication. 0 < NP < NCV-NEV. +c to provide via reverse communication. 0 < NP < NCV-NEV. c NP may be less than NCV-NEV since a leading block of the current c upper Hessenberg matrix has split off and contains "unwanted" c Ritz values. @@ -38,27 +38,27 @@ c IUPD .NE. 0: use implicit update. c c V Complex N by (NEV+NP) array. (INPUT/OUTPUT) -c The Arnoldi basis vectors are returned in the first NEV +c The Arnoldi basis vectors are returned in the first NEV c columns of V. c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c H Complex (NEV+NP) by (NEV+NP) array. (OUTPUT) c H is used to store the generated upper Hessenberg matrix c c LDH Integer. (INPUT) -c Leading dimension of H exactly as declared in the calling +c Leading dimension of H exactly as declared in the calling c program. c c RITZ Complex array of length NEV+NP. (OUTPUT) c RITZ(1:NEV) contains the computed Ritz values of OP. c c BOUNDS Complex array of length NEV+NP. (OUTPUT) -c BOUNDS(1:NEV) contain the error bounds corresponding to +c BOUNDS(1:NEV) contain the error bounds corresponding to c the computed Ritz values. -c +c c Q Complex (NEV+NP) by (NEV+NP) array. (WORKSPACE) c Private (replicated) work array used to accumulate the c rotation in the shift application step. @@ -67,7 +67,7 @@ c Leading dimension of Q exactly as declared in the calling c program. c -c WORKL Complex work array of length at least +c WORKL Complex work array of length at least c (NEV+NP)**2 + 3*(NEV+NP). (WORKSPACE) c Private (replicated) array on each PE or array allocated on c the front end. It is used in shifts calculation, shifts @@ -75,15 +75,15 @@ c c c IPNTR Integer array of length 3. (OUTPUT) -c Pointer to mark the starting locations in the WORKD for +c Pointer to mark the starting locations in the WORKD for c vectors used by the Arnoldi iteration. c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X. c IPNTR(2): pointer to the current result vector Y. -c IPNTR(3): pointer to the vector B * X when used in the +c IPNTR(3): pointer to the vector B * X when used in the c shift-and-invert mode. X is the current operand. c ------------------------------------------------------------- -c +c c WORKD Complex work array of length 3*N. (WORKSPACE) c Distributed array to be used in the basic Arnoldi iteration c for reverse communication. The user should not use WORKD @@ -101,7 +101,7 @@ c Error flag on output. c = 0: Normal return. c = 1: Maximum number of iterations taken. -c All possible eigenvalues of OP has been found. +c All possible eigenvalues of OP has been found. c NP returns the number of converged Ritz values. c = 2: No shifts could be applied. c = -8: Error return from LAPACK eigenvalue calculation; @@ -117,21 +117,21 @@ c\BeginLib c c\Local variables: -c xxxxxx Complex +c xxxxxx Complex c c\References: c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c c\Routines called: -c cgetv0 ARPACK initial vector generation routine. +c cgetv0 ARPACK initial vector generation routine. c cnaitr ARPACK Arnoldi factorization routine. c cnapps ARPACK application of implicit shifts routine. -c cneigh ARPACK compute Ritz values and error bounds routine. +c cneigh ARPACK compute Ritz values and error bounds routine. c cngets ARPACK reorder Ritz values and error bounds routine. c csortc ARPACK sorting routine. c ivout ARPACK utility routine that prints integers. @@ -142,7 +142,7 @@ c slamch LAPACK routine that determines machine constants. c slapy2 LAPACK routine to compute sqrt(x**2+y**2) carefully. c ccopy Level 1 BLAS that copies one vector to another . -c wcdotc Level 1 BLAS that computes the scalar product of two vectors. +c cdotc Level 1 BLAS that computes the scalar product of two vectors. c cswap Level 1 BLAS that swaps two vectors. c scnrm2 Level 1 BLAS that computes the norm of a vector. c @@ -151,10 +151,10 @@ c Richard Lehoucq CRPC / Rice Universitya c Chao Yang Houston, Texas c Dept. of Computational & -c Applied Mathematics -c Rice University -c Houston, Texas -c +c Applied Mathematics +c Rice University +c Houston, Texas +c c\SCCS Information: @(#) c FILE: naup2.F SID: 2.6 DATE OF SID: 06/01/00 RELEASE: 2 c @@ -166,8 +166,8 @@ c----------------------------------------------------------------------- c subroutine cnaup2 - & ( ido, bmat, n, which, nev, np, tol, resid, mode, iupd, - & ishift, mxiter, v, ldv, h, ldh, ritz, bounds, + & ( ido, bmat, n, which, nev, np, tol, resid, mode, iupd, + & ishift, mxiter, v, ldv, h, ldh, ritz, bounds, & q, ldq, workl, ipntr, workd, rwork, info ) c c %----------------------------------------------------% @@ -184,7 +184,7 @@ subroutine cnaup2 character bmat*1, which*2 integer ido, info, ishift, iupd, mode, ldh, ldq, ldv, mxiter, & n, nev, np - Real + Real & tol c c %-----------------% @@ -192,20 +192,20 @@ subroutine cnaup2 c %-----------------% c integer ipntr(13) - Complex - & bounds(nev+np), h(ldh,nev+np), q(ldq,nev+np), - & resid(n), ritz(nev+np), v(ldv,nev+np), + Complex + & bounds(nev+np), h(ldh,nev+np), q(ldq,nev+np), + & resid(n), ritz(nev+np), v(ldv,nev+np), & workd(3*n), workl( (nev+np)*(nev+np+3) ) - Real + Real & rwork(nev+np) c c %------------% c | Parameters | c %------------% c - Complex + Complex & one, zero - Real + Real & rzero parameter (one = (1.0E+0, 0.0E+0) , zero = (0.0E+0, 0.0E+0) , & rzero = 0.0E+0 ) @@ -215,16 +215,16 @@ subroutine cnaup2 c %---------------% c logical cnorm , getv0, initv , update, ushift - integer ierr , iter , kplusp, msglvl, nconv, + integer ierr , iter , kplusp, msglvl, nconv, & nevbef, nev0 , np0 , nptemp, i , - & j - Complex + & j + Complex & cmpnorm - Real + Real & rnorm , eps23, rtemp character wprime*2 c - save cnorm, getv0, initv , update, ushift, + save cnorm, getv0, initv , update, ushift, & rnorm, iter , kplusp, msglvl, nconv , & nevbef, nev0 , np0 , eps23 c @@ -246,11 +246,11 @@ subroutine cnaup2 c | External functions | c %--------------------% c - Complex - & wcdotc - Real + Complex + & ccdotc + Real & scnrm2, slamch, slapy2 - external wcdotc, scnrm2, slamch, slapy2 + external ccdotc, scnrm2, slamch, slapy2 c c %---------------------% c | Intrinsic Functions | @@ -263,11 +263,11 @@ subroutine cnaup2 c %-----------------------% c if (ido .eq. 0) then -c +c call arscnd (t0) -c +c msglvl = mcaup2 -c +c nev0 = nev np0 = np c @@ -283,7 +283,7 @@ subroutine cnaup2 kplusp = nev + np nconv = 0 iter = 0 -c +c c %---------------------------------% c | Get machine dependent constant. | c %---------------------------------% @@ -313,7 +313,7 @@ subroutine cnaup2 initv = .false. end if end if -c +c c %---------------------------------------------% c | Get a possibly random starting vector and | c | force it into the range of the operator OP. | @@ -330,7 +330,7 @@ subroutine cnaup2 if (rnorm .eq. rzero) then c c %-----------------------------------------% -c | The initial vector is zero. Error exit. | +c | The initial vector is zero. Error exit. | c %-----------------------------------------% c info = -9 @@ -339,7 +339,7 @@ subroutine cnaup2 getv0 = .false. ido = 0 end if -c +c c %-----------------------------------% c | Back from reverse communication : | c | continue with update step | @@ -359,12 +359,12 @@ subroutine cnaup2 c %-------------------------------------% c if (cnorm) go to 100 -c +c c %----------------------------------------------------------% c | Compute the first NEV steps of the Arnoldi factorization | c %----------------------------------------------------------% c - call cnaitr (ido, bmat, n, 0, nev, mode, resid, rnorm, v, ldv, + call cnaitr (ido, bmat, n, 0, nev, mode, resid, rnorm, v, ldv, & h, ldh, ipntr, workd, info) c if (ido .ne. 99) go to 9000 @@ -375,7 +375,7 @@ subroutine cnaup2 info = -9999 go to 1200 end if -c +c c %--------------------------------------------------------------% c | | c | M A I N ARNOLDI I T E R A T I O N L O O P | @@ -383,16 +383,16 @@ subroutine cnaup2 c | factorization in place. | c | | c %--------------------------------------------------------------% -c +c 1000 continue c iter = iter + 1 c if (msglvl .gt. 0) then - call ivout (logfil, 1, iter, ndigit, + call ivout (logfil, 1, [iter], ndigit, & '_naup2: **** Start of major iteration number ****') end if -c +c c %-----------------------------------------------------------% c | Compute NP additional steps of the Arnoldi factorization. | c | Adjust NP since NEV might have been updated by last call | @@ -402,9 +402,9 @@ subroutine cnaup2 np = kplusp - nev c if (msglvl .gt. 1) then - call ivout (logfil, 1, nev, ndigit, + call ivout (logfil, 1, [nev], ndigit, & '_naup2: The length of the current Arnoldi factorization') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_naup2: Extend the Arnoldi factorization by') end if c @@ -430,10 +430,10 @@ subroutine cnaup2 update = .false. c if (msglvl .gt. 1) then - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_naup2: Corresponding B-norm of the residual') end if -c +c c %--------------------------------------------------------% c | Compute the eigenvalues and corresponding error bounds | c | of the current upper Hessenberg matrix. | @@ -452,7 +452,7 @@ subroutine cnaup2 c | to be used in the convergence test. | c | The wanted part of the spectrum and corresponding | c | error bounds are in the last NEV loc. of RITZ, | -c | and BOUNDS respectively. | +c | and BOUNDS respectively. | c %---------------------------------------------------% c nev = nev0 @@ -475,7 +475,7 @@ subroutine cnaup2 c %---------------------------------------------------% c call cngets (ishift, which, nev, np, ritz, bounds) -c +c c %------------------------------------------------------------% c | Convergence test: currently we use the following criteria. | c | The relative accuracy of a Ritz value is considered | @@ -489,22 +489,22 @@ subroutine cnaup2 c do 25 i = 1, nev rtemp = max( eps23, slapy2( real (ritz(np+i)), - & aimag(ritz(np+i)) ) ) - if ( slapy2(real (bounds(np+i)),aimag(bounds(np+i))) + & aimag(ritz(np+i)) ) ) + if ( slapy2(real (bounds(np+i)),aimag(bounds(np+i))) & .le. tol*rtemp ) then nconv = nconv + 1 end if 25 continue -c +c if (msglvl .gt. 2) then kp(1) = nev kp(2) = np kp(3) = nconv - call ivout (logfil, 3, kp, ndigit, + call ivout (logfil, 3, kp, ndigit, & '_naup2: NEV, NP, NCONV are') call cvout (logfil, kplusp, ritz, ndigit, & '_naup2: The eigenvalues of H') - call cvout (logfil, kplusp, bounds, ndigit, + call cvout (logfil, kplusp, bounds, ndigit, & '_naup2: Ritz estimates of the current NCV Ritz values') end if c @@ -525,8 +525,8 @@ subroutine cnaup2 nev = nev + 1 end if 30 continue -c - if ( (nconv .ge. nev0) .or. +c + if ( (nconv .ge. nev0) .or. & (iter .gt. mxiter) .or. & (np .eq. 0) ) then c @@ -537,7 +537,7 @@ subroutine cnaup2 & ndigit, & '_naup2: Ritz estimates computed by _neigh:') end if -c +c c %------------------------------------------------% c | Prepare to exit. Put the converged Ritz values | c | and corresponding bounds in RITZ(1:NCONV) and | @@ -573,7 +573,7 @@ subroutine cnaup2 c | by 1 / max(eps23, magnitude of the Ritz value). | c %--------------------------------------------------% c - do 35 j = 1, nev0 + do 35 j = 1, nev0 rtemp = max( eps23, slapy2( real (ritz(j)), & aimag(ritz(j)) ) ) bounds(j) = bounds(j)/rtemp @@ -616,13 +616,13 @@ subroutine cnaup2 end if c c %------------------------------------% -c | Max iterations have been exceeded. | +c | Max iterations have been exceeded. | c %------------------------------------% c if (iter .gt. mxiter .and. nconv .lt. nev0) info = 1 c c %---------------------% -c | No shifts to apply. | +c | No shifts to apply. | c %---------------------% c if (np .eq. 0 .and. nconv .lt. nev0) info = 2 @@ -631,7 +631,7 @@ subroutine cnaup2 go to 1100 c else if ( (nconv .lt. nev0) .and. (ishift .eq. 1) ) then -c +c c %-------------------------------------------------% c | Do not have all the requested eigenvalues yet. | c | To prevent possible stagnation, adjust the size | @@ -646,24 +646,24 @@ subroutine cnaup2 nev = 2 end if np = kplusp - nev -c +c c %---------------------------------------% c | If the size of NEV was just increased | c | resort the eigenvalues. | c %---------------------------------------% -c - if (nevbef .lt. nev) +c + if (nevbef .lt. nev) & call cngets (ishift, which, nev, np, ritz, bounds) c - end if -c + end if +c if (msglvl .gt. 0) then - call ivout (logfil, 1, nconv, ndigit, + call ivout (logfil, 1, [nconv], ndigit, & '_naup2: no. of "converged" Ritz values at this iter.') if (msglvl .gt. 1) then kp(1) = nev kp(2) = np - call ivout (logfil, 2, kp, ndigit, + call ivout (logfil, 2, kp, ndigit, & '_naup2: NEV and NP are') call cvout (logfil, nev, ritz(np+1), ndigit, & '_naup2: "wanted" Ritz values ') @@ -687,7 +687,7 @@ subroutine cnaup2 ushift = .false. c if ( ishift .ne. 1 ) then -c +c c %----------------------------------% c | Move the NP shifts from WORKL to | c | RITZ, to free up WORKL | @@ -697,12 +697,12 @@ subroutine cnaup2 call ccopy (np, workl, 1, ritz, 1) end if c - if (msglvl .gt. 2) then - call ivout (logfil, 1, np, ndigit, + if (msglvl .gt. 2) then + call ivout (logfil, 1, [np], ndigit, & '_naup2: The number of shifts to apply ') call cvout (logfil, np, ritz, ndigit, & '_naup2: values of the shifts') - if ( ishift .eq. 1 ) + if ( ishift .eq. 1 ) & call cvout (logfil, np, bounds, ndigit, & '_naup2: Ritz estimates of the shifts') end if @@ -714,7 +714,7 @@ subroutine cnaup2 c | The first 2*N locations of WORKD are used as workspace. | c %---------------------------------------------------------% c - call cnapps (n, nev, np, ritz, v, ldv, + call cnapps (n, nev, np, ritz, v, ldv, & h, ldh, resid, q, ldq, workl, workd) c c %---------------------------------------------% @@ -731,18 +731,18 @@ subroutine cnaup2 ipntr(1) = n + 1 ipntr(2) = 1 ido = 2 -c +c c %----------------------------------% c | Exit in order to compute B*RESID | c %----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call ccopy (n, resid, 1, workd, 1) end if -c +c 100 continue -c +c c %----------------------------------% c | Back from reverse communication; | c | WORKD(1:N) := B*RESID | @@ -752,9 +752,9 @@ subroutine cnaup2 call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c - if (bmat .eq. 'G') then - cmpnorm = wcdotc (n, resid, 1, workd, 1) +c + if (bmat .eq. 'G') then + cmpnorm = ccdotc (n, resid, 1, workd, 1) rnorm = sqrt(slapy2(real (cmpnorm),aimag(cmpnorm))) else if (bmat .eq. 'I') then rnorm = scnrm2(n, resid, 1) @@ -762,12 +762,12 @@ subroutine cnaup2 cnorm = .false. c if (msglvl .gt. 2) then - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_naup2: B-norm of residual for compressed factorization') call cmout (logfil, nev, nev, h, ldh, ndigit, & '_naup2: Compressed upper Hessenberg matrix H') end if -c +c go to 1000 c c %---------------------------------------------------------------% @@ -780,7 +780,7 @@ subroutine cnaup2 c mxiter = iter nev = nconv -c +c 1200 continue ido = 99 c @@ -790,7 +790,7 @@ subroutine cnaup2 c call arscnd (t1) tcaup2 = t1 - t0 -c +c 9000 continue c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaupd.f index 82756eb457ec..57be328bf65c 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cnaupd.f @@ -2,11 +2,11 @@ c c\Name: cnaupd c -c\Description: +c\Description: c Reverse communication interface for the Implicitly Restarted Arnoldi -c iteration. This is intended to be used to find a few eigenpairs of a -c complex linear operator OP with respect to a semi-inner product defined -c by a hermitian positive semi-definite real matrix B. B may be the identity +c iteration. This is intended to be used to find a few eigenpairs of a +c complex linear operator OP with respect to a semi-inner product defined +c by a hermitian positive semi-definite real matrix B. B may be the identity c matrix. NOTE: if both OP and B are real, then ssaupd or snaupd should c be used. c @@ -14,7 +14,7 @@ c The computed approximate eigenvalues are called Ritz values and c the corresponding approximate eigenvectors are called Ritz vectors. c -c cnaupd is usually called iteratively to solve one of the +c cnaupd is usually called iteratively to solve one of the c following problems: c c Mode 1: A*x = lambda*x. @@ -25,10 +25,10 @@ c ===> (If M can be factored see remark 3 below) c c Mode 3: A*x = lambda*M*x, M hermitian semi-definite -c ===> OP = inv[A - sigma*M]*M and B = M. -c ===> shift-and-invert mode +c ===> OP = inv[A - sigma*M]*M and B = M. +c ===> shift-and-invert mode c If OP*x = amu*x, then lambda = sigma + 1/amu. -c +c c c NOTE: The action of w <- inv[A - sigma*M]*v or w <- inv[M]*v c should be accomplished either by a direct method @@ -49,7 +49,7 @@ c c\Arguments c IDO Integer. (INPUT/OUTPUT) -c Reverse communication flag. IDO must be zero on the first +c Reverse communication flag. IDO must be zero on the first c call to cnaupd. IDO will be set internally to c indicate the type of operation to be performed. Control is c then given back to the calling routine which has the @@ -72,14 +72,14 @@ c IDO = 2: compute Y = M * X where c IPNTR(1) is the pointer into WORKD for X, c IPNTR(2) is the pointer into WORKD for Y. -c IDO = 3: compute and return the shifts in the first +c IDO = 3: compute and return the shifts in the first c NP locations of WORKL. c IDO = 99: done c ------------------------------------------------------------- -c After the initialization phase, when the routine is used in -c the "shift-and-invert" mode, the vector M * X is already +c After the initialization phase, when the routine is used in +c the "shift-and-invert" mode, the vector M * X is already c available and does not need to be recomputed in forming OP*X. -c +c c BMAT Character*1. (INPUT) c BMAT specifies the type of the matrix B that defines the c semi-inner product for the operator OP. @@ -101,14 +101,14 @@ c Number of eigenvalues of OP to be computed. 0 < NEV < N-1. c c TOL Real scalar. (INPUT) -c Stopping criteria: the relative accuracy of the Ritz value +c Stopping criteria: the relative accuracy of the Ritz value c is considered acceptable if BOUNDS(I) .LE. TOL*ABS(RITZ(I)) c where ABS(RITZ(I)) is the magnitude when RITZ(I) is complex. c DEFAULT = slamch('EPS') (machine precision as computed c by the LAPACK auxiliary subroutine slamch). c c RESID Complex array of length N. (INPUT/OUTPUT) -c On INPUT: +c On INPUT: c If INFO .EQ. 0, a random initial residual vector is used. c If INFO .NE. 0, RESID contains the initial residual vector, c possibly from a previous run. @@ -118,15 +118,15 @@ c NCV Integer. (INPUT) c Number of columns of the matrix V. NCV must satisfy the two c inequalities 1 <= NCV-NEV and NCV <= N. -c This will indicate how many Arnoldi vectors are generated -c at each iteration. After the startup phase in which NEV -c Arnoldi vectors are generated, the algorithm generates -c approximately NCV-NEV Arnoldi vectors at each subsequent update -c iteration. Most of the cost in generating each Arnoldi vector is +c This will indicate how many Arnoldi vectors are generated +c at each iteration. After the startup phase in which NEV +c Arnoldi vectors are generated, the algorithm generates +c approximately NCV-NEV Arnoldi vectors at each subsequent update +c iteration. Most of the cost in generating each Arnoldi vector is c in the matrix-vector operation OP*x. (See remark 4 below.) c c V Complex array N by NCV. (OUTPUT) -c Contains the final set of Arnoldi basis vectors. +c Contains the final set of Arnoldi basis vectors. c c LDV Integer. (INPUT) c Leading dimension of V exactly as declared in the calling program. @@ -137,23 +137,23 @@ c the components of the unwanted eigenvector. c ------------------------------------------------------------- c ISHIFT = 0: the shifts are to be provided by the user via -c reverse communication. The NCV eigenvalues of +c reverse communication. The NCV eigenvalues of c the Hessenberg matrix H are returned in the part c of WORKL array corresponding to RITZ. c ISHIFT = 1: exact shifts with respect to the current -c Hessenberg matrix H. This is equivalent to -c restarting the iteration from the beginning +c Hessenberg matrix H. This is equivalent to +c restarting the iteration from the beginning c after updating the starting vector with a linear -c combination of Ritz vectors associated with the +c combination of Ritz vectors associated with the c "wanted" eigenvalues. c ISHIFT = 2: other choice of internal shift to be defined. c ------------------------------------------------------------- c -c IPARAM(2) = No longer referenced +c IPARAM(2) = No longer referenced c c IPARAM(3) = MXITER -c On INPUT: maximum number of Arnoldi update iterations allowed. -c On OUTPUT: actual number of Arnoldi update iterations taken. +c On INPUT: maximum number of Arnoldi update iterations allowed. +c On OUTPUT: actual number of Arnoldi update iterations taken. c c IPARAM(4) = NB: blocksize to be used in the recurrence. c The code currently works only for NB = 1. @@ -163,11 +163,11 @@ c the convergence criterion. c c IPARAM(6) = IUPD -c No longer referenced. Implicit restarting is ALWAYS used. +c No longer referenced. Implicit restarting is ALWAYS used. c c IPARAM(7) = MODE c On INPUT determines what type of eigenproblem is being solved. -c Must be 1,2,3; See under \Description of cnaupd for the +c Must be 1,2,3; See under \Description of cnaupd for the c four modes available. c c IPARAM(8) = NP @@ -186,7 +186,7 @@ c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X in WORKD. c IPNTR(2): pointer to the current result vector Y in WORKD. -c IPNTR(3): pointer to the vector B * X in WORKD when used in +c IPNTR(3): pointer to the vector B * X in WORKD when used in c the shift-and-invert mode. c IPNTR(4): pointer to the next available location in WORKL c that is untouched by the program. @@ -199,7 +199,7 @@ c c Note: IPNTR(9:13) is only referenced by cneupd. See Remark 2 below. c -c IPNTR(9): pointer to the NCV RITZ values of the +c IPNTR(9): pointer to the NCV RITZ values of the c original system. c IPNTR(10): Not Used c IPNTR(11): pointer to the NCV corresponding error bounds. @@ -210,12 +210,12 @@ c cneupd if RVEC = .TRUE. See Remark 2 below. c c ------------------------------------------------------------- -c +c c WORKD Complex work array of length 3*N. (REVERSE COMMUNICATION) c Distributed array to be used in the basic Arnoldi iteration -c for reverse communication. The user should not use WORKD +c for reverse communication. The user should not use WORKD c as temporary workspace during the iteration !!!!!!!!!! -c See Data Distribution Note below. +c See Data Distribution Note below. c c WORKL Complex work array of length LWORKL. (OUTPUT/WORKSPACE) c Private (replicated) array on each PE or array allocated on @@ -236,18 +236,18 @@ c Error flag on output. c = 0: Normal exit. c = 1: Maximum number of iterations taken. -c All possible eigenvalues of OP has been found. IPARAM(5) +c All possible eigenvalues of OP has been found. IPARAM(5) c returns the number of wanted converged Ritz values. c = 2: No longer an informational error. Deprecated starting c with release 2 of ARPACK. -c = 3: No shifts could be applied during a cycle of the -c Implicitly restarted Arnoldi iteration. One possibility -c is to increase the size of NCV relative to NEV. +c = 3: No shifts could be applied during a cycle of the +c Implicitly restarted Arnoldi iteration. One possibility +c is to increase the size of NCV relative to NEV. c See remark 4 below. c = -1: N must be positive. c = -2: NEV must be positive. c = -3: NCV-NEV >= 2 and less than or equal to N. -c = -4: The maximum number of Arnoldi update iteration +c = -4: The maximum number of Arnoldi update iteration c must be greater than zero. c = -5: WHICH must be one of 'LM', 'SM', 'LR', 'SR', 'LI', 'SI' c = -6: BMAT must be one of 'I' or 'G'. @@ -268,16 +268,16 @@ c selection of WHICH should be made with this in mind when using c Mode = 3. When operating in Mode = 3 setting WHICH = 'LM' will c compute the NEV eigenvalues of the original problem that are -c closest to the shift SIGMA . After convergence, approximate eigenvalues +c closest to the shift SIGMA . After convergence, approximate eigenvalues c of the original problem may be obtained with the ARPACK subroutine cneupd. c -c 2. If a basis for the invariant subspace corresponding to the converged Ritz -c values is needed, the user must call cneupd immediately following +c 2. If a basis for the invariant subspace corresponding to the converged Ritz +c values is needed, the user must call cneupd immediately following c completion of cnaupd. This is new starting with release 2 of ARPACK. c c 3. If M can be factored into a Cholesky factorization M = LL` c then Mode = 2 should not be selected. Instead one should use -c Mode = 1 with OP = inv(L)*A*inv(L`). Appropriate triangular +c Mode = 1 with OP = inv(L)*A*inv(L`). Appropriate triangular c linear systems should be solved with L and L` rather c than computing inverses. After convergence, an approximate c eigenvector z of the original problem is recovered by solving @@ -287,11 +287,11 @@ c of NCV relative to NEV. The only formal requirement is that NCV > NEV + 1. c However, it is recommended that NCV .ge. 2*NEV. If many problems of c the same type are to be solved, one should experiment with increasing -c NCV while keeping NEV fixed for a given test problem. This will +c NCV while keeping NEV fixed for a given test problem. This will c usually decrease the required number of OP*x operations but it c also increases the work and storage required to maintain the orthogonal c basis vectors. The optimal "cross-over" with respect to CPU time -c is problem dependent and must be determined empirically. +c is problem dependent and must be determined empirically. c See Chapter 8 of Reference 2 for further information. c c 5. When IPARAM(1) = 0, and IDO = 3, the user needs to provide the @@ -305,7 +305,7 @@ c c----------------------------------------------------------------------- c -c\Data Distribution Note: +c\Data Distribution Note: c c Fortran-D syntax: c ================ @@ -324,10 +324,10 @@ c Complex resid(n), v(ldv,ncv), workd(n,3), workl(lworkl) c shared resid(block), v(block,:), workd(block,:) c replicated workl(lworkl) -c +c c CM2/CM5 syntax: c ============== -c +c c----------------------------------------------------------------------- c c include 'ex-nonsym.doc' @@ -337,13 +337,13 @@ c\BeginLib c c\Local variables: -c xxxxxx Complex +c xxxxxx Complex c c\References: c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c 3. B.N. Parlett & Y. Saad, "_Complex_ Shift and Invert Strategies for @@ -363,10 +363,10 @@ c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas -c +c Applied Mathematics +c Rice University +c Houston, Texas +c c\SCCS Information: @(#) c FILE: naupd.F SID: 2.8 DATE OF SID: 04/10/01 RELEASE: 2 c @@ -377,7 +377,7 @@ c----------------------------------------------------------------------- c subroutine cnaupd - & ( ido, bmat, n, which, nev, tol, resid, ncv, v, ldv, iparam, + & ( ido, bmat, n, which, nev, tol, resid, ncv, v, ldv, iparam, & ipntr, workd, workl, lworkl, rwork, info ) c c %----------------------------------------------------% @@ -393,7 +393,7 @@ subroutine cnaupd c character bmat*1, which*2 integer ido, info, ldv, lworkl, n, ncv, nev - Real + Real & tol c c %-----------------% @@ -401,16 +401,16 @@ subroutine cnaupd c %-----------------% c integer iparam(11), ipntr(14) - Complex + Complex & resid(n), v(ldv,ncv), workd(3*n), workl(lworkl) - Real + Real & rwork(ncv) c c %------------% c | Parameters | c %------------% c - Complex + Complex & one, zero parameter (one = (1.0E+0, 0.0E+0) , zero = (0.0E+0, 0.0E+0) ) c @@ -418,7 +418,7 @@ subroutine cnaupd c | Local Scalars | c %---------------% c - integer bounds, ierr, ih, iq, ishift, iupd, iw, + integer bounds, ierr, ih, iq, ishift, iupd, iw, & ldh, ldq, levec, mode, msglvl, mxiter, nb, & nev0, next, np, ritz, j save bounds, ih, iq, ishift, iupd, iw, @@ -435,16 +435,16 @@ subroutine cnaupd c | External Functions | c %--------------------% c - Real + Real & slamch external slamch c c %-----------------------% c | Executable Statements | c %-----------------------% -c +c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -496,7 +496,7 @@ subroutine cnaupd else if (mode .eq. 1 .and. bmat .eq. 'G') then ierr = -11 end if -c +c c %------------% c | Error Exit | c %------------% @@ -506,14 +506,14 @@ subroutine cnaupd ido = 99 go to 9000 end if -c +c c %------------------------% c | Set default parameters | c %------------------------% c if (nb .le. 0) nb = 1 if (tol .le. 0.0E+0 ) tol = slamch('EpsMach') - if (ishift .ne. 0 .and. + if (ishift .ne. 0 .and. & ishift .ne. 1 .and. & ishift .ne. 2) ishift = 1 c @@ -525,8 +525,8 @@ subroutine cnaupd c %----------------------------------------------% c np = ncv - nev - nev0 = nev -c + nev0 = nev +c c %-----------------------------% c | Zero out internal workspace | c %-----------------------------% @@ -534,7 +534,7 @@ subroutine cnaupd do 10 j = 1, 3*ncv**2 + 5*ncv workl(j) = zero 10 continue -c +c c %-------------------------------------------------------------% c | Pointer into WORKL for address of H, RITZ, BOUNDS, Q | c | etc... and the remaining workspace. | @@ -572,12 +572,12 @@ subroutine cnaupd c | Carry out the Implicitly restarted Arnoldi Iteration. | c %-------------------------------------------------------% c - call cnaup2 + call cnaup2 & ( ido, bmat, n, which, nev0, np, tol, resid, mode, iupd, - & ishift, mxiter, v, ldv, workl(ih), ldh, workl(ritz), - & workl(bounds), workl(iq), ldq, workl(iw), + & ishift, mxiter, v, ldv, workl(ih), ldh, workl(ritz), + & workl(bounds), workl(iq), ldq, workl(iw), & ipntr, workd, rwork, info ) -c +c c %--------------------------------------------------% c | ido .ne. 99 implies use of reverse communication | c | to compute operations involving OP. | @@ -585,7 +585,7 @@ subroutine cnaupd c if (ido .eq. 3) iparam(8) = np if (ido .ne. 99) go to 9000 -c +c iparam(3) = mxiter iparam(5) = np iparam(9) = nopx @@ -601,13 +601,13 @@ subroutine cnaupd if (info .eq. 2) info = 3 c if (msglvl .gt. 0) then - call ivout (logfil, 1, mxiter, ndigit, + call ivout (logfil, 1, [mxiter], ndigit, & '_naupd: Number of update iterations taken') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_naupd: Number of wanted "converged" Ritz values') - call cvout (logfil, np, workl(ritz), ndigit, + call cvout (logfil, np, workl(ritz), ndigit, & '_naupd: The final Ritz values') - call cvout (logfil, np, workl(bounds), ndigit, + call cvout (logfil, np, workl(bounds), ndigit, & '_naupd: Associated Ritz estimates') end if c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cneigh.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cneigh.f index 76aa4aca155d..2e2d4d7265fb 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cneigh.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cneigh.f @@ -12,7 +12,7 @@ c c\Arguments c RNORM Real scalar. (INPUT) -c Residual norm corresponding to the current upper Hessenberg +c Residual norm corresponding to the current upper Hessenberg c matrix H. c c N Integer. (INPUT) @@ -30,8 +30,8 @@ c c BOUNDS Complex array of length N. (OUTPUT) c On output, BOUNDS contains the Ritz estimates associated with -c the eigenvalues held in RITZ. This is equal to RNORM -c times the last components of the eigenvectors corresponding +c the eigenvalues held in RITZ. This is equal to RNORM +c times the last components of the eigenvectors corresponding c to the eigenvalues in RITZ. c c Q Complex N by N array. (WORKSPACE) @@ -48,7 +48,7 @@ c c RWORK Real work array of length N (WORKSPACE) c Private (replicated) array on each PE or array allocated on -c the front end. +c the front end. c c IERR Integer. (OUTPUT) c Error exit flag from clahqr or ctrevc. @@ -74,18 +74,18 @@ c claset LAPACK matrix initialization routine. c ctrevc LAPACK routine to compute the eigenvectors of a matrix c in upper triangular form -c ccopy Level 1 BLAS that copies one vector to another. +c ccopy Level 1 BLAS that copies one vector to another. c csscal Level 1 BLAS that scales a complex vector by a real number. c scnrm2 Level 1 BLAS that computes the norm of a vector. -c +c c c\Author c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c\SCCS Information: @(#) c FILE: neigh.F SID: 2.2 DATE OF SID: 4/20/96 RELEASE: 2 @@ -97,7 +97,7 @@ c c----------------------------------------------------------------------- c - subroutine cneigh (rnorm, n, h, ldh, ritz, bounds, + subroutine cneigh (rnorm, n, h, ldh, ritz, bounds, & q, ldq, workl, rwork, ierr) c c %----------------------------------------------------% @@ -112,37 +112,37 @@ subroutine cneigh (rnorm, n, h, ldh, ritz, bounds, c %------------------% c integer ierr, n, ldh, ldq - Real + Real & rnorm c c %-----------------% c | Array Arguments | c %-----------------% c - Complex + Complex & bounds(n), h(ldh,n), q(ldq,n), ritz(n), - & workl(n*(n+3)) - Real + & workl(n*(n+3)) + Real & rwork(n) -c +c c %------------% c | Parameters | c %------------% c - Complex + Complex & one, zero Real & rone parameter (one = (1.0E+0, 0.0E+0), zero = (0.0E+0, 0.0E+0), & rone = 1.0E+0) -c +c c %------------------------% c | Local Scalars & Arrays | c %------------------------% c logical select(1) integer j, msglvl - Complex + Complex & vl(1) Real & temp @@ -151,14 +151,14 @@ subroutine cneigh (rnorm, n, h, ldh, ritz, bounds, c | External Subroutines | c %----------------------% c - external clacpy, clahqr, ctrevc, ccopy, + external clacpy, clahqr, ctrevc, ccopy, & csscal, cmout, cvout, arscnd c c %--------------------% c | External Functions | c %--------------------% c - Real + Real & scnrm2 external scnrm2 c @@ -173,17 +173,17 @@ subroutine cneigh (rnorm, n, h, ldh, ritz, bounds, c call arscnd (t0) msglvl = mceigh -c +c if (msglvl .gt. 2) then - call cmout (logfil, n, n, h, ldh, ndigit, + call cmout (logfil, n, n, h, ldh, ndigit, & '_neigh: Entering upper Hessenberg matrix H ') end if -c +c c %----------------------------------------------------------% c | 1. Compute the eigenvalues, the last components of the | c | corresponding Schur vectors and the full Schur form T | c | of the current upper Hessenberg matrix H. | -c | clahqr returns the full Schur form of H | +c | clahqr returns the full Schur form of H | c | in WORKL(1:N**2), and the Schur vectors in q. | c %----------------------------------------------------------% c @@ -205,7 +205,7 @@ subroutine cneigh (rnorm, n, h, ldh, ritz, bounds, c | eigenvectors. | c %----------------------------------------------------------% c - call ctrevc ('Right', 'Back', select, n, workl, n, vl, n, q, + call ctrevc ('Right', 'Back', select, n, workl, n, vl, n, q, & ldq, n, n, workl(n*n+1), rwork, ierr) c if (ierr .ne. 0) go to 9000 diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cneupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cneupd.f index 593435e00958..29154ce37e4e 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cneupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cneupd.f @@ -1,48 +1,48 @@ c\BeginDoc -c -c\Name: cneupd -c -c\Description: -c This subroutine returns the converged approximations to eigenvalues -c of A*z = lambda*B*z and (optionally): -c -c (1) The corresponding approximate eigenvectors; -c -c (2) An orthonormal basis for the associated approximate -c invariant subspace; -c -c (3) Both. -c -c There is negligible additional cost to obtain eigenvectors. An orthonormal +c +c\Name: cneupd +c +c\Description: +c This subroutine returns the converged approximations to eigenvalues +c of A*z = lambda*B*z and (optionally): +c +c (1) The corresponding approximate eigenvectors; +c +c (2) An orthonormal basis for the associated approximate +c invariant subspace; +c +c (3) Both. +c +c There is negligible additional cost to obtain eigenvectors. An orthonormal c basis is always computed. There is an additional storage cost of n*nev -c if both are requested (in this case a separate array Z must be supplied). +c if both are requested (in this case a separate array Z must be supplied). c c The approximate eigenvalues and eigenvectors of A*z = lambda*B*z c are derived from approximate eigenvalues and eigenvectors of c of the linear operator OP prescribed by the MODE selection in the c call to CNAUPD. CNAUPD must be called before this routine is called. c These approximate eigenvalues and vectors are commonly called Ritz -c values and Ritz vectors respectively. They are referred to as such -c in the comments that follow. The computed orthonormal basis for the -c invariant subspace corresponding to these Ritz values is referred to as a -c Schur basis. -c +c values and Ritz vectors respectively. They are referred to as such +c in the comments that follow. The computed orthonormal basis for the +c invariant subspace corresponding to these Ritz values is referred to as a +c Schur basis. +c c The definition of OP as well as other terms and the relation of computed c Ritz values and vectors of OP with respect to the given problem -c A*z = lambda*B*z may be found in the header of CNAUPD. For a brief +c A*z = lambda*B*z may be found in the header of CNAUPD. For a brief c description, see definitions of IPARAM(7), MODE and WHICH in the c documentation of CNAUPD. c c\Usage: -c call cneupd -c ( RVEC, HOWMNY, SELECT, D, Z, LDZ, SIGMA, WORKEV, BMAT, -c N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, WORKD, +c call cneupd +c ( RVEC, HOWMNY, SELECT, D, Z, LDZ, SIGMA, WORKEV, BMAT, +c N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, WORKD, c WORKL, LWORKL, RWORK, INFO ) c c\Arguments: c RVEC LOGICAL (INPUT) c Specifies whether a basis for the invariant subspace corresponding -c to the converged Ritz value approximations for the eigenproblem +c to the converged Ritz value approximations for the eigenproblem c A*z = lambda*B*z is computed. c c RVEC = .FALSE. Compute Ritz values only. @@ -51,7 +51,7 @@ c See Remarks below. c c HOWMNY Character*1 (INPUT) -c Specifies the form of the basis for the invariant subspace +c Specifies the form of the basis for the invariant subspace c corresponding to the converged Ritz values that is to be computed. c c = 'A': Compute NEV Ritz vectors; @@ -62,34 +62,34 @@ c SELECT Logical array of dimension NCV. (INPUT) c If HOWMNY = 'S', SELECT specifies the Ritz vectors to be c computed. To select the Ritz vector corresponding to a -c Ritz value D(j), SELECT(j) must be set to .TRUE.. -c If HOWMNY = 'A' or 'P', SELECT need not be initialized +c Ritz value D(j), SELECT(j) must be set to .TRUE.. +c If HOWMNY = 'A' or 'P', SELECT need not be initialized c but it is used as internal workspace. c c D Complex array of dimension NEV+1. (OUTPUT) -c On exit, D contains the Ritz approximations +c On exit, D contains the Ritz approximations c to the eigenvalues lambda for A*z = lambda*B*z. c c Z Complex N by NEV array (OUTPUT) -c On exit, if RVEC = .TRUE. and HOWMNY = 'A', then the columns of -c Z represents approximate eigenvectors (Ritz vectors) corresponding +c On exit, if RVEC = .TRUE. and HOWMNY = 'A', then the columns of +c Z represents approximate eigenvectors (Ritz vectors) corresponding c to the NCONV=IPARAM(5) Ritz values for eigensystem c A*z = lambda*B*z. c c If RVEC = .FALSE. or HOWMNY = 'P', then Z is NOT REFERENCED. c -c NOTE: If if RVEC = .TRUE. and a Schur basis is not required, -c the array Z may be set equal to first NEV+1 columns of the Arnoldi -c basis array V computed by CNAUPD. In this case the Arnoldi basis +c NOTE: If if RVEC = .TRUE. and a Schur basis is not required, +c the array Z may be set equal to first NEV+1 columns of the Arnoldi +c basis array V computed by CNAUPD. In this case the Arnoldi basis c will be destroyed and overwritten with the eigenvector basis. c c LDZ Integer. (INPUT) c The leading dimension of the array Z. If Ritz vectors are -c desired, then LDZ .ge. max( 1, N ) is required. +c desired, then LDZ .ge. max( 1, N ) is required. c In any case, LDZ .ge. 1 is required. c c SIGMA Complex (INPUT) -c If IPARAM(7) = 3 then SIGMA represents the shift. +c If IPARAM(7) = 3 then SIGMA represents the shift. c Not referenced if IPARAM(7) = 1 or 2. c c WORKEV Complex work array of dimension 2*NCV. (WORKSPACE) @@ -97,12 +97,12 @@ c **** The remaining arguments MUST be the same as for the **** c **** call to CNAUPD that was just completed. **** c -c NOTE: The remaining arguments +c NOTE: The remaining arguments c -c BMAT, N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, -c WORKD, WORKL, LWORKL, RWORK, INFO +c BMAT, N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, +c WORKD, WORKL, LWORKL, RWORK, INFO c -c must be passed directly to CNEUPD following the last call +c must be passed directly to CNEUPD following the last call c to CNAUPD. These arguments MUST NOT BE MODIFIED between c the the last call to CNAUPD and the call to CNEUPD. c @@ -128,7 +128,7 @@ c WORKL(1:ncv*ncv+2*ncv) contains information obtained in c cnaupd. They are not changed by cneupd. c WORKL(ncv*ncv+2*ncv+1:3*ncv*ncv+4*ncv) holds the -c untransformed Ritz values, the untransformed error estimates of +c untransformed Ritz values, the untransformed error estimates of c the Ritz values, the upper triangular matrix for H, and the c associated matrix representation of the invariant subspace for H. c @@ -187,18 +187,18 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c 3. B. Nour-Omid, B. N. Parlett, T. Ericsson and P. S. Jensen, c "How to Implement the Spectral Transformation", Math Comp., -c Vol. 48, No. 178, April, 1987 pp. 664-673. +c Vol. 48, No. 178, April, 1987 pp. 664-673. c c\Routines called: c ivout ARPACK utility routine that prints integers. c cmout ARPACK utility routine that prints matrices c cvout ARPACK utility routine that prints vectors. -c cgeqr2 LAPACK routine that computes the QR factorization of +c cgeqr2 LAPACK routine that computes the QR factorization of c a matrix. c clacpy LAPACK matrix copy routine. c clahqr LAPACK routine that computes the Schur form of a @@ -207,7 +207,7 @@ c ctrevc LAPACK routine to compute the eigenvectors of a matrix c in upper triangular form. c ctrsen LAPACK routine that re-orders the Schur form. -c cunm2r LAPACK routine that applies an orthogonal matrix in +c cunm2r LAPACK routine that applies an orthogonal matrix in c factored form. c slamch LAPACK routine that determines machine constants. c ctrmm Level 3 BLAS matrix times an upper triangular matrix. @@ -219,7 +219,7 @@ c c\Remarks c -c 1. Currently only HOWMNY = 'A' and 'P' are implemented. +c 1. Currently only HOWMNY = 'A' and 'P' are implemented. c c 2. Schur vectors are an orthogonal representation for the basis of c Ritz vectors. Thus, their numerical properties are often superior. @@ -227,16 +227,16 @@ c A * V(:,1:IPARAM(5)) = V(:,1:IPARAM(5)) * T, and c transpose( V(:,1:IPARAM(5)) ) * V(:,1:IPARAM(5)) = I c are approximately satisfied. -c Here T is the leading submatrix of order IPARAM(5) of the -c upper triangular matrix stored workl(ipntr(12)). +c Here T is the leading submatrix of order IPARAM(5) of the +c upper triangular matrix stored workl(ipntr(12)). c c\Authors c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University -c Chao Yang Houston, Texas -c Dept. of Computational & -c Applied Mathematics -c Rice University +c Chao Yang Houston, Texas +c Dept. of Computational & +c Applied Mathematics +c Rice University c Houston, Texas c c\SCCS Information: @(#) @@ -266,9 +266,9 @@ subroutine cneupd(rvec , howmny, select, d , character bmat, howmny, which*2 logical rvec integer info, ldz, ldv, lworkl, n, ncv, nev - Complex + Complex & sigma - Real + Real & tol c c %-----------------% @@ -281,7 +281,7 @@ subroutine cneupd(rvec , howmny, select, d , & rwork(ncv) Complex & d(nev) , resid(n) , v(ldv,ncv), - & z(ldz, nev), + & z(ldz, nev), & workd(3*n) , workl(lworkl), workev(2*ncv) c c %------------% @@ -302,7 +302,7 @@ subroutine cneupd(rvec , howmny, select, d , & mode , msglvl, ritz , wr , k , irz , & ibd , outncv, iq , np , numcnv, jj , & ishift, nconv2 - Complex + Complex & rnorm, temp, vl(1) Real & conds, sep, rtemp, eps23 @@ -315,7 +315,7 @@ subroutine cneupd(rvec , howmny, select, d , external ccopy , cgeru, cgeqr2, clacpy, cmout, & cunm2r, ctrmm, cvout, ivout, & clahqr -c +c c %--------------------% c | External Functions | c %--------------------% @@ -325,13 +325,13 @@ subroutine cneupd(rvec , howmny, select, d , external scnrm2, slamch, slapy2 c Complex - & wcdotc - external wcdotc + & ccdotc + external ccdotc c c %-----------------------% c | Executable Statements | c %-----------------------% -c +c c %------------------------% c | Set default parameters | c %------------------------% @@ -382,12 +382,12 @@ subroutine cneupd(rvec , howmny, select, d , else if (howmny .eq. 'S' ) then ierr = -12 end if -c +c if (mode .eq. 1 .or. mode .eq. 2) then type = 'REGULR' else if (mode .eq. 3 ) then type = 'SHIFTI' - else + else ierr = -10 end if if (mode .eq. 1 .and. bmat .eq. 'G') ierr = -11 @@ -400,7 +400,7 @@ subroutine cneupd(rvec , howmny, select, d , info = ierr go to 9000 end if -c +c c %--------------------------------------------------------% c | Pointer into WORKL for address of H, RITZ, WORKEV, Q | c | etc... and the remaining workspace. | @@ -428,7 +428,7 @@ subroutine cneupd(rvec , howmny, select, d , c | subspace for H. | c | GRAND total of NCV * ( 3 * NCV + 4 ) locations. | c %-----------------------------------------------------------% -c +c ih = ipntr(5) ritz = ipntr(6) iq = ipntr(7) @@ -536,9 +536,9 @@ subroutine cneupd(rvec , howmny, select, d , c %-----------------------------------------------------------% c if (msglvl .gt. 2) then - call ivout(logfil, 1, numcnv, ndigit, + call ivout(logfil, 1, [numcnv], ndigit, & '_neupd: Number of specified eigenvalues') - call ivout(logfil, 1, nconv, ndigit, + call ivout(logfil, 1, [nconv], ndigit, & '_neupd: Number of "converged" eigenvalues') end if c @@ -555,10 +555,10 @@ subroutine cneupd(rvec , howmny, select, d , c %-------------------------------------------------------% c call ccopy(ldh*ncv, workl(ih), 1, workl(iuptri), 1) - call claset('All', ncv, ncv , + call claset('All', ncv, ncv , & zero , one, workl(invsub), & ldq) - call clahqr(.true., .true. , ncv , + call clahqr(.true., .true. , ncv , & 1 , ncv , workl(iuptri), & ldh , workl(iheig) , 1 , & ncv , workl(invsub), ldq , @@ -577,7 +577,7 @@ subroutine cneupd(rvec , howmny, select, d , call cvout (logfil, ncv, workl(ihbds), ndigit, & '_neupd: Last row of the Schur vector matrix') if (msglvl .gt. 3) then - call cmout (logfil , ncv, ncv , + call cmout (logfil , ncv, ncv , & workl(iuptri), ldh, ndigit, & '_neupd: The upper triangular matrix ') end if @@ -592,7 +592,7 @@ subroutine cneupd(rvec , howmny, select, d , call ctrsen('None' , 'V' , select , & ncv , workl(iuptri), ldh , & workl(invsub), ldq , workl(iheig), - & nconv2 , conds , sep , + & nconv2 , conds , sep , & workev , ncv , ierr) c if (nconv2 .lt. nconv) then @@ -625,7 +625,7 @@ subroutine cneupd(rvec , howmny, select, d , c call ccopy(ncv , workl(invsub+ncv-1), ldq, & workl(ihbds), 1) -c +c c %--------------------------------------------% c | Place the computed eigenvalues of H into D | c | if a spectral transformation was not used. | @@ -651,7 +651,7 @@ subroutine cneupd(rvec , howmny, select, d , c | * Postmultiply Z by R. | c | The N by NCONV matrix Z is now a matrix representation | c | of the approximate invariant subspace associated with | -c | the Ritz values in workl(iheig). The first NCONV | +c | the Ritz values in workl(iheig). The first NCONV | c | columns of V are now approximate Schur vectors | c | associated with the upper triangular matrix of order | c | NCONV in workl(iuptri). | @@ -674,7 +674,7 @@ subroutine cneupd(rvec , howmny, select, d , c | matrix consisting of plus or minus ones. | c %---------------------------------------------------% c - if ( real( workl(invsub+(j-1)*ldq+j-1) ) .lt. + if ( real( workl(invsub+(j-1)*ldq+j-1) ) .lt. & real(zero) ) then call cscal(nconv, -one, workl(iuptri+j-1), ldq) call cscal(nconv, -one, workl(iuptri+(j-1)*ldq), 1) @@ -730,8 +730,8 @@ subroutine cneupd(rvec , howmny, select, d , c | upper triangular, thus the length of the | c | inner product can be set to j. | c %------------------------------------------% -c - workev(j) = wcdotc(j, workl(ihbds), 1, +c + workev(j) = ccdotc(j, workl(ihbds), 1, & workl(invsub+(j-1)*ldq), 1) 40 continue c @@ -750,7 +750,7 @@ subroutine cneupd(rvec , howmny, select, d , c %---------------------------------------% c | Copy Ritz estimates into workl(ihbds) | c %---------------------------------------% -c +c call ccopy(nconv, workev, 1, workl(ihbds), 1) c c %----------------------------------------------% @@ -762,7 +762,7 @@ subroutine cneupd(rvec , howmny, select, d , & 'Non-unit', n , nconv , & one , workl(invsub), ldq , & z , ldz) - end if + end if c else c @@ -785,25 +785,25 @@ subroutine cneupd(rvec , howmny, select, d , c if (type .eq. 'REGULR') then c - if (rvec) + if (rvec) & call cscal(ncv, rnorm, workl(ihbds), 1) -c +c else -c +c c %---------------------------------------% c | A spectral transformation was used. | c | * Determine the Ritz estimates of the | c | Ritz values in the original system. | c %---------------------------------------% c - if (rvec) + if (rvec) & call cscal(ncv, rnorm, workl(ihbds), 1) -c +c do 50 k=1, ncv temp = workl(iheig+k-1) workl(ihbds+k-1) = workl(ihbds+k-1) / temp / temp 50 continue -c +c end if c c %-----------------------------------------------------------% @@ -813,7 +813,7 @@ subroutine cneupd(rvec , howmny, select, d , c | NOTES: | c | *The Ritz vectors are not affected by the transformation. | c %-----------------------------------------------------------% -c +c if (type .eq. 'SHIFTI') then do 60 k=1, nconv d(k) = one / workl(iheig+k-1) + sigma @@ -868,7 +868,7 @@ subroutine cneupd(rvec , howmny, select, d , 9000 continue c return -c +c c %---------------% c | End of cneupd| c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cngets.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cngets.f index 3c3a0085ec4e..20626a2d5065 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cngets.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cngets.f @@ -2,9 +2,9 @@ c c\Name: cngets c -c\Description: +c\Description: c Given the eigenvalues of the upper Hessenberg matrix H, -c computes the NP shifts AMU that are zeros of the polynomial of +c computes the NP shifts AMU that are zeros of the polynomial of c degree NP which filters out components of the unwanted eigenvectors c corresponding to the AMU's based on some given criteria. c @@ -40,8 +40,8 @@ c On INPUT, RITZ contains the the eigenvalues of H. c On OUTPUT, RITZ are sorted so that the unwanted c eigenvalues are in the first NP locations and the wanted -c portion is in the last KEV locations. When exact shifts are -c selected, the unwanted part corresponds to the shifts to +c portion is in the last KEV locations. When exact shifts are +c selected, the unwanted part corresponds to the shifts to c be applied. Also, if ISHIFT .eq. 1, the unwanted eigenvalues c are further sorted so that the ones with largest Ritz values c are first. @@ -49,7 +49,7 @@ c BOUNDS Complex array of length KEV+NP. (INPUT/OUTPUT) c Error bounds corresponding to the ordering in RITZ. c -c +c c c\EndDoc c @@ -70,9 +70,9 @@ c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c\SCCS Information: @(#) c FILE: ngets.F SID: 2.2 DATE OF SID: 4/20/96 RELEASE: 2 @@ -136,14 +136,14 @@ subroutine cngets ( ishift, which, kev, np, ritz, bounds) c | Initialize timing statistics | c | & message level for debugging | c %-------------------------------% -c +c call arscnd (t0) msglvl = mcgets -c +c call csortc (which, .true., kev+np, ritz, bounds) -c +c if ( ishift .eq. 1 ) then -c +c c %-------------------------------------------------------% c | Sort the unwanted Ritz values used as shifts so that | c | the ones with largest Ritz estimates are first | @@ -152,27 +152,27 @@ subroutine cngets ( ishift, which, kev, np, ritz, bounds) c | are applied in subroutine cnapps. | c | Be careful and use 'SM' since we want to sort BOUNDS! | c %-------------------------------------------------------% -c +c call csortc ( 'SM', .true., np, bounds, ritz ) c end if -c +c call arscnd (t1) tcgets = tcgets + (t1 - t0) c if (msglvl .gt. 0) then - call ivout (logfil, 1, kev, ndigit, '_ngets: KEV is') - call ivout (logfil, 1, np, ndigit, '_ngets: NP is') + call ivout (logfil, 1, [kev], ndigit, '_ngets: KEV is') + call ivout (logfil, 1, [np], ndigit, '_ngets: NP is') call cvout (logfil, kev+np, ritz, ndigit, & '_ngets: Eigenvalues of current H matrix ') - call cvout (logfil, kev+np, bounds, ndigit, + call cvout (logfil, kev+np, bounds, ndigit, & '_ngets: Ritz estimates of the current KEV+NP Ritz values') end if -c +c return -c +c c %---------------% c | End of cngets | c %---------------% -c +c end diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/csortc.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/csortc.f index 017c487f5318..a02bd3ffad66 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/csortc.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/csortc.f @@ -3,9 +3,9 @@ c\Name: csortc c c\Description: -c Sorts the Complex array in X into the order +c Sorts the Complex array in X into the order c specified by WHICH and optionally applies the permutation to the -c Real array Y. +c Real array Y. c c\Usage: c call csortc @@ -15,7 +15,7 @@ c WHICH Character*2. (Input) c 'LM' -> sort X into increasing order of magnitude. c 'SM' -> sort X into decreasing order of magnitude. -c 'LR' -> sort X with real(X) in increasing algebraic order +c 'LR' -> sort X with real(X) in increasing algebraic order c 'SR' -> sort X with real(X) in decreasing algebraic order c 'LI' -> sort X with imag(X) in increasing algebraic order c 'SI' -> sort X with imag(X) in decreasing algebraic order @@ -45,9 +45,9 @@ c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c Adapted from the sort routine in LANSO. c @@ -72,7 +72,7 @@ subroutine csortc (which, apply, n, x, y) c | Array Arguments | c %-----------------% c - Complex + Complex & x(0:n-1), y(0:n-1) c c %---------------% @@ -80,9 +80,9 @@ subroutine csortc (which, apply, n, x, y) c %---------------% c integer i, igap, j - Complex + Complex & temp - Real + Real & temp1, temp2 c c %--------------------% @@ -103,7 +103,7 @@ subroutine csortc (which, apply, n, x, y) c %-----------------------% c igap = n / 2 -c +c if (which .eq. 'LM') then c c %--------------------------------------------% @@ -163,7 +163,7 @@ subroutine csortc (which, apply, n, x, y) temp = x(j) x(j) = x(j+igap) x(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -177,7 +177,7 @@ subroutine csortc (which, apply, n, x, y) 60 continue igap = igap / 2 go to 40 -c +c else if (which .eq. 'LR') then c c %------------------------------------------------% @@ -197,7 +197,7 @@ subroutine csortc (which, apply, n, x, y) temp = x(j) x(j) = x(j+igap) x(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -211,7 +211,7 @@ subroutine csortc (which, apply, n, x, y) 90 continue igap = igap / 2 go to 70 -c +c else if (which .eq. 'SR') then c c %------------------------------------------------% @@ -230,7 +230,7 @@ subroutine csortc (which, apply, n, x, y) temp = x(j) x(j) = x(j+igap) x(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -244,7 +244,7 @@ subroutine csortc (which, apply, n, x, y) 120 continue igap = igap / 2 go to 100 -c +c else if (which .eq. 'LI') then c c %--------------------------------------------% @@ -277,7 +277,7 @@ subroutine csortc (which, apply, n, x, y) 150 continue igap = igap / 2 go to 130 -c +c else if (which .eq. 'SI') then c c %---------------------------------------------% @@ -296,7 +296,7 @@ subroutine csortc (which, apply, n, x, y) temp = x(j) x(j) = x(j+igap) x(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -311,7 +311,7 @@ subroutine csortc (which, apply, n, x, y) igap = igap / 2 go to 160 end if -c +c 9000 continue return c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cstatn.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cstatn.f index bfb740549c5b..02f75e0b26bf 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cstatn.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/cstatn.f @@ -14,7 +14,7 @@ subroutine cstatn c %--------------------------------% c include 'stat.h' - + c %-----------------------% c | Executable Statements | c %-----------------------% @@ -24,7 +24,7 @@ subroutine cstatn nrorth = 0 nitref = 0 nrstrt = 0 - + tcaupd = 0.0E+0 tcaup2 = 0.0E+0 tcaitr = 0.0E+0 @@ -35,13 +35,13 @@ subroutine cstatn titref = 0.0E+0 tgetv0 = 0.0E+0 trvec = 0.0E+0 - + c %----------------------------------------------------% c | User time including reverse communication overhead | c %----------------------------------------------------% tmvopx = 0.0E+0 tmvbx = 0.0E+0 - + return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dgetv0.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dgetv0.f index 7c0869e481b2..1d6dc01bdb50 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dgetv0.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dgetv0.f @@ -3,13 +3,13 @@ c c\Name: dgetv0 c -c\Description: +c\Description: c Generate a random initial residual vector for the Arnoldi process. -c Force the residual vector to be in the range of the operator OP. +c Force the residual vector to be in the range of the operator OP. c c\Usage: c call dgetv0 -c ( IDO, BMAT, ITRY, INITV, N, J, V, LDV, RESID, RNORM, +c ( IDO, BMAT, ITRY, INITV, N, J, V, LDV, RESID, RNORM, c IPNTR, WORKD, IERR ) c c\Arguments @@ -36,7 +36,7 @@ c B = 'G' -> generalized eigenvalue problem A*x = lambda*B*x c c ITRY Integer. (INPUT) -c ITRY counts the number of times that dgetv0 is called. +c ITRY counts the number of times that dgetv0 is called. c It should be set to 1 on the initial call to dgetv0. c c INITV Logical variable. (INPUT) @@ -55,11 +55,11 @@ c if this is a "restart". c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c RESID Double precision array of length N. (INPUT/OUTPUT) -c Initial residual vector to be generated. If RESID is +c Initial residual vector to be generated. If RESID is c provided, force RESID into the range of the operator OP. c c RNORM Double precision scalar. (OUTPUT) @@ -88,7 +88,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c @@ -98,7 +98,7 @@ c dlarnv LAPACK routine for generating a random vector. c dgemv Level 2 BLAS routine for matrix vector multiplication. c dcopy Level 1 BLAS that copies one vector to another. -c ddot Level 1 BLAS that computes the scalar product of two vectors. +c ddot Level 1 BLAS that computes the scalar product of two vectors. c dnrm2 Level 1 BLAS that computes the norm of a vector. c c\Author @@ -106,20 +106,20 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: getv0.F SID: 2.7 DATE OF SID: 04/07/99 RELEASE: 2 c c\EndLib c c----------------------------------------------------------------------- c - subroutine dgetv0 - & ( ido, bmat, itry, initv, n, j, v, ldv, resid, rnorm, + subroutine dgetv0 + & ( ido, bmat, itry, initv, n, j, v, ldv, resid, rnorm, & ipntr, workd, ierr ) -c +c c %----------------------------------------------------% c | Include files for debugging and timing information | c %----------------------------------------------------% @@ -208,7 +208,7 @@ subroutine dgetv0 end if c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -216,7 +216,7 @@ subroutine dgetv0 c call arscnd (t0) msglvl = mgetv0 -c +c ierr = 0 iter = 0 first = .FALSE. @@ -235,23 +235,25 @@ subroutine dgetv0 idist = 2 call dlarnv (idist, iseed, n, resid) end if -c +c c %----------------------------------------------------------% c | Force the starting vector into the range of OP to handle | c | the generalized problem when B is possibly (singular). | c %----------------------------------------------------------% c call arscnd (t2) - if (bmat .eq. 'G') then + if (itry .eq. 1) then nopx = nopx + 1 ipntr(1) = 1 ipntr(2) = n + 1 call dcopy (n, resid, 1, workd, 1) ido = -1 go to 9000 + else if (itry .gt. 1 .and. bmat .eq. 'G') then + call dcopy (n, resid, 1, workd(n + 1), 1) end if end if -c +c c %-----------------------------------------% c | Back from computing OP*(initial-vector) | c %-----------------------------------------% @@ -259,16 +261,16 @@ subroutine dgetv0 if (first) go to 20 c c %-----------------------------------------------% -c | Back from computing B*(orthogonalized-vector) | +c | Back from computing OP*(orthogonalized-vector) | c %-----------------------------------------------% c if (orth) go to 40 -c +c if (bmat .eq. 'G') then call arscnd (t3) tmvopx = tmvopx + (t3 - t2) end if -c +c c %------------------------------------------------------% c | Starting vector is now in the range of OP; r = OP*r; | c | Compute B-norm of starting vector. | @@ -276,9 +278,9 @@ subroutine dgetv0 c call arscnd (t2) first = .TRUE. + if (itry .eq. 1) call dcopy (n, workd(n + 1), 1, resid, 1) if (bmat .eq. 'G') then nbx = nbx + 1 - call dcopy (n, workd(n+1), 1, resid, 1) ipntr(1) = n + 1 ipntr(2) = 1 ido = 2 @@ -286,14 +288,14 @@ subroutine dgetv0 else if (bmat .eq. 'I') then call dcopy (n, resid, 1, workd, 1) end if -c +c 20 continue c if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c first = .FALSE. if (bmat .eq. 'G') then rnorm0 = ddot (n, resid, 1, workd, 1) @@ -308,7 +310,7 @@ subroutine dgetv0 c %---------------------------------------------% c if (j .eq. 1) go to 50 -c +c c %---------------------------------------------------------------- c | Otherwise need to B-orthogonalize the starting vector against | c | the current Arnoldi basis using Gram-Schmidt with iter. ref. | @@ -324,11 +326,11 @@ subroutine dgetv0 orth = .TRUE. 30 continue c - call dgemv ('T', n, j-1, one, v, ldv, workd, 1, + call dgemv ('T', n, j-1, one, v, ldv, workd, 1, & zero, workd(n+1), 1) - call dgemv ('N', n, j-1, -one, v, ldv, workd(n+1), 1, + call dgemv ('N', n, j-1, -one, v, ldv, workd(n+1), 1, & one, resid, 1) -c +c c %----------------------------------------------------------% c | Compute the B-norm of the orthogonalized starting vector | c %----------------------------------------------------------% @@ -344,14 +346,14 @@ subroutine dgetv0 else if (bmat .eq. 'I') then call dcopy (n, resid, 1, workd, 1) end if -c +c 40 continue c if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c if (bmat .eq. 'G') then rnorm = ddot (n, resid, 1, workd, 1) rnorm = sqrt(abs(rnorm)) @@ -364,14 +366,14 @@ subroutine dgetv0 c %--------------------------------------% c if (msglvl .gt. 2) then - call dvout (logfil, 1, rnorm0, ndigit, + call dvout (logfil, 1, [rnorm0], ndigit, & '_getv0: re-orthonalization ; rnorm0 is') - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_getv0: re-orthonalization ; rnorm is') end if c if (rnorm .gt. 0.717*rnorm0) go to 50 -c +c iter = iter + 1 if (iter .le. 5) then c @@ -393,11 +395,11 @@ subroutine dgetv0 rnorm = zero ierr = -1 end if -c +c 50 continue c if (msglvl .gt. 0) then - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_getv0: B-norm of initial / restarted starting vector') end if if (msglvl .gt. 3) then @@ -405,10 +407,10 @@ subroutine dgetv0 & '_getv0: initial / restarted starting vector') end if ido = 99 -c +c call arscnd (t1) tgetv0 = tgetv0 + (t1 - t0) -c +c 9000 continue return c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaitr.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaitr.f index 1d8bf304938f..c02cd3909287 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaitr.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaitr.f @@ -3,8 +3,8 @@ c c\Name: dnaitr c -c\Description: -c Reverse communication interface for applying NP additional steps to +c\Description: +c Reverse communication interface for applying NP additional steps to c a K step nonsymmetric Arnoldi factorization. c c Input: OP*V_{k} - V_{k}*H = r_{k}*e_{k}^T @@ -20,7 +20,7 @@ c c\Usage: c call dnaitr -c ( IDO, BMAT, N, K, NP, NB, RESID, RNORM, V, LDV, H, LDH, +c ( IDO, BMAT, N, K, NP, NB, RESID, RNORM, V, LDV, H, LDH, c IPNTR, WORKD, INFO ) c c\Arguments @@ -62,8 +62,8 @@ c Number of additional Arnoldi steps to take. c c NB Integer. (INPUT) -c Blocksize to be used in the recurrence. -c Only work for NB = 1 right now. The goal is to have a +c Blocksize to be used in the recurrence. +c Only work for NB = 1 right now. The goal is to have a c program that implement both the block and non-block method. c c RESID Double precision array of length N. (INPUT/OUTPUT) @@ -75,37 +75,37 @@ c B-norm of the updated residual r_{k+p} on output. c c V Double precision N by K+NP array. (INPUT/OUTPUT) -c On INPUT: V contains the Arnoldi vectors in the first K +c On INPUT: V contains the Arnoldi vectors in the first K c columns. c On OUTPUT: V contains the new NP Arnoldi vectors in the next c NP columns. The first K columns are unchanged. c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c H Double precision (K+NP) by (K+NP) array. (INPUT/OUTPUT) c H is used to store the generated upper Hessenberg matrix. c c LDH Integer. (INPUT) -c Leading dimension of H exactly as declared in the calling +c Leading dimension of H exactly as declared in the calling c program. c c IPNTR Integer array of length 3. (OUTPUT) -c Pointer to mark the starting locations in the WORK for +c Pointer to mark the starting locations in the WORK for c vectors used by the Arnoldi iteration. c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X. c IPNTR(2): pointer to the current result vector Y. -c IPNTR(3): pointer to the vector B * X when used in the +c IPNTR(3): pointer to the vector B * X when used in the c shift-and-invert mode. X is the current operand. c ------------------------------------------------------------- -c +c c WORKD Double precision work array of length 3*N. (REVERSE COMMUNICATION) c Distributed array to be used in the basic Arnoldi iteration -c for reverse communication. The calling program should not +c for reverse communication. The calling program should not c use WORKD as temporary workspace during the iteration !!!!!! -c On input, WORKD(1:N) = B*RESID and is used to save some +c On input, WORKD(1:N) = B*RESID and is used to save some c computation at the first step. c c INFO Integer. (OUTPUT) @@ -125,7 +125,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c @@ -143,7 +143,7 @@ c daxpy Level 1 BLAS that computes a vector triad. c dscal Level 1 BLAS that scales a vector. c dcopy Level 1 BLAS that copies one vector to another . -c ddot Level 1 BLAS that computes the scalar product of two vectors. +c ddot Level 1 BLAS that computes the scalar product of two vectors. c dnrm2 Level 1 BLAS that computes the norm of a vector. c c\Author @@ -151,22 +151,22 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas -c +c Rice University +c Houston, Texas +c c\Revision history: c xx/xx/92: Version ' 2.4' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: naitr.F SID: 2.4 DATE OF SID: 8/27/96 RELEASE: 2 c c\Remarks c The algorithm implemented is: -c +c c restart = .false. -c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; +c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; c r_{k} contains the initial residual vector even for k = 0; -c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already +c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already c computed by the calling program. c c betaj = rnorm ; p_{k+1} = B*r_{k} ; @@ -174,7 +174,7 @@ c 1) if ( betaj < tol ) stop or restart depending on j. c ( At present tol is zero ) c if ( restart ) generate a new starting vector. -c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; +c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; c p_{j} = p_{j}/betaj c 3) r_{j} = OP*v_{j} where OP is defined as in dnaupd c For shift-invert mode p_{j} = B*v_{j} is already available. @@ -189,7 +189,7 @@ c 5) Re-orthogonalization step: c s = V_{j}'*B*r_{j} c r_{j} = r_{j} - V_{j}*s; rnorm1 = || r_{j} || -c alphaj = alphaj + s_{j}; +c alphaj = alphaj + s_{j}; c 6) Iterative refinement step: c If (rnorm1 > 0.717*rnorm) then c rnorm = rnorm1 @@ -199,7 +199,7 @@ c If this is the first time in step 6), go to 5) c Else r_{j} lies in the span of V_{j} numerically. c Set r_{j} = 0 and rnorm = 0; go to 1) -c EndIf +c EndIf c End Do c c\EndLib @@ -207,7 +207,7 @@ c----------------------------------------------------------------------- c subroutine dnaitr - & (ido, bmat, n, k, np, nb, resid, rnorm, v, ldv, h, ldh, + & (ido, bmat, n, k, np, nb, resid, rnorm, v, ldv, h, ldh, & ipntr, workd, info) c c %----------------------------------------------------% @@ -250,14 +250,14 @@ subroutine dnaitr integer ierr, i, infol, ipj, irj, ivj, iter, itry, j, msglvl, & jj Double precision - & betaj, ovfl, temp1, rnorm1, smlnum, tst1, ulp, unfl, + & betaj, ovfl, temp1, rnorm1, smlnum, tst1, ulp, unfl, & wnorm save first, orth1, orth2, rstart, step3, step4, & ierr, ipj, irj, ivj, iter, itry, j, msglvl, ovfl, & betaj, rnorm1, smlnum, ulp, unfl, wnorm c c %-----------------------% -c | Local Array Arguments | +c | Local Array Arguments | c %-----------------------% c Double precision @@ -267,7 +267,7 @@ subroutine dnaitr c | External Subroutines | c %----------------------% c - external daxpy, dcopy, dscal, dgemv, dgetv0, dlabad, + external daxpy, dcopy, dscal, dgemv, dgetv0, dlabad, & dvout, dmout, ivout, arscnd c c %--------------------% @@ -313,7 +313,7 @@ subroutine dnaitr end if c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -321,7 +321,7 @@ subroutine dnaitr c call arscnd (t0) msglvl = mnaitr -c +c c %------------------------------% c | Initial call to this routine | c %------------------------------% @@ -337,7 +337,7 @@ subroutine dnaitr irj = ipj + n ivj = irj + n end if -c +c c %-------------------------------------------------% c | When in reverse communication mode one of: | c | STEP3, STEP4, ORTH1, ORTH2, RSTART | @@ -367,19 +367,19 @@ subroutine dnaitr c | | c | Note: B*r_{j-1} is already in WORKD(1:N)=WORKD(IPJ:IPJ+N-1) | c %--------------------------------------------------------------% - + 1000 continue c if (msglvl .gt. 1) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: generating Arnoldi vector number') - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_naitr: B-norm of the current residual is') end if -c +c c %---------------------------------------------------% c | STEP 1: Check if the B norm of j-th residual | -c | vector is zero. Equivalent to determing whether | +c | vector is zero. Equivalent to determining whether | c | an exact j-step Arnoldi factorization is present. | c %---------------------------------------------------% c @@ -393,16 +393,16 @@ subroutine dnaitr c %---------------------------------------------------% c if (msglvl .gt. 0) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: ****** RESTART AT STEP ******') end if -c +c c %---------------------------------------------% c | ITRY is the loop variable that controls the | c | maximum amount of times that a restart is | c | attempted. NRSTRT is used by stat.h | c %---------------------------------------------% -c +c betaj = zero nrstrt = nrstrt + 1 itry = 1 @@ -416,7 +416,7 @@ subroutine dnaitr c | RSTART = .true. flow returns here. | c %--------------------------------------% c - call dgetv0 (ido, bmat, itry, .false., n, j, v, ldv, + call dgetv0 (ido, bmat, itry, .false., n, j, v, ldv, & resid, rnorm, ipntr, workd, ierr) if (ido .ne. 99) go to 9000 if (ierr .lt. 0) then @@ -435,7 +435,7 @@ subroutine dnaitr ido = 99 go to 9000 end if -c +c 40 continue c c %---------------------------------------------------------% @@ -457,9 +457,9 @@ subroutine dnaitr c | use LAPACK routine SLASCL | c %-----------------------------------------% c - call dlascl ('General', i, i, rnorm, one, n, 1, + call dlascl ('General', i, i, rnorm, one, n, 1, & v(1,j), n, infol) - call dlascl ('General', i, i, rnorm, one, n, 1, + call dlascl ('General', i, i, rnorm, one, n, 1, & workd(ipj), n, infol) end if c @@ -476,14 +476,14 @@ subroutine dnaitr ipntr(2) = irj ipntr(3) = ipj ido = 1 -c +c c %-----------------------------------% c | Exit in order to compute OP*v_{j} | c %-----------------------------------% -c - go to 9000 +c + go to 9000 50 continue -c +c c %----------------------------------% c | Back from reverse communication; | c | WORKD(IRJ:IRJ+N-1) := OP*v_{j} | @@ -492,7 +492,7 @@ subroutine dnaitr c call arscnd (t3) tmvopx = tmvopx + (t3 - t2) - + step3 = .false. c c %------------------------------------------% @@ -500,7 +500,7 @@ subroutine dnaitr c %------------------------------------------% c call dcopy (n, workd(irj), 1, resid, 1) -c +c c %---------------------------------------% c | STEP 4: Finish extending the Arnoldi | c | factorization to length j. | @@ -513,17 +513,17 @@ subroutine dnaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-------------------------------------% c | Exit in order to compute B*OP*v_{j} | c %-------------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call dcopy (n, resid, 1, workd(ipj), 1) end if 60 continue -c +c c %----------------------------------% c | Back from reverse communication; | c | WORKD(IPJ:IPJ+N-1) := B*OP*v_{j} | @@ -534,7 +534,7 @@ subroutine dnaitr call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c step4 = .false. c c %-------------------------------------% @@ -542,7 +542,7 @@ subroutine dnaitr c | Compute the B-norm of OP*v_{j}. | c %-------------------------------------% c - if (bmat .eq. 'G') then + if (bmat .eq. 'G') then wnorm = ddot (n, resid, 1, workd(ipj), 1) wnorm = sqrt(abs(wnorm)) else if (bmat .eq. 'I') then @@ -562,13 +562,13 @@ subroutine dnaitr c | Compute the j Fourier coefficients w_{j} | c | WORKD(IPJ:IPJ+N-1) contains B*OP*v_{j}. | c %------------------------------------------% -c +c call dgemv ('T', n, j, one, v, ldv, workd(ipj), 1, & zero, h(1,j), 1) c c %--------------------------------------% c | Orthogonalize r_{j} against V_{j}. | -c | RESID contains OP*v_{j}. See STEP 3. | +c | RESID contains OP*v_{j}. See STEP 3. | c %--------------------------------------% c call dgemv ('N', n, j, -one, v, ldv, h(1,j), 1, @@ -577,7 +577,7 @@ subroutine dnaitr if (j .gt. 1) h(j,j-1) = betaj c call arscnd (t4) -c +c orth1 = .true. c call arscnd (t2) @@ -587,17 +587,17 @@ subroutine dnaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %----------------------------------% c | Exit in order to compute B*r_{j} | c %----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call dcopy (n, resid, 1, workd(ipj), 1) - end if + end if 70 continue -c +c c %---------------------------------------------------% c | Back from reverse communication if ORTH1 = .true. | c | WORKD(IPJ:IPJ+N-1) := B*r_{j}. | @@ -607,20 +607,20 @@ subroutine dnaitr call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c orth1 = .false. c c %------------------------------% c | Compute the B-norm of r_{j}. | c %------------------------------% c - if (bmat .eq. 'G') then + if (bmat .eq. 'G') then rnorm = ddot (n, resid, 1, workd(ipj), 1) rnorm = sqrt(abs(rnorm)) else if (bmat .eq. 'I') then rnorm = dnrm2(n, resid, 1) end if -c +c c %-----------------------------------------------------------% c | STEP 5: Re-orthogonalization / Iterative refinement phase | c | Maximum NITER_ITREF tries. | @@ -642,20 +642,20 @@ subroutine dnaitr if (rnorm .gt. 0.717*wnorm) go to 100 iter = 0 nrorth = nrorth + 1 -c +c c %---------------------------------------------------% c | Enter the Iterative refinement phase. If further | c | refinement is necessary, loop back here. The loop | c | variable is ITER. Perform a step of Classical | c | Gram-Schmidt using all the Arnoldi vectors V_{j} | c %---------------------------------------------------% -c +c 80 continue c if (msglvl .gt. 2) then xtemp(1) = wnorm xtemp(2) = rnorm - call dvout (logfil, 2, xtemp, ndigit, + call dvout (logfil, 2, xtemp, ndigit, & '_naitr: re-orthonalization; wnorm and rnorm are') call dvout (logfil, j, h(1,j), ndigit, & '_naitr: j-th column of H') @@ -666,7 +666,7 @@ subroutine dnaitr c | WORKD(IRJ:IRJ+J-1) = v(:,1:J)'*WORKD(IPJ:IPJ+N-1). | c %----------------------------------------------------% c - call dgemv ('T', n, j, one, v, ldv, workd(ipj), 1, + call dgemv ('T', n, j, one, v, ldv, workd(ipj), 1, & zero, workd(irj), 1) c c %---------------------------------------------% @@ -676,10 +676,10 @@ subroutine dnaitr c | + v(:,1:J)*WORKD(IRJ:IRJ+J-1)*e'_j. | c %---------------------------------------------% c - call dgemv ('N', n, j, -one, v, ldv, workd(irj), 1, + call dgemv ('N', n, j, -one, v, ldv, workd(irj), 1, & one, resid, 1) call daxpy (j, one, workd(irj), 1, h(1,j), 1) -c +c orth2 = .true. call arscnd (t2) if (bmat .eq. 'G') then @@ -688,16 +688,16 @@ subroutine dnaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-----------------------------------% c | Exit in order to compute B*r_{j}. | c | r_{j} is the corrected residual. | c %-----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call dcopy (n, resid, 1, workd(ipj), 1) - end if + end if 90 continue c c %---------------------------------------------------% @@ -712,8 +712,8 @@ subroutine dnaitr c %-----------------------------------------------------% c | Compute the B-norm of the corrected residual r_{j}. | c %-----------------------------------------------------% -c - if (bmat .eq. 'G') then +c + if (bmat .eq. 'G') then rnorm1 = ddot (n, resid, 1, workd(ipj), 1) rnorm1 = sqrt(abs(rnorm1)) else if (bmat .eq. 'I') then @@ -721,7 +721,7 @@ subroutine dnaitr end if c if (msglvl .gt. 0 .and. iter .gt. 0) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: Iterative refinement for Arnoldi residual') if (msglvl .gt. 2) then xtemp(1) = rnorm @@ -749,7 +749,7 @@ subroutine dnaitr c %---------------------------------------% c rnorm = rnorm1 -c +c else c c %-------------------------------------------% @@ -771,21 +771,21 @@ subroutine dnaitr 95 continue rnorm = zero end if -c +c c %----------------------------------------------% c | Branch here directly if iterative refinement | c | wasn't necessary or after at most NITER_REF | c | steps of iterative refinement. | c %----------------------------------------------% -c +c 100 continue -c +c rstart = .false. orth2 = .false. -c +c call arscnd (t5) titref = titref + (t5 - t4) -c +c c %------------------------------------% c | STEP 6: Update j = j+1; Continue | c %------------------------------------% @@ -796,25 +796,25 @@ subroutine dnaitr tnaitr = tnaitr + (t1 - t0) ido = 99 do 110 i = max(1,k), k+np-1 -c +c c %--------------------------------------------% c | Check for splitting and deflation. | c | Use a standard test as in the QR algorithm | c | REFERENCE: LAPACK subroutine dlahqr | c %--------------------------------------------% -c +c tst1 = abs( h( i, i ) ) + abs( h( i+1, i+1 ) ) if( tst1.eq.zero ) & tst1 = dlanhs( '1', k+np, h, ldh, workd(n+1) ) - if( abs( h( i+1,i ) ).le.max( ulp*tst1, smlnum ) ) + if( abs( h( i+1,i ) ).le.max( ulp*tst1, smlnum ) ) & h(i+1,i) = zero 110 continue -c +c if (msglvl .gt. 2) then - call dmout (logfil, k+np, k+np, h, ldh, ndigit, + call dmout (logfil, k+np, k+np, h, ldh, ndigit, & '_naitr: Final upper Hessenberg matrix H of order K+NP') end if -c +c go to 9000 end if c @@ -823,7 +823,7 @@ subroutine dnaitr c %--------------------------------------------------------% c go to 1000 -c +c c %---------------------------------------------------------------% c | | c | E N D O F M A I N I T E R A T I O N L O O P | diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnapps.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnapps.f index 3b2f76bee1c5..1cf3725696f0 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnapps.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnapps.f @@ -13,14 +13,14 @@ c A*(V_{k}*Q) - (V_{k}*Q)*(Q^T* H_{k}*Q) = r_{k+p}*e_{k+p}^T * Q c c where Q is an orthogonal matrix which is the product of rotations -c and reflections resulting from the NP bulge chage sweeps. +c and reflections resulting from the NP bulge change sweeps. c The updated Arnoldi factorization becomes: c c A*VNEW_{k} - VNEW_{k}*HNEW_{k} = rnew_{k}*e_{k}^T. c c\Usage: c call dnapps -c ( N, KEV, NP, SHIFTR, SHIFTI, V, LDV, H, LDH, RESID, Q, LDQ, +c ( N, KEV, NP, SHIFTR, SHIFTI, V, LDV, H, LDH, RESID, Q, LDQ, c WORKL, WORKD ) c c\Arguments @@ -29,8 +29,8 @@ c c KEV Integer. (INPUT/OUTPUT) c KEV+NP is the size of the input matrix H. -c KEV is the size of the updated matrix HNEW. KEV is only -c updated on ouput when fewer than NP shifts are applied in +c KEV is the size of the updated matrix HNEW. KEV is only +c updated on output when fewer than NP shifts are applied in c order to keep the conjugate pair together. c c NP Integer. (INPUT) @@ -38,7 +38,7 @@ c c SHIFTR, Double precision array of length NP. (INPUT) c SHIFTI Real and imaginary part of the shifts to be applied. -c Upon, entry to dnapps, the shifts must be sorted so that the +c Upon, entry to dnapps, the shifts must be sorted so that the c conjugate pairs are in consecutive locations. c c V Double precision N by (KEV+NP) array. (INPUT/OUTPUT) @@ -51,7 +51,7 @@ c program. c c H Double precision (KEV+NP) by (KEV+NP) array. (INPUT/OUTPUT) -c On INPUT, H contains the current KEV+NP by KEV+NP upper +c On INPUT, H contains the current KEV+NP by KEV+NP upper c Hessenber matrix of the Arnoldi factorization. c On OUTPUT, H contains the updated KEV by KEV upper Hessenberg c matrix in the KEV leading submatrix. @@ -62,7 +62,7 @@ c c RESID Double precision array of length N. (INPUT/OUTPUT) c On INPUT, RESID contains the the residual vector r_{k+p}. -c On OUTPUT, RESID is the update residual vector rnew_{k} +c On OUTPUT, RESID is the update residual vector rnew_{k} c in the first KEV locations. c c Q Double precision KEV+NP by KEV+NP work array. (WORKSPACE) @@ -102,7 +102,7 @@ c dvout ARPACK utility routine that prints vectors. c dlabad LAPACK routine that computes machine constants. c dlacpy LAPACK matrix copy routine. -c dlamch LAPACK routine that determines machine constants. +c dlamch LAPACK routine that determines machine constants. c dlanhs LAPACK routine that computes various norms of a matrix. c dlapy2 LAPACK routine to compute sqrt(x**2+y**2) carefully. c dlarf LAPACK routine that applies Householder reflection to @@ -120,13 +120,13 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.4' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: napps.F SID: 2.4 DATE OF SID: 3/28/97 RELEASE: 2 c c\Remarks @@ -141,7 +141,7 @@ c----------------------------------------------------------------------- c subroutine dnapps - & ( n, kev, np, shiftr, shifti, v, ldv, h, ldh, resid, q, ldq, + & ( n, kev, np, shiftr, shifti, v, ldv, h, ldh, resid, q, ldq, & workl, workd ) c c %----------------------------------------------------% @@ -162,7 +162,7 @@ subroutine dnapps c %-----------------% c Double precision - & h(ldh,kev+np), resid(n), shifti(np), shiftr(np), + & h(ldh,kev+np), resid(n), shifti(np), shiftr(np), & v(ldv,kev+np), q(ldq,kev+np), workd(2*n), workl(kev+np) c c %------------% @@ -180,9 +180,9 @@ subroutine dnapps integer i, iend, ir, istart, j, jj, kplusp, msglvl, nr logical cconj, first Double precision - & c, f, g, h11, h12, h21, h22, h32, ovfl, r, s, sigmai, + & c, f, g, h11, h12, h21, h22, h32, ovfl, r, s, sigmai, & sigmar, smlnum, ulp, unfl, u(3), t, tau, tst1 - save first, ovfl, smlnum, ulp, unfl + save first, ovfl, smlnum, ulp, unfl c c %----------------------% c | External Subroutines | @@ -206,7 +206,7 @@ subroutine dnapps intrinsic abs, max, min c c %----------------% -c | Data statments | +c | Data statements | c %----------------% c data first / .true. / @@ -239,8 +239,8 @@ subroutine dnapps c call arscnd (t0) msglvl = mnapps - kplusp = kev + np -c + kplusp = kev + np +c c %--------------------------------------------% c | Initialize Q to the identity to accumulate | c | the rotations and reflections | @@ -266,11 +266,11 @@ subroutine dnapps sigmai = shifti(jj) c if (msglvl .gt. 2 ) then - call ivout (logfil, 1, jj, ndigit, + call ivout (logfil, 1, [jj], ndigit, & '_napps: shift number.') - call dvout (logfil, 1, sigmar, ndigit, + call dvout (logfil, 1, [sigmar], ndigit, & '_napps: The real part of the shift ') - call dvout (logfil, 1, sigmai, ndigit, + call dvout (logfil, 1, [sigmai], ndigit, & '_napps: The imaginary part of the shift ') end if c @@ -335,11 +335,11 @@ subroutine dnapps & tst1 = dlanhs( '1', kplusp-jj+1, h, ldh, workl ) if( abs( h( i+1,i ) ).le.max( ulp*tst1, smlnum ) ) then if (msglvl .gt. 0) then - call ivout (logfil, 1, i, ndigit, + call ivout (logfil, 1, [i], ndigit, & '_napps: matrix splitting at row/column no.') - call ivout (logfil, 1, jj, ndigit, + call ivout (logfil, 1, [jj], ndigit, & '_napps: matrix splitting with shift number.') - call dvout (logfil, 1, h(i+1,i), ndigit, + call dvout (logfil, 1, h(i+1,i), ndigit, & '_napps: off diagonal element.') end if iend = i @@ -351,9 +351,9 @@ subroutine dnapps 40 continue c if (msglvl .gt. 2) then - call ivout (logfil, 1, istart, ndigit, + call ivout (logfil, 1, [istart], ndigit, & '_napps: Start of current block ') - call ivout (logfil, 1, iend, ndigit, + call ivout (logfil, 1, [iend], ndigit, & '_napps: End of current block ') end if c @@ -368,7 +368,7 @@ subroutine dnapps c | complex conjugate pair of shifts on a 2 by 2 matrix. | c %------------------------------------------------------% c - if ( istart + 1 .eq. iend .and. abs( sigmai ) .gt. zero ) + if ( istart + 1 .eq. iend .and. abs( sigmai ) .gt. zero ) & go to 100 c h11 = h(istart,istart) @@ -381,12 +381,12 @@ subroutine dnapps c f = h11 - sigmar g = h21 -c +c do 80 i = istart, iend-1 c -c %------------------------------------------------------% +c %-----------------------------------------------------% c | Construct the plane rotation G to zero out the bulge | -c %------------------------------------------------------% +c %-----------------------------------------------------% c call dlartg (f, g, c, s, r) if (i .gt. istart) then @@ -413,7 +413,7 @@ subroutine dnapps do 50 j = i, kplusp t = c*h(i,j) + s*h(i+1,j) h(i+1,j) = -s*h(i,j) + c*h(i+1,j) - h(i,j) = t + h(i,j) = t 50 continue c c %---------------------------------------------% @@ -423,17 +423,17 @@ subroutine dnapps do 60 j = 1, min(i+2,iend) t = c*h(j,i) + s*h(j,i+1) h(j,i+1) = -s*h(j,i) + c*h(j,i+1) - h(j,i) = t + h(j,i) = t 60 continue c c %----------------------------------------------------% c | Accumulate the rotation in the matrix Q; Q <- Q*G | c %----------------------------------------------------% c - do 70 j = 1, min( i+jj, kplusp ) + do 70 j = 1, min( i+jj, kplusp ) t = c*q(j,i) + s*q(j,i+1) q(j,i+1) = - s*q(j,i) + c*q(j,i+1) - q(j,i) = t + q(j,i) = t 70 continue c c %---------------------------% @@ -449,7 +449,7 @@ subroutine dnapps c %-----------------------------------% c | Finished applying the real shift. | c %-----------------------------------% -c +c else c c %----------------------------------------------------% @@ -465,9 +465,9 @@ subroutine dnapps c %---------------------------------------------------------% c s = 2.0*sigmar - t = dlapy2 ( sigmar, sigmai ) + t = dlapy2 ( sigmar, sigmai ) u(1) = ( h11 * (h11 - s) + t * t ) / h21 + h12 - u(2) = h11 + h22 - s + u(2) = h11 + h22 - s u(3) = h32 c do 90 i = istart, iend-1 @@ -507,7 +507,7 @@ subroutine dnapps c | Accumulate the reflector in the matrix Q; Q <- Q*G | c %-----------------------------------------------------% c - call dlarf ('Right', kplusp, nr, u, 1, tau, + call dlarf ('Right', kplusp, nr, u, 1, tau, & q(1,i), ldq, workl) c c %----------------------------% @@ -526,7 +526,7 @@ subroutine dnapps c | Finished applying a complex pair of shifts | c | to the current block | c %--------------------------------------------% -c +c end if c 100 continue @@ -568,7 +568,7 @@ subroutine dnapps tst1 = abs( h( i, i ) ) + abs( h( i+1, i+1 ) ) if( tst1.eq.zero ) & tst1 = dlanhs( '1', kev, h, ldh, workl ) - if( h( i+1,i ) .le. max( ulp*tst1, smlnum ) ) + if( h( i+1,i ) .le. max( ulp*tst1, smlnum ) ) & h(i+1,i) = zero 130 continue c @@ -581,9 +581,9 @@ subroutine dnapps c %-------------------------------------------------% c if (h(kev+1,kev) .gt. zero) - & call dgemv ('N', n, kplusp, one, v, ldv, q(1,kev+1), 1, zero, + & call dgemv ('N', n, kplusp, one, v, ldv, q(1,kev+1), 1, zero, & workd(n+1), 1) -c +c c %----------------------------------------------------------% c | Compute column 1 to kev of (V*Q) in backward order | c | taking advantage of the upper Hessenberg structure of Q. | @@ -599,15 +599,17 @@ subroutine dnapps c | Move v(:,kplusp-kev+1:kplusp) into v(:,1:kev). | c %-------------------------------------------------% c - call dlacpy ('A', n, kev, v(1,kplusp-kev+1), ldv, v, ldv) -c + do 150 i = 1, kev + call dcopy(n, v(1,kplusp-kev+i), 1, v(1,i), 1) + 150 continue +c c %--------------------------------------------------------------% c | Copy the (kev+1)-st column of (V*Q) in the appropriate place | c %--------------------------------------------------------------% c if (h(kev+1,kev) .gt. zero) & call dcopy (n, workd(n+1), 1, v(1,kev+1), 1) -c +c c %-------------------------------------% c | Update the residual vector: | c | r <- sigmak*r + betak*v(:,kev+1) | @@ -625,7 +627,7 @@ subroutine dnapps & '_napps: sigmak = (e_{kev+p}^T*Q)*e_{kev}') call dvout (logfil, 1, h(kev+1,kev), ndigit, & '_napps: betak = e_{kev+1}^T*H*e_{kev}') - call ivout (logfil, 1, kev, ndigit, + call ivout (logfil, 1, [kev], ndigit, & '_napps: Order of the final Hessenberg matrix ') if (msglvl .gt. 2) then call dmout (logfil, kev, kev, h, ldh, ndigit, @@ -633,11 +635,11 @@ subroutine dnapps end if c end if -c +c 9000 continue call arscnd (t1) tnapps = tnapps + (t1 - t0) -c +c return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaup2.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaup2.f index cb7431ef77a3..86375a64690f 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaup2.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaup2.f @@ -26,7 +26,7 @@ c The logic for adjusting is contained within the current c subroutine. c If ISHIFT=0, NP is the number of shifts the user needs -c to provide via reverse comunication. 0 < NP < NCV-NEV. +c to provide via reverse communication. 0 < NP < NCV-NEV. c NP may be less than NCV-NEV for two reasons. The first, is c to keep complex conjugate pairs of "wanted" Ritz values c together. The second, is that a leading block of the current @@ -388,7 +388,7 @@ subroutine dnaup2 iter = iter + 1 c if (msglvl .gt. 0) then - call ivout (logfil, 1, iter, ndigit, + call ivout (logfil, 1, [iter], ndigit, & '_naup2: **** Start of major iteration number ****') end if c @@ -401,9 +401,9 @@ subroutine dnaup2 np = kplusp - nev c if (msglvl .gt. 1) then - call ivout (logfil, 1, nev, ndigit, + call ivout (logfil, 1, [nev], ndigit, & '_naup2: The length of the current Arnoldi factorization') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_naup2: Extend the Arnoldi factorization by') end if c @@ -435,7 +435,7 @@ subroutine dnaup2 update = .false. c if (msglvl .gt. 1) then - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_naup2: Corresponding B-norm of the residual') end if c @@ -598,7 +598,7 @@ subroutine dnaup2 c c %----------------------------------------------------% c | Sort the Ritz values according to the scaled Ritz | -c | esitmates. This will push all the converged ones | +c | estimates. This will push all the converged ones | c | towards the front of ritzr, ritzi, bounds | c | (in the case when NCONV < NEV.) | c %----------------------------------------------------% @@ -689,7 +689,7 @@ subroutine dnaup2 end if c if (msglvl .gt. 0) then - call ivout (logfil, 1, nconv, ndigit, + call ivout (logfil, 1, [nconv], ndigit, & '_naup2: no. of "converged" Ritz values at this iter.') if (msglvl .gt. 1) then kp(1) = nev @@ -708,7 +708,7 @@ subroutine dnaup2 if (ishift .eq. 0) then c c %-------------------------------------------------------% -c | User specified shifts: reverse comminucation to | +c | User specified shifts: reverse communication to | c | compute the shifts. They are returned in the first | c | 2*NP locations of WORKL. | c %-------------------------------------------------------% @@ -741,7 +741,7 @@ subroutine dnaup2 end if c if (msglvl .gt. 2) then - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_naup2: The number of shifts to apply ') call dvout (logfil, np, ritzr, ndigit, & '_naup2: Real part of the shifts') @@ -807,7 +807,7 @@ subroutine dnaup2 cnorm = .false. c if (msglvl .gt. 2) then - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_naup2: B-norm of residual for compressed factorization') call dmout (logfil, nev, nev, h, ldh, ndigit, & '_naup2: Compressed upper Hessenberg matrix H') diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaupd.f index 114a6d794c49..0b4cbb0d8450 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnaupd.f @@ -273,7 +273,7 @@ c = -8: Error return from LAPACK eigenvalue calculation; c = -9: Starting vector is zero. c = -10: IPARAM(7) must be 1,2,3,4. -c = -11: IPARAM(7) = 1 and BMAT = 'G' are incompatable. +c = -11: IPARAM(7) = 1 and BMAT = 'G' are incompatible. c = -12: IPARAM(1) must be equal to 0 or 1. c = -9999: Could not build an Arnoldi factorization. c IPARAM(5) returns the size of the current Arnoldi @@ -298,7 +298,7 @@ c L`z = x where x is a Ritz vector of OP. c c 4. At present there is no a-priori analysis to guide the selection -c of NCV relative to NEV. The only formal requrement is that NCV > NEV + 2. +c of NCV relative to NEV. The only formal requirement is that NCV > NEV + 2. c However, it is recommended that NCV .ge. 2*NEV+1. If many problems of c the same type are to be solved, one should experiment with increasing c NCV while keeping NEV fixed for a given test problem. This will @@ -628,9 +628,9 @@ subroutine dnaupd if (info .eq. 2) info = 3 c if (msglvl .gt. 0) then - call ivout (logfil, 1, mxiter, ndigit, + call ivout (logfil, 1, [mxiter], ndigit, & '_naupd: Number of update iterations taken') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_naupd: Number of wanted "converged" Ritz values') call dvout (logfil, np, workl(ritzr), ndigit, & '_naupd: Real part of the final Ritz values') diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnconv.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnconv.f index 4ab737db5056..4d531f86515e 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnconv.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dnconv.f @@ -3,7 +3,7 @@ c c\Name: dnconv c -c\Description: +c\Description: c Convergence testing for the nonsymmetric Arnoldi eigenvalue routine. c c\Usage: @@ -44,16 +44,16 @@ c c\Author c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University +c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.1' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: nconv.F SID: 2.3 DATE OF SID: 4/20/96 RELEASE: 2 c c\Remarks @@ -106,7 +106,7 @@ subroutine dnconv (n, ritzr, ritzi, bounds, tol, nconv) c %-----------------------% c | Executable Statements | c %-----------------------% -c +c c %-------------------------------------------------------------% c | Convergence test: unlike in the symmetric code, I am not | c | using things like refined error bounds and gap condition | @@ -133,10 +133,10 @@ subroutine dnconv (n, ritzr, ritzi, bounds, tol, nconv) temp = max( eps23, dlapy2( ritzr(i), ritzi(i) ) ) if (bounds(i) .le. tol*temp) nconv = nconv + 1 20 continue -c +c call arscnd (t1) tnconv = tnconv + (t1 - t0) -c +c return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dneigh.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dneigh.f index 3970b2d554e3..3c49e32bf074 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dneigh.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dneigh.f @@ -13,7 +13,7 @@ c c\Arguments c RNORM Double precision scalar. (INPUT) -c Residual norm corresponding to the current upper Hessenberg +c Residual norm corresponding to the current upper Hessenberg c matrix H. c c N Integer. (INPUT) @@ -27,13 +27,13 @@ c program. c c RITZR, Double precision arrays of length N. (OUTPUT) -c RITZI On output, RITZR(1:N) (resp. RITZI(1:N)) contains the real +c RITZI On output, RITZR(1:N) (resp. RITZI(1:N)) contains the real c (respectively imaginary) parts of the eigenvalues of H. c c BOUNDS Double precision array of length N. (OUTPUT) c On output, BOUNDS contains the Ritz estimates associated with -c the eigenvalues RITZR and RITZI. This is equal to RNORM -c times the last components of the eigenvectors corresponding +c the eigenvalues RITZR and RITZI. This is equal to RNORM +c times the last components of the eigenvectors corresponding c to the eigenvalues in RITZR and RITZI. c c Q Double precision N by N array. (WORKSPACE) @@ -61,7 +61,7 @@ c xxxxxx real c c\Routines called: -c dlahqr ARPACK routine to compute the real Schur form of an +c dlahqr LAPACK routine to compute the real Schur form of an c upper Hessenberg matrix and last row of the Schur vectors. c arscnd ARPACK utility routine for timing. c dmout ARPACK utility routine that prints matrices @@ -74,20 +74,20 @@ c dcopy Level 1 BLAS that copies one vector to another . c dnrm2 Level 1 BLAS that computes the norm of a vector. c dscal Level 1 BLAS that scales a vector. -c +c c c\Author c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.1' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: neigh.F SID: 2.3 DATE OF SID: 4/20/96 RELEASE: 2 c c\Remarks @@ -97,7 +97,7 @@ c c----------------------------------------------------------------------- c - subroutine dneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, + subroutine dneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, & q, ldq, workl, ierr) c c %----------------------------------------------------% @@ -112,32 +112,32 @@ subroutine dneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, c %------------------% c integer ierr, n, ldh, ldq - Double precision + Double precision & rnorm c c %-----------------% c | Array Arguments | c %-----------------% c - Double precision + Double precision & bounds(n), h(ldh,n), q(ldq,n), ritzi(n), ritzr(n), & workl(n*(n+3)) -c +c c %------------% c | Parameters | c %------------% c - Double precision + Double precision & one, zero parameter (one = 1.0D+0, zero = 0.0D+0) -c +c c %------------------------% c | Local Scalars & Arrays | c %------------------------% c logical select(1) integer i, iconj, msglvl - Double precision + Double precision & temp, vl(1) c c %----------------------% @@ -172,12 +172,12 @@ subroutine dneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, c call arscnd (t0) msglvl = mneigh -c +c if (msglvl .gt. 2) then - call dmout (logfil, n, n, h, ldh, ndigit, + call dmout (logfil, n, n, h, ldh, ndigit, & '_neigh: Entering upper Hessenberg matrix H ') end if -c +c c %-----------------------------------------------------------% c | 1. Compute the eigenvalues, the last components of the | c | corresponding Schur vectors and the full Schur form T | @@ -231,7 +231,7 @@ subroutine dneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, c %----------------------% c | Real eigenvalue case | c %----------------------% -c +c temp = dnrm2( n, q(1,i), 1 ) call dscal ( n, one / temp, q(1,i), 1 ) else @@ -245,7 +245,7 @@ subroutine dneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, c %-------------------------------------------% c if (iconj .eq. 0) then - temp = dlapy2( dnrm2( n, q(1,i), 1 ), + temp = dlapy2( dnrm2( n, q(1,i), 1 ), & dnrm2( n, q(1,i+1), 1 ) ) call dscal ( n, one / temp, q(1,i), 1 ) call dscal ( n, one / temp, q(1,i+1), 1 ) @@ -253,7 +253,7 @@ subroutine dneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, else iconj = 0 end if - end if + end if 10 continue c call dgemv ('T', n, n, one, q, ldq, bounds, 1, zero, workl, 1) @@ -274,7 +274,7 @@ subroutine dneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, c %----------------------% c | Real eigenvalue case | c %----------------------% -c +c bounds(i) = rnorm * abs( workl(i) ) else c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dneupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dneupd.f index b5c6d6326cfb..860ceb856caf 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dneupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dneupd.f @@ -1,8 +1,8 @@ c\BeginDoc c -c\Name: dneupd +c\Name: dneupd c -c\Description: +c\Description: c c This subroutine returns the converged approximations to eigenvalues c of A*z = lambda*B*z and (optionally): @@ -28,34 +28,34 @@ c invariant subspace corresponding to these Ritz values is referred to as a c Schur basis. c -c See documentation in the header of the subroutine DNAUPD for +c See documentation in the header of the subroutine DNAUPD for c definition of OP as well as other terms and the relation of computed c Ritz values and Ritz vectors of OP with respect to the given problem -c A*z = lambda*B*z. For a brief description, see definitions of +c A*z = lambda*B*z. For a brief description, see definitions of c IPARAM(7), MODE and WHICH in the documentation of DNAUPD . c c\Usage: -c call dneupd -c ( RVEC, HOWMNY, SELECT, DR, DI, Z, LDZ, SIGMAR, SIGMAI, WORKEV, BMAT, -c N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, WORKD, WORKL, +c call dneupd +c ( RVEC, HOWMNY, SELECT, DR, DI, Z, LDZ, SIGMAR, SIGMAI, WORKEV, BMAT, +c N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, WORKD, WORKL, c LWORKL, INFO ) c c\Arguments: -c RVEC LOGICAL (INPUT) -c Specifies whether a basis for the invariant subspace corresponding -c to the converged Ritz value approximations for the eigenproblem +c RVEC LOGICAL (INPUT) +c Specifies whether a basis for the invariant subspace corresponding +c to the converged Ritz value approximations for the eigenproblem c A*z = lambda*B*z is computed. c c RVEC = .FALSE. Compute Ritz values only. c c RVEC = .TRUE. Compute the Ritz vectors or Schur vectors. -c See Remarks below. -c -c HOWMNY Character*1 (INPUT) -c Specifies the form of the basis for the invariant subspace +c See Remarks below. +c +c HOWMNY Character*1 (INPUT) +c Specifies the form of the basis for the invariant subspace c corresponding to the converged Ritz values that is to be computed. c -c = 'A': Compute NEV Ritz vectors; +c = 'A': Compute NEV Ritz vectors; c = 'P': Compute NEV Schur vectors; c = 'S': compute some of the Ritz vectors, specified c by the logical array SELECT. @@ -63,43 +63,43 @@ c SELECT Logical array of dimension NCV. (INPUT) c If HOWMNY = 'S', SELECT specifies the Ritz vectors to be c computed. To select the Ritz vector corresponding to a -c Ritz value (DR(j), DI(j)), SELECT(j) must be set to .TRUE.. +c Ritz value (DR(j), DI(j)), SELECT(j) must be set to .TRUE.. c If HOWMNY = 'A' or 'P', SELECT is used as internal workspace. c c DR Double precision array of dimension NEV+1. (OUTPUT) -c If IPARAM(7) = 1,2 or 3 and SIGMAI=0.0 then on exit: DR contains -c the real part of the Ritz approximations to the eigenvalues of -c A*z = lambda*B*z. +c If IPARAM(7) = 1,2 or 3 and SIGMAI=0.0 then on exit: DR contains +c the real part of the Ritz approximations to the eigenvalues of +c A*z = lambda*B*z. c If IPARAM(7) = 3, 4 and SIGMAI is not equal to zero, then on exit: -c DR contains the real part of the Ritz values of OP computed by +c DR contains the real part of the Ritz values of OP computed by c DNAUPD . A further computation must be performed by the user c to transform the Ritz values computed for OP by DNAUPD to those c of the original system A*z = lambda*B*z. See remark 3 below. c c DI Double precision array of dimension NEV+1. (OUTPUT) -c On exit, DI contains the imaginary part of the Ritz value +c On exit, DI contains the imaginary part of the Ritz value c approximations to the eigenvalues of A*z = lambda*B*z associated c with DR. c -c NOTE: When Ritz values are complex, they will come in complex -c conjugate pairs. If eigenvectors are requested, the -c corresponding Ritz vectors will also come in conjugate -c pairs and the real and imaginary parts of these are -c represented in two consecutive columns of the array Z +c NOTE: When Ritz values are complex, they will come in complex +c conjugate pairs. If eigenvectors are requested, the +c corresponding Ritz vectors will also come in conjugate +c pairs and the real and imaginary parts of these are +c represented in two consecutive columns of the array Z c (see below). c c Z Double precision N by NEV+1 array if RVEC = .TRUE. and HOWMNY = 'A'. (OUTPUT) -c On exit, if RVEC = .TRUE. and HOWMNY = 'A', then the columns of -c Z represent approximate eigenvectors (Ritz vectors) corresponding -c to the NCONV=IPARAM(5) Ritz values for eigensystem -c A*z = lambda*B*z. -c -c The complex Ritz vector associated with the Ritz value -c with positive imaginary part is stored in two consecutive -c columns. The first column holds the real part of the Ritz -c vector and the second column holds the imaginary part. The -c Ritz vector associated with the Ritz value with negative -c imaginary part is simply the complex conjugate of the Ritz vector +c On exit, if RVEC = .TRUE. and HOWMNY = 'A', then the columns of +c Z represent approximate eigenvectors (Ritz vectors) corresponding +c to the NCONV=IPARAM(5) Ritz values for eigensystem +c A*z = lambda*B*z. +c +c The complex Ritz vector associated with the Ritz value +c with positive imaginary part is stored in two consecutive +c columns. The first column holds the real part of the Ritz +c vector and the second column holds the imaginary part. The +c Ritz vector associated with the Ritz value with negative +c imaginary part is simply the complex conjugate of the Ritz vector c associated with the positive imaginary part. c c If RVEC = .FALSE. or HOWMNY = 'P', then Z is not referenced. @@ -114,11 +114,11 @@ c desired, then LDZ >= max( 1, N ). In any case, LDZ >= 1. c c SIGMAR Double precision (INPUT) -c If IPARAM(7) = 3 or 4, represents the real part of the shift. +c If IPARAM(7) = 3 or 4, represents the real part of the shift. c Not referenced if IPARAM(7) = 1 or 2. c c SIGMAI Double precision (INPUT) -c If IPARAM(7) = 3 or 4, represents the imaginary part of the shift. +c If IPARAM(7) = 3 or 4, represents the imaginary part of the shift. c Not referenced if IPARAM(7) = 1 or 2. See remark 3 below. c c WORKEV Double precision work array of dimension 3*NCV. (WORKSPACE) @@ -181,12 +181,12 @@ c c = 0: Normal exit. c -c = 1: The Schur form computed by LAPACK routine dlahqr +c = 1: The Schur form computed by LAPACK routine dlahqr c could not be reordered by LAPACK routine dtrsen . -c Re-enter subroutine dneupd with IPARAM(5)=NCV and -c increase the size of the arrays DR and DI to have -c dimension at least dimension NCV and allocate at least NCV -c columns for Z. NOTE: Not necessary if Z and V share +c Re-enter subroutine dneupd with IPARAM(5)=NCV and +c increase the size of the arrays DR and DI to have +c dimension at least dimension NCV and allocate at least NCV +c columns for Z. NOTE: Not necessary if Z and V share c the same space. Please notify the authors if this error c occurs. c @@ -218,7 +218,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c 3. B.N. Parlett & Y. Saad, "Complex Shift and Invert Strategies for @@ -229,7 +229,7 @@ c ivout ARPACK utility routine that prints integers. c dmout ARPACK utility routine that prints matrices c dvout ARPACK utility routine that prints vectors. -c dgeqr2 LAPACK routine that computes the QR factorization of +c dgeqr2 LAPACK routine that computes the QR factorization of c a matrix. c dlacpy LAPACK matrix copy routine. c dlahqr LAPACK routine to compute the real Schur form of an @@ -237,7 +237,7 @@ c dlamch LAPACK routine that determines machine constants. c dlapy2 LAPACK routine to compute sqrt(x**2+y**2) carefully. c dlaset LAPACK matrix initialization routine. -c dorm2r LAPACK routine that applies an orthogonal matrix in +c dorm2r LAPACK routine that applies an orthogonal matrix in c factored form. c dtrevc LAPACK routine to compute the eigenvectors of a matrix c in upper quasi-triangular form. @@ -259,10 +259,10 @@ c Ritz vectors. Thus, their numerical properties are often superior. c If RVEC = .TRUE. then the relationship c A * V(:,1:IPARAM(5)) = V(:,1:IPARAM(5)) * T, and -c trans(V(:,1:IPARAM(5))) * V(:,1:IPARAM(5)) = I are approximately -c satisfied. Here T is the leading submatrix of order IPARAM(5) of the +c trans(V(:,1:IPARAM(5))) * V(:,1:IPARAM(5)) = I are approximately +c satisfied. Here T is the leading submatrix of order IPARAM(5) of the c real upper quasi-triangular matrix stored workl(ipntr(12)). That is, -c T is block upper triangular with 1-by-1 and 2-by-2 diagonal blocks; +c T is block upper triangular with 1-by-1 and 2-by-2 diagonal blocks; c each 2-by-2 diagonal block has its diagonal elements equal and its c off-diagonal elements of opposite sign. Corresponding to each 2-by-2 c diagonal block is a complex conjugate pair of Ritz values. The real @@ -270,14 +270,14 @@ c c 3. If IPARAM(7) = 3 or 4 and SIGMAI is not equal zero, then the user must c form the IPARAM(5) Rayleigh quotients in order to transform the Ritz -c values computed by DNAUPD for OP to those of A*z = lambda*B*z. +c values computed by DNAUPD for OP to those of A*z = lambda*B*z. c Set RVEC = .true. and HOWMNY = 'A', and -c compute +c compute c trans(Z(:,I)) * A * Z(:,I) if DI(I) = 0. -c If DI(I) is not equal to zero and DI(I+1) = - D(I), +c If DI(I) is not equal to zero and DI(I+1) = - D(I), c then the desired real and imaginary parts of the Ritz value are c trans(Z(:,I)) * A * Z(:,I) + trans(Z(:,I+1)) * A * Z(:,I+1), -c trans(Z(:,I)) * A * Z(:,I+1) - trans(Z(:,I+1)) * A * Z(:,I), +c trans(Z(:,I)) * A * Z(:,I+1) - trans(Z(:,I+1)) * A * Z(:,I), c respectively. c Another possibility is to set RVEC = .true. and HOWMNY = 'P' and c compute trans(V(:,1:IPARAM(5))) * A * V(:,1:IPARAM(5)) and then an upper @@ -286,20 +286,20 @@ c c\Authors c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University +c Richard Lehoucq CRPC / Rice University c Chao Yang Houston, Texas c Dept. of Computational & -c Applied Mathematics -c Rice University -c Houston, Texas -c -c\SCCS Information: @(#) -c FILE: neupd.F SID: 2.7 DATE OF SID: 09/20/00 RELEASE: 2 +c Applied Mathematics +c Rice University +c Houston, Texas +c +c\SCCS Information: @(#) +c FILE: neupd.F SID: 2.7 DATE OF SID: 09/20/00 RELEASE: 2 c c\EndLib c c----------------------------------------------------------------------- - subroutine dneupd (rvec , howmny, select, dr , di, + subroutine dneupd (rvec , howmny, select, dr , di, & z , ldz , sigmar, sigmai, workev, & bmat , n , which , nev , tol, & resid, ncv , v , ldv , iparam, @@ -319,7 +319,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, character bmat, howmny, which*2 logical rvec integer info, ldz, ldv, lworkl, n, ncv, nev - Double precision + Double precision & sigmar, sigmai, tol c c %-----------------% @@ -328,16 +328,16 @@ subroutine dneupd (rvec , howmny, select, dr , di, c integer iparam(11), ipntr(14) logical select(ncv) - Double precision - & dr(nev+1) , di(nev+1), resid(n) , - & v(ldv,ncv) , z(ldz,*) , workd(3*n), + Double precision + & dr(nev+1) , di(nev+1), resid(n) , + & v(ldv,ncv) , z(ldz,*) , workd(3*n), & workl(lworkl), workev(3*ncv) c c %------------% c | Parameters | c %------------% c - Double precision + Double precision & one, zero parameter (one = 1.0D+0 , zero = 0.0D+0 ) c @@ -346,16 +346,16 @@ subroutine dneupd (rvec , howmny, select, dr , di, c %---------------% c character type*6 - integer bounds, ierr , ih , ihbds , - & iheigr, iheigi, iconj , nconv , + integer bounds, ierr , ih , ihbds , + & iheigr, iheigi, iconj , nconv , & invsub, iuptri, iwev , iwork(1), & j , k , ldh , ldq , & mode , msglvl, outncv, ritzr , & ritzi , wri , wrr , irr , & iri , ibd , ishift, numcnv , - & np , jj , nconv2 + & np , jj , nconv2 logical reord - Double precision + Double precision & conds , rnorm, sep , temp, & vl(1,1), temp1, eps23 c @@ -363,18 +363,18 @@ subroutine dneupd (rvec , howmny, select, dr , di, c | External Subroutines | c %----------------------% c - external dcopy , dger , dgeqr2 , dlacpy , - & dlahqr , dlaset , dmout , dorm2r , - & dtrevc , dtrmm , dtrsen , dscal , + external dcopy , dger , dgeqr2 , dlacpy , + & dlahqr , dlaset , dmout , dorm2r , + & dtrevc , dtrmm , dtrsen , dscal , & dvout , ivout c c %--------------------% c | External Functions | c %--------------------% c - Double precision - & dlapy2 , dnrm2 , dlamch , ddot - external dlapy2 , dnrm2 , dlamch , ddot + Double precision + & dlapy2 , dnrm2 , dlamch , ddot + external dlapy2 , dnrm2 , dlamch , ddot c c %---------------------% c | Intrinsic Functions | @@ -385,7 +385,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, c %-----------------------% c | Executable Statements | c %-----------------------% -c +c c %------------------------% c | Set default parameters | c %------------------------% @@ -434,7 +434,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, else if (howmny .eq. 'S' ) then ierr = -12 end if -c +c if (mode .eq. 1 .or. mode .eq. 2) then type = 'REGULR' else if (mode .eq. 3 .and. sigmai .eq. zero) then @@ -443,7 +443,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, type = 'REALPT' else if (mode .eq. 4 ) then type = 'IMAGPT' - else + else ierr = -10 end if if (mode .eq. 1 .and. bmat .eq. 'G') ierr = -11 @@ -456,7 +456,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, info = ierr go to 9000 end if -c +c c %--------------------------------------------------------% c | Pointer into WORKL for address of H, RITZ, BOUNDS, Q | c | etc... and the remaining workspace. | @@ -483,7 +483,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, c | subspace for H. | c | GRAND total of NCV * ( 3 * NCV + 6 ) locations. | c %-----------------------------------------------------------% -c +c ih = ipntr(5) ritzr = ipntr(6) ritzi = ipntr(7) @@ -537,7 +537,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, end if c if (rvec) then -c +c reord = .false. c c %---------------------------------------------------% @@ -562,7 +562,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, c np = ncv - nev ishift = 0 - call dngets (ishift , which , nev , + call dngets (ishift , which , nev , & np , workl(irr), workl(iri), & workl(bounds), workl , workl(np+1)) c @@ -601,9 +601,9 @@ subroutine dneupd (rvec , howmny, select, dr , di, c %-----------------------------------------------------------% c if (msglvl .gt. 2) then - call ivout(logfil, 1, numcnv, ndigit, + call ivout(logfil, 1, [numcnv], ndigit, & '_neupd: Number of specified eigenvalues') - call ivout(logfil, 1, nconv, ndigit, + call ivout(logfil, 1, [nconv], ndigit, & '_neupd: Number of "converged" eigenvalues') end if c @@ -618,24 +618,24 @@ subroutine dneupd (rvec , howmny, select, dr , di, c | Make a copy of the upper Hessenberg matrix. | c | Initialize the Schur vector matrix Q to the identity. | c %-----------------------------------------------------------% -c +c call dcopy (ldh*ncv, workl(ih), 1, workl(iuptri), 1) - call dlaset ('All', ncv, ncv, + call dlaset ('All', ncv, ncv, & zero , one, workl(invsub), & ldq) - call dlahqr (.true., .true. , ncv, - & 1 , ncv , workl(iuptri), + call dlahqr (.true., .true. , ncv, + & 1 , ncv , workl(iuptri), & ldh , workl(iheigr), workl(iheigi), - & 1 , ncv , workl(invsub), + & 1 , ncv , workl(invsub), & ldq , ierr) - call dcopy (ncv , workl(invsub+ncv-1), ldq, + call dcopy (ncv , workl(invsub+ncv-1), ldq, & workl(ihbds), 1) -c +c if (ierr .ne. 0) then info = -8 go to 9000 end if -c +c if (msglvl .gt. 1) then call dvout (logfil, ncv, workl(iheigr), ndigit, & '_neupd: Real part of the eigenvalues of H') @@ -644,25 +644,25 @@ subroutine dneupd (rvec , howmny, select, dr , di, call dvout (logfil, ncv, workl(ihbds), ndigit, & '_neupd: Last row of the Schur vector matrix') if (msglvl .gt. 3) then - call dmout (logfil , ncv, ncv , + call dmout (logfil , ncv, ncv , & workl(iuptri), ldh, ndigit, & '_neupd: The upper quasi-triangular matrix ') end if - end if + end if c if (reord) then -c +c c %-----------------------------------------------------% -c | Reorder the computed upper quasi-triangular matrix. | +c | Reorder the computed upper quasi-triangular matrix. | c %-----------------------------------------------------% -c - call dtrsen ('None' , 'V' , +c + call dtrsen ('None' , 'V' , & select , ncv , - & workl(iuptri), ldh , - & workl(invsub), ldq , - & workl(iheigr), workl(iheigi), + & workl(iuptri), ldh , + & workl(invsub), ldq , + & workl(iheigr), workl(iheigi), & nconv2 , conds , - & sep , workl(ihbds) , + & sep , workl(ihbds) , & ncv , iwork , & 1 , ierr) c @@ -682,12 +682,12 @@ subroutine dneupd (rvec , howmny, select, dr , di, call dvout (logfil, ncv, workl(iheigi), ndigit, & '_neupd: Imag part of the eigenvalues of H--reordered') if (msglvl .gt. 3) then - call dmout (logfil , ncv, ncv , + call dmout (logfil , ncv, ncv , & workl(iuptri), ldq, ndigit, & '_neupd: Quasi-triangular matrix after re-ordering') end if end if -c +c end if c c %---------------------------------------% @@ -704,23 +704,23 @@ subroutine dneupd (rvec , howmny, select, dr , di, c | if a spectral transformation was not used. | c %----------------------------------------------------% c - if (type .eq. 'REGULR') then + if (type .eq. 'REGULR') then call dcopy (nconv, workl(iheigr), 1, dr, 1) call dcopy (nconv, workl(iheigi), 1, di, 1) end if -c +c c %----------------------------------------------------------% c | Compute the QR factorization of the matrix representing | c | the wanted invariant subspace located in the first NCONV | c | columns of workl(invsub,ldq). | c %----------------------------------------------------------% -c - call dgeqr2 (ncv, nconv , workl(invsub), +c + call dgeqr2 (ncv, nconv , workl(invsub), & ldq, workev, workev(ncv+1), & ierr) c c %---------------------------------------------------------% -c | * Postmultiply V by Q using dorm2r . | +c | * Postmultiply V by Q using dorm2r . | c | * Copy the first NCONV columns of VQ into Z. | c | * Postmultiply Z by R. | c | The N by NCONV matrix Z is now a matrix representation | @@ -730,15 +730,15 @@ subroutine dneupd (rvec , howmny, select, dr , di, c | vectors associated with the real upper quasi-triangular | c | matrix of order NCONV in workl(iuptri) | c %---------------------------------------------------------% -c - call dorm2r ('Right', 'Notranspose', n , +c + call dorm2r ('Right', 'Notranspose', n , & ncv , nconv , workl(invsub), - & ldq , workev , v , + & ldq , workev , v , & ldv , workd(n+1) , ierr) call dlacpy ('All', n, nconv, v, ldv, z, ldz) c do 20 j=1, nconv -c +c c %---------------------------------------------------% c | Perform both a column and row scaling if the | c | diagonal element of workl(invsub,ldq) is negative | @@ -747,21 +747,21 @@ subroutine dneupd (rvec , howmny, select, dr , di, c | Note that since Q is orthogonal, R is a diagonal | c | matrix consisting of plus or minus ones | c %---------------------------------------------------% -c +c if (workl(invsub+(j-1)*ldq+j-1) .lt. zero) then call dscal (nconv, -one, workl(iuptri+j-1), ldq) call dscal (nconv, -one, workl(iuptri+(j-1)*ldq), 1) end if -c +c 20 continue -c +c if (howmny .eq. 'A') then -c +c c %--------------------------------------------% -c | Compute the NCONV wanted eigenvectors of T | +c | Compute the NCONV wanted eigenvectors of T | c | located in workl(iuptri,ldq). | c %--------------------------------------------% -c +c do 30 j=1, ncv if (j .le. nconv) then select(j) = .true. @@ -770,8 +770,8 @@ subroutine dneupd (rvec , howmny, select, dr , di, end if 30 continue c - call dtrevc ('Right', 'Select' , select , - & ncv , workl(iuptri), ldq , + call dtrevc ('Right', 'Select' , select , + & ncv , workl(iuptri), ldq , & vl , 1 , workl(invsub), & ldq , ncv , outncv , & workev , ierr) @@ -780,7 +780,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, info = -9 go to 9000 end if -c +c c %------------------------------------------------% c | Scale the returning eigenvectors so that their | c | Euclidean norms are all one. LAPACK subroutine | @@ -788,22 +788,22 @@ subroutine dneupd (rvec , howmny, select, dr , di, c | that the element of largest magnitude has | c | magnitude 1; | c %------------------------------------------------% -c +c iconj = 0 do 40 j=1, nconv c if ( workl(iheigi+j-1) .eq. zero ) then -c +c c %----------------------% c | real eigenvalue case | c %----------------------% -c +c temp = dnrm2 ( ncv, workl(invsub+(j-1)*ldq), 1 ) - call dscal ( ncv, one / temp, + call dscal ( ncv, one / temp, & workl(invsub+(j-1)*ldq), 1 ) c else -c +c c %-------------------------------------------% c | Complex conjugate pair case. Note that | c | since the real and imaginary part of | @@ -813,15 +813,15 @@ subroutine dneupd (rvec , howmny, select, dr , di, c %-------------------------------------------% c if (iconj .eq. 0) then - temp = dlapy2 (dnrm2 (ncv, - & workl(invsub+(j-1)*ldq), + temp = dlapy2 (dnrm2 (ncv, + & workl(invsub+(j-1)*ldq), & 1), - & dnrm2 (ncv, + & dnrm2 (ncv, & workl(invsub+j*ldq), - & 1)) - call dscal (ncv, one/temp, + & 1)) + call dscal (ncv, one/temp, & workl(invsub+(j-1)*ldq), 1 ) - call dscal (ncv, one/temp, + call dscal (ncv, one/temp, & workl(invsub+j*ldq), 1 ) iconj = 1 else @@ -861,7 +861,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, call dvout (logfil, ncv, workl(ihbds), ndigit, & '_neupd: Last row of the eigenvector matrix for T') if (msglvl .gt. 3) then - call dmout (logfil, ncv, ncv, workl(invsub), ldq, + call dmout (logfil, ncv, ncv, workl(invsub), ldq, & ndigit, '_neupd: The eigenvector matrix for T') end if end if @@ -877,32 +877,32 @@ subroutine dneupd (rvec , howmny, select, dr , di, c | associated with leading portion of T in the first NCONV | c | columns of workl(invsub,ldq). | c %---------------------------------------------------------% -c - call dgeqr2 (ncv, nconv , workl(invsub), +c + call dgeqr2 (ncv, nconv , workl(invsub), & ldq, workev, workev(ncv+1), & ierr) -c +c c %----------------------------------------------% -c | * Postmultiply Z by Q. | +c | * Postmultiply Z by Q. | c | * Postmultiply Z by R. | -c | The N by NCONV matrix Z is now contains the | +c | The N by NCONV matrix Z is now contains the | c | Ritz vectors associated with the Ritz values | c | in workl(iheigr) and workl(iheigi). | c %----------------------------------------------% -c +c call dorm2r ('Right', 'Notranspose', n , & ncv , nconv , workl(invsub), & ldq , workev , z , & ldz , workd(n+1) , ierr) -c +c call dtrmm ('Right' , 'Upper' , 'No transpose', & 'Non-unit', n , nconv , & one , workl(invsub), ldq , & z , ldz) -c +c end if -c - else +c + else c c %------------------------------------------------------% c | An approximate invariant subspace is not needed. | @@ -915,7 +915,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, call dcopy (nconv, workl(ritzi), 1, workl(iheigi), 1) call dcopy (nconv, workl(bounds), 1, workl(ihbds), 1) end if -c +c c %------------------------------------------------% c | Transform the Ritz values and possibly vectors | c | and corresponding error bounds of OP to those | @@ -924,26 +924,26 @@ subroutine dneupd (rvec , howmny, select, dr , di, c if (type .eq. 'REGULR') then c - if (rvec) - & call dscal (ncv, rnorm, workl(ihbds), 1) -c - else -c + if (rvec) + & call dscal (ncv, rnorm, workl(ihbds), 1) +c + else +c c %---------------------------------------% c | A spectral transformation was used. | c | * Determine the Ritz estimates of the | c | Ritz values in the original system. | c %---------------------------------------% -c +c if (type .eq. 'SHIFTI') then c - if (rvec) + if (rvec) & call dscal (ncv, rnorm, workl(ihbds), 1) c do 50 k=1, ncv - temp = dlapy2 ( workl(iheigr+k-1), + temp = dlapy2 ( workl(iheigr+k-1), & workl(iheigi+k-1) ) - workl(ihbds+k-1) = abs( workl(ihbds+k-1) ) + workl(ihbds+k-1) = abs( workl(ihbds+k-1) ) & / temp / temp 50 continue c @@ -958,26 +958,26 @@ subroutine dneupd (rvec , howmny, select, dr , di, 70 continue c end if -c +c c %-----------------------------------------------------------% c | * Transform the Ritz values back to the original system. | c | For TYPE = 'SHIFTI' the transformation is | c | lambda = 1/theta + sigma | c | For TYPE = 'REALPT' or 'IMAGPT' the user must from | -c | Rayleigh quotients or a projection. See remark 3 above.| +c | Rayleigh quotients or a projection. See remark 3 above.| c | NOTES: | c | *The Ritz vectors are not affected by the transformation. | c %-----------------------------------------------------------% -c - if (type .eq. 'SHIFTI') then +c + if (type .eq. 'SHIFTI') then c do 80 k=1, ncv - temp = dlapy2 ( workl(iheigr+k-1), + temp = dlapy2 ( workl(iheigr+k-1), & workl(iheigi+k-1) ) - workl(iheigr+k-1) = workl(iheigr+k-1)/temp/temp - & + sigmar + workl(iheigr+k-1) = workl(iheigr+k-1)/temp/temp + & + sigmar workl(iheigi+k-1) = -workl(iheigi+k-1)/temp/temp - & + sigmai + & + sigmai 80 continue c call dcopy (nconv, workl(iheigr), 1, dr, 1) @@ -994,9 +994,9 @@ subroutine dneupd (rvec , howmny, select, dr , di, c if (type .eq. 'SHIFTI' .and. msglvl .gt. 1) then call dvout (logfil, nconv, dr, ndigit, - & '_neupd: Untransformed real part of the Ritz valuess.') + & '_neupd: Untransformed real part of the Ritz values.') call dvout (logfil, nconv, di, ndigit, - & '_neupd: Untransformed imag part of the Ritz valuess.') + & '_neupd: Untransformed imag part of the Ritz values.') call dvout (logfil, nconv, workl(ihbds), ndigit, & '_neupd: Ritz estimates of untransformed Ritz values.') else if (type .eq. 'REGULR' .and. msglvl .gt. 1) then @@ -1007,7 +1007,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, call dvout (logfil, nconv, workl(ihbds), ndigit, & '_neupd: Associated Ritz estimates.') end if -c +c c %-------------------------------------------------% c | Eigenvector Purification step. Formally perform | c | one of inverse subspace iteration. Only used | @@ -1029,19 +1029,22 @@ subroutine dneupd (rvec , howmny, select, dr , di, c iconj = 0 do 110 j=1, nconv - if (workl(iheigi+j-1) .eq. zero) then + if ((workl(iheigi+j-1) .eq. zero) .and. + & (workl(iheigr+j-1) .ne. zero)) then workev(j) = workl(invsub+(j-1)*ldq+ncv-1) / & workl(iheigr+j-1) else if (iconj .eq. 0) then temp = dlapy2 ( workl(iheigr+j-1), workl(iheigi+j-1) ) - workev(j) = ( workl(invsub+(j-1)*ldq+ncv-1) * - & workl(iheigr+j-1) + - & workl(invsub+j*ldq+ncv-1) * - & workl(iheigi+j-1) ) / temp / temp - workev(j+1) = ( workl(invsub+j*ldq+ncv-1) * - & workl(iheigr+j-1) - - & workl(invsub+(j-1)*ldq+ncv-1) * - & workl(iheigi+j-1) ) / temp / temp + if (temp .ne. zero) then + workev(j) = ( workl(invsub+(j-1)*ldq+ncv-1) * + & workl(iheigr+j-1) + + & workl(invsub+j*ldq+ncv-1) * + & workl(iheigi+j-1) ) / temp / temp + workev(j+1) = ( workl(invsub+j*ldq+ncv-1) * + & workl(iheigr+j-1) - + & workl(invsub+(j-1)*ldq+ncv-1) * + & workl(iheigi+j-1) ) / temp / temp + end if iconj = 1 else iconj = 0 @@ -1060,7 +1063,7 @@ subroutine dneupd (rvec , howmny, select, dr , di, 9000 continue c return -c +c c %---------------% c | End of DNEUPD | c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dngets.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dngets.f index 4c93a6dc5fe0..47d3ac2ce022 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dngets.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dngets.f @@ -3,9 +3,9 @@ c c\Name: dngets c -c\Description: +c\Description: c Given the eigenvalues of the upper Hessenberg matrix H, -c computes the NP shifts AMU that are zeros of the polynomial of +c computes the NP shifts AMU that are zeros of the polynomial of c degree NP which filters out components of the unwanted eigenvectors c corresponding to the AMU's based on some given criteria. c @@ -42,12 +42,12 @@ c pairs together. c c RITZR, Double precision array of length KEV+NP. (INPUT/OUTPUT) -c RITZI On INPUT, RITZR and RITZI contain the real and imaginary +c RITZI On INPUT, RITZR and RITZI contain the real and imaginary c parts of the eigenvalues of H. c On OUTPUT, RITZR and RITZI are sorted so that the unwanted c eigenvalues are in the first NP locations and the wanted -c portion is in the last KEV locations. When exact shifts are -c selected, the unwanted part corresponds to the shifts to +c portion is in the last KEV locations. When exact shifts are +c selected, the unwanted part corresponds to the shifts to c be applied. Also, if ISHIFT .eq. 1, the unwanted eigenvalues c are further sorted so that the ones with largest Ritz values c are first. @@ -56,7 +56,7 @@ c Error bounds corresponding to the ordering in RITZ. c c SHIFTR, SHIFTI *** USE deprecated as of version 2.1. *** -c +c c c\EndDoc c @@ -76,13 +76,13 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.1' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: ngets.F SID: 2.3 DATE OF SID: 4/20/96 RELEASE: 2 c c\Remarks @@ -114,7 +114,7 @@ subroutine dngets ( ishift, which, kev, np, ritzr, ritzi, bounds, c %-----------------% c Double precision - & bounds(kev+np), ritzr(kev+np), ritzi(kev+np), + & bounds(kev+np), ritzr(kev+np), ritzi(kev+np), & shiftr(1), shifti(1) c c %------------% @@ -151,10 +151,10 @@ subroutine dngets ( ishift, which, kev, np, ritzr, ritzi, bounds, c | Initialize timing statistics | c | & message level for debugging | c %-------------------------------% -c +c call arscnd (t0) msglvl = mngets -c +c c %----------------------------------------------------% c | LM, SM, LR, SR, LI, SI case. | c | Sort the eigenvalues of H into the desired order | @@ -178,16 +178,16 @@ subroutine dngets ( ishift, which, kev, np, ritzr, ritzi, bounds, else if (which .eq. 'SI') then call dsortc ('SM', .true., kev+np, ritzr, ritzi, bounds) end if -c +c call dsortc (which, .true., kev+np, ritzr, ritzi, bounds) -c +c c %-------------------------------------------------------% c | Increase KEV by one if the ( ritzr(np),ritzi(np) ) | c | = ( ritzr(np+1),-ritzi(np+1) ) and ritz(np) .ne. zero | c | Accordingly decrease NP by one. In other words keep | c | complex conjugate pairs together. | c %-------------------------------------------------------% -c +c if ( ( ritzr(np+1) - ritzr(np) ) .eq. zero & .and. ( ritzi(np+1) + ritzi(np) ) .eq. zero ) then np = np - 1 @@ -195,7 +195,7 @@ subroutine dngets ( ishift, which, kev, np, ritzr, ritzi, bounds, end if c if ( ishift .eq. 1 ) then -c +c c %-------------------------------------------------------% c | Sort the unwanted Ritz values used as shifts so that | c | the ones with largest Ritz estimates are first | @@ -204,28 +204,28 @@ subroutine dngets ( ishift, which, kev, np, ritzr, ritzi, bounds, c | are applied in subroutine dnapps. | c | Be careful and use 'SR' since we want to sort BOUNDS! | c %-------------------------------------------------------% -c +c call dsortc ( 'SR', .true., np, bounds, ritzr, ritzi ) end if -c +c call arscnd (t1) tngets = tngets + (t1 - t0) c if (msglvl .gt. 0) then - call ivout (logfil, 1, kev, ndigit, '_ngets: KEV is') - call ivout (logfil, 1, np, ndigit, '_ngets: NP is') + call ivout (logfil, 1, [kev], ndigit, '_ngets: KEV is') + call ivout (logfil, 1, [np], ndigit, '_ngets: NP is') call dvout (logfil, kev+np, ritzr, ndigit, & '_ngets: Eigenvalues of current H matrix -- real part') call dvout (logfil, kev+np, ritzi, ndigit, & '_ngets: Eigenvalues of current H matrix -- imag part') - call dvout (logfil, kev+np, bounds, ndigit, + call dvout (logfil, kev+np, bounds, ndigit, & '_ngets: Ritz estimates of the current KEV+NP Ritz values') end if -c +c return -c +c c %---------------% c | End of dngets | c %---------------% -c +c end diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaitr.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaitr.f index c38dfe062d22..3460d990c94a 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaitr.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaitr.f @@ -3,8 +3,8 @@ c c\Name: dsaitr c -c\Description: -c Reverse communication interface for applying NP additional steps to +c\Description: +c Reverse communication interface for applying NP additional steps to c a K step symmetric Arnoldi factorization. c c Input: OP*V_{k} - V_{k}*H = r_{k}*e_{k}^T @@ -20,7 +20,7 @@ c c\Usage: c call dsaitr -c ( IDO, BMAT, N, K, NP, MODE, RESID, RNORM, V, LDV, H, LDH, +c ( IDO, BMAT, N, K, NP, MODE, RESID, RNORM, V, LDV, H, LDH, c IPNTR, WORKD, INFO ) c c\Arguments @@ -76,13 +76,13 @@ c On OUTPUT the B-norm of the updated residual r_{k+p}. c c V Double precision N by K+NP array. (INPUT/OUTPUT) -c On INPUT: V contains the Arnoldi vectors in the first K +c On INPUT: V contains the Arnoldi vectors in the first K c columns. c On OUTPUT: V contains the new NP Arnoldi vectors in the next c NP columns. The first K columns are unchanged. c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c H Double precision (K+NP) by 2 array. (INPUT/OUTPUT) @@ -91,26 +91,26 @@ c and the main diagonal in the second column. c c LDH Integer. (INPUT) -c Leading dimension of H exactly as declared in the calling +c Leading dimension of H exactly as declared in the calling c program. c c IPNTR Integer array of length 3. (OUTPUT) -c Pointer to mark the starting locations in the WORK for +c Pointer to mark the starting locations in the WORK for c vectors used by the Arnoldi iteration. c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X. c IPNTR(2): pointer to the current result vector Y. -c IPNTR(3): pointer to the vector B * X when used in the +c IPNTR(3): pointer to the vector B * X when used in the c shift-and-invert mode. X is the current operand. c ------------------------------------------------------------- -c +c c WORKD Double precision work array of length 3*N. (REVERSE COMMUNICATION) c Distributed array to be used in the basic Arnoldi iteration -c for reverse communication. The calling program should not +c for reverse communication. The calling program should not c use WORKD as temporary workspace during the iteration !!!!!! c On INPUT, WORKD(1:N) = B*RESID where RESID is associated -c with the K step Arnoldi factorization. Used to save some -c computation at the first step. +c with the K step Arnoldi factorization. Used to save some +c computation at the first step. c On OUTPUT, WORKD(1:N) = B*RESID where RESID is associated c with the K+NP step Arnoldi factorization. c @@ -139,7 +139,7 @@ c daxpy Level 1 BLAS that computes a vector triad. c dscal Level 1 BLAS that scales a vector. c dcopy Level 1 BLAS that copies one vector to another . -c ddot Level 1 BLAS that computes the scalar product of two vectors. +c ddot Level 1 BLAS that computes the scalar product of two vectors. c dnrm2 Level 1 BLAS that computes the norm of a vector. c c\Author @@ -147,29 +147,29 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas -c +c Rice University +c Houston, Texas +c c\Revision history: c xx/xx/93: Version ' 2.4' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: saitr.F SID: 2.6 DATE OF SID: 8/28/96 RELEASE: 2 c c\Remarks c The algorithm implemented is: -c +c c restart = .false. -c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; +c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; c r_{k} contains the initial residual vector even for k = 0; -c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already +c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already c computed by the calling program. c c betaj = rnorm ; p_{k+1} = B*r_{k} ; c For j = k+1, ..., k+np Do c 1) if ( betaj < tol ) stop or restart depending on j. c if ( restart ) generate a new starting vector. -c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; +c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; c p_{j} = p_{j}/betaj c 3) r_{j} = OP*v_{j} where OP is defined as in dsaupd c For shift-invert mode p_{j} = B*v_{j} is already available. @@ -184,7 +184,7 @@ c 5) Re-orthogonalization step: c s = V_{j}'*B*r_{j} c r_{j} = r_{j} - V_{j}*s; rnorm1 = || r_{j} || -c alphaj = alphaj + s_{j}; +c alphaj = alphaj + s_{j}; c 6) Iterative refinement step: c If (rnorm1 > 0.717*rnorm) then c rnorm = rnorm1 @@ -194,7 +194,7 @@ c If this is the first time in step 6), go to 5) c Else r_{j} lies in the span of V_{j} numerically. c Set r_{j} = 0 and rnorm = 0; go to 1) -c EndIf +c EndIf c End Do c c\EndLib @@ -202,7 +202,7 @@ c----------------------------------------------------------------------- c subroutine dsaitr - & (ido, bmat, n, k, np, mode, resid, rnorm, v, ldv, h, ldh, + & (ido, bmat, n, k, np, mode, resid, rnorm, v, ldv, h, ldh, & ipntr, workd, info) c c %----------------------------------------------------% @@ -242,7 +242,7 @@ subroutine dsaitr c %---------------% c logical first, orth1, orth2, rstart, step3, step4 - integer i, ierr, ipj, irj, ivj, iter, itry, j, msglvl, + integer i, ierr, ipj, irj, ivj, iter, itry, j, msglvl, & infol, jj Double precision & rnorm1, wnorm, safmin, temp1 @@ -251,7 +251,7 @@ subroutine dsaitr & rnorm1, safmin, wnorm c c %-----------------------% -c | Local Array Arguments | +c | Local Array Arguments | c %-----------------------% c Double precision @@ -294,7 +294,7 @@ subroutine dsaitr end if c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -302,7 +302,7 @@ subroutine dsaitr c call arscnd (t0) msglvl = msaitr -c +c c %------------------------------% c | Initial call to this routine | c %------------------------------% @@ -313,14 +313,14 @@ subroutine dsaitr rstart = .false. orth1 = .false. orth2 = .false. -c +c c %--------------------------------% c | Pointer to the current step of | c | the factorization to build | c %--------------------------------% c j = k + 1 -c +c c %------------------------------------------% c | Pointers used for reverse communication | c | when using WORKD. | @@ -330,7 +330,7 @@ subroutine dsaitr irj = ipj + n ivj = irj + n end if -c +c c %-------------------------------------------------% c | When in reverse communication mode one of: | c | STEP3, STEP4, ORTH1, ORTH2, RSTART | @@ -353,7 +353,7 @@ subroutine dsaitr c %------------------------------% c | Else this is the first step. | c %------------------------------% -c +c c %--------------------------------------------------------------% c | | c | A R N O L D I I T E R A T I O N L O O P | @@ -364,15 +364,15 @@ subroutine dsaitr 1000 continue c if (msglvl .gt. 2) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_saitr: generating Arnoldi vector no.') - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_saitr: B-norm of the current residual =') end if -c +c c %---------------------------------------------------------% -c | Check for exact zero. Equivalent to determing whether a | -c | j-step Arnoldi factorization is present. | +c | Check for exact zero. Equivalent to determining whether | +c | a j-step Arnoldi factorization is present. | c %---------------------------------------------------------% c if (rnorm .gt. zero) go to 40 @@ -384,10 +384,10 @@ subroutine dsaitr c %---------------------------------------------------% c if (msglvl .gt. 0) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_saitr: ****** restart at step ******') end if -c +c c %---------------------------------------------% c | ITRY is the loop variable that controls the | c | maximum amount of times that a restart is | @@ -406,7 +406,7 @@ subroutine dsaitr c | RSTART = .true. flow returns here. | c %--------------------------------------% c - call dgetv0 (ido, bmat, itry, .false., n, j, v, ldv, + call dgetv0 (ido, bmat, itry, .false., n, j, v, ldv, & resid, rnorm, ipntr, workd, ierr) if (ido .ne. 99) go to 9000 if (ierr .lt. 0) then @@ -425,7 +425,7 @@ subroutine dsaitr ido = 99 go to 9000 end if -c +c 40 continue c c %---------------------------------------------------------% @@ -447,12 +447,12 @@ subroutine dsaitr c | use LAPACK routine SLASCL | c %-----------------------------------------% c - call dlascl ('General', i, i, rnorm, one, n, 1, + call dlascl ('General', i, i, rnorm, one, n, 1, & v(1,j), n, infol) - call dlascl ('General', i, i, rnorm, one, n, 1, + call dlascl ('General', i, i, rnorm, one, n, 1, & workd(ipj), n, infol) end if -c +c c %------------------------------------------------------% c | STEP 3: r_{j} = OP*v_{j}; Note that p_{j} = B*v_{j} | c | Note that this is not quite yet r_{j}. See STEP 4 | @@ -466,14 +466,14 @@ subroutine dsaitr ipntr(2) = irj ipntr(3) = ipj ido = 1 -c +c c %-----------------------------------% c | Exit in order to compute OP*v_{j} | c %-----------------------------------% -c +c go to 9000 50 continue -c +c c %-----------------------------------% c | Back from reverse communication; | c | WORKD(IRJ:IRJ+N-1) := OP*v_{j}. | @@ -481,7 +481,7 @@ subroutine dsaitr c call arscnd (t3) tmvopx = tmvopx + (t3 - t2) -c +c step3 = .false. c c %------------------------------------------% @@ -489,7 +489,7 @@ subroutine dsaitr c %------------------------------------------% c call dcopy (n, workd(irj), 1, resid, 1) -c +c c %-------------------------------------------% c | STEP 4: Finish extending the symmetric | c | Arnoldi to length j. If MODE = 2 | @@ -507,17 +507,17 @@ subroutine dsaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-------------------------------------% c | Exit in order to compute B*OP*v_{j} | c %-------------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call dcopy(n, resid, 1 , workd(ipj), 1) end if 60 continue -c +c c %-----------------------------------% c | Back from reverse communication; | c | WORKD(IPJ:IPJ+N-1) := B*OP*v_{j}. | @@ -526,7 +526,7 @@ subroutine dsaitr if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) - end if + end if c step4 = .false. c @@ -545,7 +545,7 @@ subroutine dsaitr c wnorm = ddot (n, resid, 1, workd(ivj), 1) wnorm = sqrt(abs(wnorm)) - else if (bmat .eq. 'G') then + else if (bmat .eq. 'G') then wnorm = ddot (n, resid, 1, workd(ipj), 1) wnorm = sqrt(abs(wnorm)) else if (bmat .eq. 'I') then @@ -567,19 +567,19 @@ subroutine dsaitr c %------------------------------------------% c if (mode .ne. 2 ) then - call dgemv('T', n, j, one, v, ldv, workd(ipj), 1, zero, + call dgemv('T', n, j, one, v, ldv, workd(ipj), 1, zero, & workd(irj), 1) else if (mode .eq. 2) then - call dgemv('T', n, j, one, v, ldv, workd(ivj), 1, zero, + call dgemv('T', n, j, one, v, ldv, workd(ivj), 1, zero, & workd(irj), 1) end if c c %--------------------------------------% c | Orthgonalize r_{j} against V_{j}. | -c | RESID contains OP*v_{j}. See STEP 3. | +c | RESID contains OP*v_{j}. See STEP 3. | c %--------------------------------------% c - call dgemv('N', n, j, -one, v, ldv, workd(irj), 1, one, + call dgemv('N', n, j, -one, v, ldv, workd(irj), 1, one, & resid, 1) c c %--------------------------------------% @@ -593,10 +593,10 @@ subroutine dsaitr h(j,1) = rnorm end if call arscnd (t4) -c +c orth1 = .true. iter = 0 -c +c call arscnd (t2) if (bmat .eq. 'G') then nbx = nbx + 1 @@ -604,17 +604,17 @@ subroutine dsaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %----------------------------------% c | Exit in order to compute B*r_{j} | c %----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call dcopy (n, resid, 1, workd(ipj), 1) end if 70 continue -c +c c %---------------------------------------------------% c | Back from reverse communication if ORTH1 = .true. | c | WORKD(IPJ:IPJ+N-1) := B*r_{j}. | @@ -624,14 +624,14 @@ subroutine dsaitr call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c orth1 = .false. c c %------------------------------% c | Compute the B-norm of r_{j}. | c %------------------------------% c - if (bmat .eq. 'G') then + if (bmat .eq. 'G') then rnorm = ddot (n, resid, 1, workd(ipj), 1) rnorm = sqrt(abs(rnorm)) else if (bmat .eq. 'I') then @@ -655,7 +655,7 @@ subroutine dsaitr c if (rnorm .gt. 0.717*wnorm) go to 100 nrorth = nrorth + 1 -c +c c %---------------------------------------------------% c | Enter the Iterative refinement phase. If further | c | refinement is necessary, loop back here. The loop | @@ -668,7 +668,7 @@ subroutine dsaitr if (msglvl .gt. 2) then xtemp(1) = wnorm xtemp(2) = rnorm - call dvout (logfil, 2, xtemp, ndigit, + call dvout (logfil, 2, xtemp, ndigit, & '_saitr: re-orthonalization ; wnorm and rnorm are') end if c @@ -677,7 +677,7 @@ subroutine dsaitr c | WORKD(IRJ:IRJ+J-1) = v(:,1:J)'*WORKD(IPJ:IPJ+N-1). | c %----------------------------------------------------% c - call dgemv ('T', n, j, one, v, ldv, workd(ipj), 1, + call dgemv ('T', n, j, one, v, ldv, workd(ipj), 1, & zero, workd(irj), 1) c c %----------------------------------------------% @@ -688,12 +688,12 @@ subroutine dsaitr c | H(j,j) is updated. | c %----------------------------------------------% c - call dgemv ('N', n, j, -one, v, ldv, workd(irj), 1, + call dgemv ('N', n, j, -one, v, ldv, workd(irj), 1, & one, resid, 1) c if (j .eq. 1 .or. rstart) h(j,1) = zero h(j,2) = h(j,2) + workd(irj + j - 1) -c +c orth2 = .true. call arscnd (t2) if (bmat .eq. 'G') then @@ -702,12 +702,12 @@ subroutine dsaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-----------------------------------% c | Exit in order to compute B*r_{j}. | c | r_{j} is the corrected residual. | c %-----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call dcopy (n, resid, 1, workd(ipj), 1) @@ -726,8 +726,8 @@ subroutine dsaitr c %-----------------------------------------------------% c | Compute the B-norm of the corrected residual r_{j}. | c %-----------------------------------------------------% -c - if (bmat .eq. 'G') then +c + if (bmat .eq. 'G') then rnorm1 = ddot (n, resid, 1, workd(ipj), 1) rnorm1 = sqrt(abs(rnorm1)) else if (bmat .eq. 'I') then @@ -735,7 +735,7 @@ subroutine dsaitr end if c if (msglvl .gt. 0 .and. iter .gt. 0) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_saitr: Iterative refinement for Arnoldi residual') if (msglvl .gt. 2) then xtemp(1) = rnorm @@ -744,7 +744,7 @@ subroutine dsaitr & '_saitr: iterative refinement ; rnorm and rnorm1 are') end if end if -c +c c %-----------------------------------------% c | Determine if we need to perform another | c | step of re-orthogonalization. | @@ -757,7 +757,7 @@ subroutine dsaitr c %--------------------------------% c rnorm = rnorm1 -c +c else c c %-------------------------------------------% @@ -779,7 +779,7 @@ subroutine dsaitr 95 continue rnorm = zero end if -c +c c %----------------------------------------------% c | Branch here directly if iterative refinement | c | wasn't necessary or after at most NITER_REF | @@ -787,13 +787,13 @@ subroutine dsaitr c %----------------------------------------------% c 100 continue -c +c rstart = .false. orth2 = .false. -c +c call arscnd (t5) titref = titref + (t5 - t4) -c +c c %----------------------------------------------------------% c | Make sure the last off-diagonal element is non negative | c | If not perform a similarity transformation on H(1:j,1:j) | @@ -802,13 +802,13 @@ subroutine dsaitr c if (h(j,1) .lt. zero) then h(j,1) = -h(j,1) - if ( j .lt. k+np) then + if ( j .lt. k+np) then call dscal(n, -one, v(1,j+1), 1) else call dscal(n, -one, resid, 1) end if end if -c +c c %------------------------------------% c | STEP 6: Update j = j+1; Continue | c %------------------------------------% @@ -820,10 +820,10 @@ subroutine dsaitr ido = 99 c if (msglvl .gt. 1) then - call dvout (logfil, k+np, h(1,2), ndigit, + call dvout (logfil, k+np, h(1,2), ndigit, & '_saitr: main diagonal of matrix H of step K+NP.') if (k+np .gt. 1) then - call dvout (logfil, k+np-1, h(2,1), ndigit, + call dvout (logfil, k+np-1, h(2,1), ndigit, & '_saitr: sub diagonal of matrix H of step K+NP.') end if end if @@ -836,7 +836,7 @@ subroutine dsaitr c %--------------------------------------------------------% c go to 1000 -c +c c %---------------------------------------------------------------% c | | c | E N D O F M A I N I T E R A T I O N L O O P | diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsapps.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsapps.f index bfdba9e4d214..f84ef838976b 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsapps.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsapps.f @@ -12,8 +12,8 @@ c c A*(V_{k}*Q) - (V_{k}*Q)*(Q^T* H_{k}*Q) = r_{k+p}*e_{k+p}^T * Q c -c where Q is an orthogonal matrix of order KEV+NP. Q is the product of -c rotations resulting from the NP bulge chasing sweeps. The updated Arnoldi +c where Q is an orthogonal matrix of order KEV+NP. Q is the product of +c rotations resulting from the NP bulge chasing sweeps. The updated Arnoldi c factorization becomes: c c A*VNEW_{k} - VNEW_{k}*HNEW_{k} = rnew_{k}*e_{k}^T. @@ -49,7 +49,7 @@ c INPUT: H contains the symmetric tridiagonal matrix of the c Arnoldi factorization with the subdiagonal in the 1st column c starting at H(2,1) and the main diagonal in the 2nd column. -c OUTPUT: H contains the updated tridiagonal matrix in the +c OUTPUT: H contains the updated tridiagonal matrix in the c KEV leading submatrix. c c LDH Integer. (INPUT) @@ -85,12 +85,12 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c c\Routines called: -c ivout ARPACK utility routine that prints integers. +c ivout ARPACK utility routine that prints integers. c arscnd ARPACK utility routine for timing. c dvout ARPACK utility routine that prints vectors. c dlamch LAPACK routine that determines machine constants. @@ -107,19 +107,19 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c 12/16/93: Version ' 2.4' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sapps.F SID: 2.6 DATE OF SID: 3/28/97 RELEASE: 2 c c\Remarks c 1. In this version, each shift is applied to all the subblocks of -c the tridiagonal matrix H and not just to the submatrix that it -c comes from. This routine assumes that the subdiagonal elements +c the tridiagonal matrix H and not just to the submatrix that it +c comes from. This routine assumes that the subdiagonal elements c of H that are stored in h(1:kev+np,1) are nonegative upon input c and enforce this condition upon output. This version incorporates c deflation. See code for documentation. @@ -149,7 +149,7 @@ subroutine dsapps c %-----------------% c Double precision - & h(ldh,2), q(ldq,kev+np), resid(n), shift(np), + & h(ldh,2), q(ldq,kev+np), resid(n), shift(np), & v(ldv,kev+np), workd(2*n) c c %------------% @@ -175,7 +175,7 @@ subroutine dsapps c | External Subroutines | c %----------------------% c - external daxpy, dcopy, dscal, dlacpy, dlartg, dlaset, dvout, + external daxpy, dcopy, dscal, dlacpy, dlartg, dlaset, dvout, & ivout, arscnd, dgemv c c %--------------------% @@ -193,7 +193,7 @@ subroutine dsapps intrinsic abs c c %----------------% -c | Data statments | +c | Data statements | c %----------------% c data first / .true. / @@ -215,9 +215,9 @@ subroutine dsapps c call arscnd (t0) msglvl = msapps -c - kplusp = kev + np -c +c + kplusp = kev + np +c c %----------------------------------------------% c | Initialize Q to the identity matrix of order | c | kplusp used to accumulate the rotations. | @@ -230,7 +230,7 @@ subroutine dsapps c %----------------------------------------------% c if (np .eq. 0) go to 9000 -c +c c %----------------------------------------------------------% c | Apply the np shifts implicitly. Apply each shift to the | c | whole matrix and not just to the submatrix from which it | @@ -238,7 +238,7 @@ subroutine dsapps c %----------------------------------------------------------% c do 90 jj = 1, np -c +c istart = itop c c %----------------------------------------------------------% @@ -261,11 +261,11 @@ subroutine dsapps big = abs(h(i,2)) + abs(h(i+1,2)) if (h(i+1,1) .le. epsmch*big) then if (msglvl .gt. 0) then - call ivout (logfil, 1, i, ndigit, + call ivout (logfil, 1, [i], ndigit, & '_sapps: deflation at row/column no.') - call ivout (logfil, 1, jj, ndigit, - & '_sapps: occured before shift number.') - call dvout (logfil, 1, h(i+1,1), ndigit, + call ivout (logfil, 1, [jj], ndigit, + & '_sapps: occurred before shift number.') + call dvout (logfil, 1, h(i+1,1), ndigit, & '_sapps: the corresponding off diagonal element') end if h(i+1,1) = zero @@ -277,7 +277,7 @@ subroutine dsapps 40 continue c if (istart .lt. iend) then -c +c c %--------------------------------------------------------% c | Construct the plane rotation G'(istart,istart+1,theta) | c | that attempts to drive h(istart+1,1) to zero. | @@ -286,7 +286,7 @@ subroutine dsapps f = h(istart,2) - shift(jj) g = h(istart+1,1) call dlartg (f, g, c, s, r) -c +c c %-------------------------------------------------------% c | Apply rotation to the left and right of H; | c | H <- G' * H * G, where G = G(istart,istart+1,theta). | @@ -296,11 +296,11 @@ subroutine dsapps a1 = c*h(istart,2) + s*h(istart+1,1) a2 = c*h(istart+1,1) + s*h(istart+1,2) a4 = c*h(istart+1,2) - s*h(istart+1,1) - a3 = c*h(istart+1,1) - s*h(istart,2) + a3 = c*h(istart+1,1) - s*h(istart,2) h(istart,2) = c*a1 + s*a2 h(istart+1,2) = c*a4 - s*a3 h(istart+1,1) = c*a3 + s*a4 -c +c c %----------------------------------------------------% c | Accumulate the rotation in the matrix Q; Q <- Q*G | c %----------------------------------------------------% @@ -323,7 +323,7 @@ subroutine dsapps c %----------------------------------------------% c do 70 i = istart+1, iend-1 -c +c c %----------------------------------------------% c | Construct the plane rotation G'(i,i+1,theta) | c | that zeros the i-th bulge that was created | @@ -351,23 +351,23 @@ subroutine dsapps c = -c s = -s end if -c +c c %--------------------------------------------% c | Apply rotation to the left and right of H; | c | H <- G * H * G', where G = G(i,i+1,theta) | c %--------------------------------------------% c h(i,1) = r -c +c a1 = c*h(i,2) + s*h(i+1,1) a2 = c*h(i+1,1) + s*h(i+1,2) a3 = c*h(i+1,1) - s*h(i,2) a4 = c*h(i+1,2) - s*h(i+1,1) -c +c h(i,2) = c*a1 + s*a2 h(i+1,2) = c*a4 - s*a3 h(i+1,1) = c*a3 + s*a4 -c +c c %----------------------------------------------------% c | Accumulate the rotation in the matrix Q; Q <- Q*G | c %----------------------------------------------------% @@ -425,16 +425,16 @@ subroutine dsapps c %------------------------------------------% c | All shifts have been applied. Check for | c | more possible deflation that might occur | -c | after the last shift is applied. | +c | after the last shift is applied. | c %------------------------------------------% c do 100 i = itop, kplusp-1 big = abs(h(i,2)) + abs(h(i+1,2)) if (h(i+1,1) .le. epsmch*big) then if (msglvl .gt. 0) then - call ivout (logfil, 1, i, ndigit, + call ivout (logfil, 1, [i], ndigit, & '_sapps: deflation at row/column no.') - call dvout (logfil, 1, h(i+1,1), ndigit, + call dvout (logfil, 1, h(i+1,1), ndigit, & '_sapps: the corresponding off diagonal element') end if h(i+1,1) = zero @@ -447,13 +447,13 @@ subroutine dsapps c | This is not necessary if h(kev+1,1) = 0. | c %-------------------------------------------------% c - if ( h(kev+1,1) .gt. zero ) + if ( h(kev+1,1) .gt. zero ) & call dgemv ('N', n, kplusp, one, v, ldv, & q(1,kev+1), 1, zero, workd(n+1), 1) -c +c c %-------------------------------------------------------% c | Compute column 1 to kev of (V*Q) in backward order | -c | taking advantage that Q is an upper triangular matrix | +c | taking advantage that Q is an upper triangular matrix | c | with lower bandwidth np. | c | Place results in v(:,kplusp-kev:kplusp) temporarily. | c %-------------------------------------------------------% @@ -468,16 +468,18 @@ subroutine dsapps c | Move v(:,kplusp-kev+1:kplusp) into v(:,1:kev). | c %-------------------------------------------------% c - call dlacpy ('All', n, kev, v(1,np+1), ldv, v, ldv) -c + do 140 i = 1, kev + call dcopy (n, v(1,np+i), 1, v(1,i), 1) + 140 continue +c c %--------------------------------------------% c | Copy the (kev+1)-st column of (V*Q) in the | c | appropriate place if h(kev+1,1) .ne. zero. | c %--------------------------------------------% c - if ( h(kev+1,1) .gt. zero ) + if ( h(kev+1,1) .gt. zero ) & call dcopy (n, workd(n+1), 1, v(1,kev+1), 1) -c +c c %-------------------------------------% c | Update the residual vector: | c | r <- sigmak*r + betak*v(:,kev+1) | @@ -487,26 +489,26 @@ subroutine dsapps c %-------------------------------------% c call dscal (n, q(kplusp,kev), resid, 1) - if (h(kev+1,1) .gt. zero) + if (h(kev+1,1) .gt. zero) & call daxpy (n, h(kev+1,1), v(1,kev+1), 1, resid, 1) c if (msglvl .gt. 1) then - call dvout (logfil, 1, q(kplusp,kev), ndigit, + call dvout (logfil, 1, q(kplusp,kev), ndigit, & '_sapps: sigmak of the updated residual vector') - call dvout (logfil, 1, h(kev+1,1), ndigit, + call dvout (logfil, 1, h(kev+1,1), ndigit, & '_sapps: betak of the updated residual vector') - call dvout (logfil, kev, h(1,2), ndigit, + call dvout (logfil, kev, h(1,2), ndigit, & '_sapps: updated main diagonal of H for next iteration') if (kev .gt. 1) then - call dvout (logfil, kev-1, h(2,1), ndigit, + call dvout (logfil, kev-1, h(2,1), ndigit, & '_sapps: updated sub diagonal of H for next iteration') end if end if c call arscnd (t1) tsapps = tsapps + (t1 - t0) -c - 9000 continue +c + 9000 continue return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaup2.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaup2.f index 12cc2e779c37..fd4143f537e6 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaup2.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaup2.f @@ -3,35 +3,35 @@ c c\Name: dsaup2 c -c\Description: +c\Description: c Intermediate level interface called by dsaupd. c c\Usage: -c call dsaup2 +c call dsaup2 c ( IDO, BMAT, N, WHICH, NEV, NP, TOL, RESID, MODE, IUPD, -c ISHIFT, MXITER, V, LDV, H, LDH, RITZ, BOUNDS, Q, LDQ, WORKL, +c ISHIFT, MXITER, V, LDV, H, LDH, RITZ, BOUNDS, Q, LDQ, WORKL, c IPNTR, WORKD, INFO ) c c\Arguments c c IDO, BMAT, N, WHICH, NEV, TOL, RESID: same as defined in dsaupd. c MODE, ISHIFT, MXITER: see the definition of IPARAM in dsaupd. -c +c c NP Integer. (INPUT/OUTPUT) -c Contains the number of implicit shifts to apply during -c each Arnoldi/Lanczos iteration. -c If ISHIFT=1, NP is adjusted dynamically at each iteration +c Contains the number of implicit shifts to apply during +c each Arnoldi/Lanczos iteration. +c If ISHIFT=1, NP is adjusted dynamically at each iteration c to accelerate convergence and prevent stagnation. -c This is also roughly equal to the number of matrix-vector +c This is also roughly equal to the number of matrix-vector c products (involving the operator OP) per Arnoldi iteration. c The logic for adjusting is contained within the current c subroutine. c If ISHIFT=0, NP is the number of shifts the user needs -c to provide via reverse comunication. 0 < NP < NCV-NEV. +c to provide via reverse communication. 0 < NP < NCV-NEV. c NP may be less than NCV-NEV since a leading block of the current c upper Tridiagonal matrix has split off and contains "unwanted" c Ritz values. -c Upon termination of the IRA iteration, NP contains the number +c Upon termination of the IRA iteration, NP contains the number c of "converged" wanted Ritz values. c c IUPD Integer. (INPUT) @@ -42,18 +42,18 @@ c The Lanczos basis vectors. c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c H Double precision (NEV+NP) by 2 array. (OUTPUT) c H is used to store the generated symmetric tridiagonal matrix -c The subdiagonal is stored in the first column of H starting +c The subdiagonal is stored in the first column of H starting c at H(2,1). The main diagonal is stored in the arscnd column -c of H starting at H(1,2). If dsaup2 converges store the +c of H starting at H(1,2). If dsaup2 converges store the c B-norm of the final residual vector in H(1,1). c c LDH Integer. (INPUT) -c Leading dimension of H exactly as declared in the calling +c Leading dimension of H exactly as declared in the calling c program. c c RITZ Double precision array of length NEV+NP. (OUTPUT) @@ -63,33 +63,33 @@ c BOUNDS(1:NEV) contain the error bounds corresponding to RITZ. c c Q Double precision (NEV+NP) by (NEV+NP) array. (WORKSPACE) -c Private (replicated) work array used to accumulate the +c Private (replicated) work array used to accumulate the c rotation in the shift application step. c c LDQ Integer. (INPUT) c Leading dimension of Q exactly as declared in the calling c program. -c +c c WORKL Double precision array of length at least 3*(NEV+NP). (INPUT/WORKSPACE) c Private (replicated) array on each PE or array allocated on -c the front end. It is used in the computation of the +c the front end. It is used in the computation of the c tridiagonal eigenvalue problem, the calculation and c application of the shifts and convergence checking. c If ISHIFT .EQ. O and IDO .EQ. 3, the first NP locations -c of WORKL are used in reverse communication to hold the user +c of WORKL are used in reverse communication to hold the user c supplied shifts. c c IPNTR Integer array of length 3. (OUTPUT) -c Pointer to mark the starting locations in the WORKD for +c Pointer to mark the starting locations in the WORKD for c vectors used by the Lanczos iteration. c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X. c IPNTR(2): pointer to the current result vector Y. -c IPNTR(3): pointer to the vector B * X when used in one of +c IPNTR(3): pointer to the vector B * X when used in one of c the spectral transformation modes. X is the current c operand. c ------------------------------------------------------------- -c +c c WORKD Double precision work array of length 3*N. (REVERSE COMMUNICATION) c Distributed array to be used in the basic Lanczos iteration c for reverse communication. The user should not use WORKD @@ -102,9 +102,9 @@ c possibly from a previous run. c Error flag on output. c = 0: Normal return. -c = 1: All possible eigenvalues of OP has been found. +c = 1: All possible eigenvalues of OP has been found. c NP returns the size of the invariant subspace -c spanning the operator OP. +c spanning the operator OP. c = 2: No shifts could be applied. c = -8: Error return from trid. eigenvalue calculation; c This should never happen. @@ -122,7 +122,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c 3. B.N. Parlett, "The Symmetric Eigenvalue Problem". Prentice-Hall, @@ -132,15 +132,15 @@ c 5. B. Nour-Omid, B.N. Parlett, T. Ericson, P.S. Jensen, "How to c Implement the Spectral Transformation", Math. Comp., 48 (1987), c pp 663-673. -c 6. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos -c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", +c 6. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos +c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", c SIAM J. Matr. Anal. Apps., January (1993). c 7. L. Reichel, W.B. Gragg, "Algorithm 686: FORTRAN Subroutines c for Updating the QR decomposition", ACM TOMS, December 1990, c Volume 16 Number 4, pp 369-377. c c\Routines called: -c dgetv0 ARPACK initial vector generation routine. +c dgetv0 ARPACK initial vector generation routine. c dsaitr ARPACK Lanczos factorization routine. c dsapps ARPACK application of implicit shifts routine. c dsconv ARPACK convergence of Ritz values routine. @@ -152,7 +152,7 @@ c dvout ARPACK utility routine that prints vectors. c dlamch LAPACK routine that determines machine constants. c dcopy Level 1 BLAS that copies one vector to another. -c ddot Level 1 BLAS that computes the scalar product of two vectors. +c ddot Level 1 BLAS that computes the scalar product of two vectors. c dnrm2 Level 1 BLAS that computes the norm of a vector. c dscal Level 1 BLAS that scales a vector. c dswap Level 1 BLAS that swaps two vectors. @@ -162,14 +162,14 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas -c +c Rice University +c Houston, Texas +c c\Revision history: c 12/15/93: Version ' 2.4' c xx/xx/95: Version ' 2.4'. (R.B. Lehoucq) c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: saup2.F SID: 2.7 DATE OF SID: 5/19/98 RELEASE: 2 c c\EndLib @@ -177,8 +177,8 @@ c----------------------------------------------------------------------- c subroutine dsaup2 - & ( ido, bmat, n, which, nev, np, tol, resid, mode, iupd, - & ishift, mxiter, v, ldv, h, ldh, ritz, bounds, + & ( ido, bmat, n, which, nev, np, tol, resid, mode, iupd, + & ishift, mxiter, v, ldv, h, ldh, ritz, bounds, & q, ldq, workl, ipntr, workd, info ) c c %----------------------------------------------------% @@ -204,8 +204,8 @@ subroutine dsaup2 c integer ipntr(3) Double precision - & bounds(nev+np), h(ldh,2), q(ldq,nev+np), resid(n), - & ritz(nev+np), v(ldv,nev+np), workd(3*n), + & bounds(nev+np), h(ldh,2), q(ldq,nev+np), resid(n), + & ritz(nev+np), v(ldv,nev+np), workd(3*n), & workl(3*(nev+np)) c c %------------% @@ -222,8 +222,8 @@ subroutine dsaup2 c character wprime*2 logical cnorm, getv0, initv, update, ushift - integer ierr, iter, j, kplusp, msglvl, nconv, nevbef, nev0, - & np0, nptemp, nevd2, nevm2, kp(3) + integer ierr, iter, j, kplusp, msglvl, nconv, nevbef, nev0, + & np0, nptemp, nevd2, nevm2, kp(3) Double precision & rnorm, temp, eps23 save cnorm, getv0, initv, update, ushift, @@ -234,7 +234,7 @@ subroutine dsaup2 c | External Subroutines | c %----------------------% c - external dcopy, dgetv0, dsaitr, dscal, dsconv, dseigt, dsgets, + external dcopy, dgetv0, dsaitr, dscal, dsconv, dseigt, dsgets, & dsapps, dsortr, dvout, ivout, arscnd, dswap c c %--------------------% @@ -256,7 +256,7 @@ subroutine dsaup2 c %-----------------------% c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -292,7 +292,7 @@ subroutine dsaup2 kplusp = nev0 + np0 nconv = 0 iter = 0 -c +c c %--------------------------------------------% c | Set flags for computing the first NEV steps | c | of the Lanczos factorization. | @@ -315,7 +315,7 @@ subroutine dsaup2 initv = .false. end if end if -c +c c %---------------------------------------------% c | Get a possibly random starting vector and | c | force it into the range of the operator OP. | @@ -332,7 +332,7 @@ subroutine dsaup2 if (rnorm .eq. zero) then c c %-----------------------------------------% -c | The initial vector is zero. Error exit. | +c | The initial vector is zero. Error exit. | c %-----------------------------------------% c info = -9 @@ -341,7 +341,7 @@ subroutine dsaup2 getv0 = .false. ido = 0 end if -c +c c %------------------------------------------------------------% c | Back from reverse communication: continue with update step | c %------------------------------------------------------------% @@ -360,14 +360,14 @@ subroutine dsaup2 c %-------------------------------------% c if (cnorm) go to 100 -c +c c %----------------------------------------------------------% c | Compute the first NEV steps of the Lanczos factorization | c %----------------------------------------------------------% c - call dsaitr (ido, bmat, n, 0, nev0, mode, resid, rnorm, v, ldv, + call dsaitr (ido, bmat, n, 0, nev0, mode, resid, rnorm, v, ldv, & h, ldh, ipntr, workd, info) -c +c c %---------------------------------------------------% c | ido .ne. 99 implies use of reverse communication | c | to compute operations involving OP and possibly B | @@ -388,7 +388,7 @@ subroutine dsaup2 info = -9999 go to 1200 end if -c +c c %--------------------------------------------------------------% c | | c | M A I N LANCZOS I T E R A T I O N L O O P | @@ -396,22 +396,22 @@ subroutine dsaup2 c | factorization in place. | c | | c %--------------------------------------------------------------% -c +c 1000 continue c iter = iter + 1 c if (msglvl .gt. 0) then - call ivout (logfil, 1, iter, ndigit, + call ivout (logfil, 1, [iter], ndigit, & '_saup2: **** Start of major iteration number ****') end if if (msglvl .gt. 1) then - call ivout (logfil, 1, nev, ndigit, + call ivout (logfil, 1, [nev], ndigit, & '_saup2: The length of the current Lanczos factorization') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_saup2: Extend the Lanczos factorization by') end if -c +c c %------------------------------------------------------------% c | Compute NP additional steps of the Lanczos factorization. | c %------------------------------------------------------------% @@ -420,9 +420,9 @@ subroutine dsaup2 20 continue update = .true. c - call dsaitr (ido, bmat, n, nev, np, mode, resid, rnorm, v, + call dsaitr (ido, bmat, n, nev, np, mode, resid, rnorm, v, & ldv, h, ldh, ipntr, workd, info) -c +c c %---------------------------------------------------% c | ido .ne. 99 implies use of reverse communication | c | to compute operations involving OP and possibly B | @@ -434,7 +434,7 @@ subroutine dsaup2 c c %-----------------------------------------------------% c | dsaitr was unable to build an Lanczos factorization | -c | of length NEV0+NP0. INFO is returned with the size | +c | of length NEV0+NP0. INFO is returned with the size | c | of the factorization built. Exit main loop. | c %-----------------------------------------------------% c @@ -446,10 +446,10 @@ subroutine dsaup2 update = .false. c if (msglvl .gt. 1) then - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_saup2: Current B-norm of residual for factorization') end if -c +c c %--------------------------------------------------------% c | Compute the eigenvalues and corresponding error bounds | c | of the current symmetric tridiagonal matrix. | @@ -483,7 +483,7 @@ subroutine dsaup2 nev = nev0 np = np0 call dsgets (ishift, which, nev, np, ritz, bounds, workl) -c +c c %-------------------% c | Convergence test. | c %-------------------% @@ -520,11 +520,11 @@ subroutine dsaup2 nev = nev + 1 end if 30 continue -c - if ( (nconv .ge. nev0) .or. +c + if ( (nconv .ge. nev0) .or. & (iter .gt. mxiter) .or. & (np .eq. 0) ) then -c +c c %------------------------------------------------% c | Prepare to exit. Put the converged Ritz values | c | and corresponding bounds in RITZ(1:NCONV) and | @@ -547,7 +547,7 @@ subroutine dsaup2 wprime = 'SA' call dsortr (wprime, .true., kplusp, ritz, bounds) nevd2 = nev0 / 2 - nevm2 = nev0 - nevd2 + nevm2 = nev0 - nevd2 if ( nev .gt. 1 ) then np = kplusp - nev0 call dswap ( min(nevd2,np), ritz(nevm2+1), 1, @@ -588,7 +588,7 @@ subroutine dsaup2 c c %----------------------------------------------------% c | Sort the Ritz values according to the scaled Ritz | -c | esitmates. This will push all the converged ones | +c | estimates. This will push all the converged ones | c | towards the front of ritzr, ritzi, bounds | c | (in the case when NCONV < NEV.) | c %----------------------------------------------------% @@ -652,13 +652,13 @@ subroutine dsaup2 end if c c %------------------------------------% -c | Max iterations have been exceeded. | +c | Max iterations have been exceeded. | c %------------------------------------% c if (iter .gt. mxiter .and. nconv .lt. nev) info = 1 c c %---------------------% -c | No shifts to apply. | +c | No shifts to apply. | c %---------------------% c if (np .eq. 0 .and. nconv .lt. nev0) info = 2 @@ -682,20 +682,20 @@ subroutine dsaup2 nev = 2 end if np = kplusp - nev -c +c c %---------------------------------------% c | If the size of NEV was just increased | c | resort the eigenvalues. | c %---------------------------------------% -c - if (nevbef .lt. nev) +c + if (nevbef .lt. nev) & call dsgets (ishift, which, nev, np, ritz, bounds, & workl) c end if c if (msglvl .gt. 0) then - call ivout (logfil, 1, nconv, ndigit, + call ivout (logfil, 1, [nconv], ndigit, & '_saup2: no. of "converged" Ritz values at this iter.') if (msglvl .gt. 1) then kp(1) = nev @@ -709,7 +709,7 @@ subroutine dsaup2 end if end if -c +c if (ishift .eq. 0) then c c %-----------------------------------------------------% @@ -732,8 +732,8 @@ subroutine dsaup2 c %------------------------------------% c ushift = .false. -c -c +c +c c %---------------------------------------------------------% c | Move the NP shifts to the first NP locations of RITZ to | c | free up WORKL. This is for the non-exact shift case; | @@ -743,7 +743,7 @@ subroutine dsaup2 if (ishift .eq. 0) call dcopy (np, workl, 1, ritz, 1) c if (msglvl .gt. 2) then - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_saup2: The number of shifts to apply ') call dvout (logfil, np, workl, ndigit, & '_saup2: shifts selected') @@ -752,7 +752,7 @@ subroutine dsaup2 & '_saup2: corresponding Ritz estimates') end if end if -c +c c %---------------------------------------------------------% c | Apply the NP0 implicit shifts by QR bulge chasing. | c | Each shift is applied to the entire tridiagonal matrix. | @@ -778,18 +778,18 @@ subroutine dsaup2 ipntr(1) = n + 1 ipntr(2) = 1 ido = 2 -c +c c %----------------------------------% c | Exit in order to compute B*RESID | c %----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call dcopy (n, resid, 1, workd, 1) end if -c +c 100 continue -c +c c %----------------------------------% c | Back from reverse communication; | c | WORKD(1:N) := B*RESID | @@ -799,8 +799,8 @@ subroutine dsaup2 call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c - if (bmat .eq. 'G') then +c + if (bmat .eq. 'G') then rnorm = ddot (n, resid, 1, workd, 1) rnorm = sqrt(abs(rnorm)) else if (bmat .eq. 'I') then @@ -810,14 +810,14 @@ subroutine dsaup2 130 continue c if (msglvl .gt. 2) then - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_saup2: B-norm of residual for NEV factorization') call dvout (logfil, nev, h(1,2), ndigit, & '_saup2: main diagonal of compressed H matrix') call dvout (logfil, nev-1, h(2,1), ndigit, & '_saup2: subdiagonal of compressed H matrix') end if -c +c go to 1000 c c %---------------------------------------------------------------% @@ -825,12 +825,12 @@ subroutine dsaup2 c | E N D O F M A I N I T E R A T I O N L O O P | c | | c %---------------------------------------------------------------% -c +c 1100 continue c mxiter = iter nev = nconv -c +c 1200 continue ido = 99 c @@ -840,7 +840,7 @@ subroutine dsaup2 c call arscnd (t1) tsaup2 = t1 - t0 -c +c 9000 continue return c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaupd.f index 1fbc14cd1cf8..81a0ce52cc82 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsaupd.f @@ -267,9 +267,9 @@ c Informatinal error from LAPACK routine dsteqr . c = -9: Starting vector is zero. c = -10: IPARAM(7) must be 1,2,3,4,5. -c = -11: IPARAM(7) = 1 and BMAT = 'G' are incompatable. +c = -11: IPARAM(7) = 1 and BMAT = 'G' are incompatible. c = -12: IPARAM(1) must be equal to 0 or 1. -c = -13: NEV and WHICH = 'BE' are incompatable. +c = -13: NEV and WHICH = 'BE' are incompatible. c = -9999: Could not build an Arnoldi factorization. c IPARAM(5) returns the size of the current Arnoldi c factorization. The user is advised to check that @@ -297,7 +297,7 @@ c L`z = x where x is a Ritz vector of OP. c c 4. At present there is no a-priori analysis to guide the selection -c of NCV relative to NEV. The only formal requrement is that NCV > NEV. +c of NCV relative to NEV. The only formal requirement is that NCV > NEV. c However, it is recommended that NCV .ge. 2*NEV. If many problems of c the same type are to be solved, one should experiment with increasing c NCV while keeping NEV fixed for a given test problem. This will @@ -306,7 +306,7 @@ c basis vectors. The optimal "cross-over" with respect to CPU time c is problem dependent and must be determined empirically. c -c 5. If IPARAM(7) = 2 then in the Reverse commuication interface the user +c 5. If IPARAM(7) = 2 then in the Reverse communication interface the user c must do the following. When IDO = 1, Y = OP * X is to be computed. c When IPARAM(7) = 2 OP = inv(B)*A. After computing A*X the user c must overwrite X with A*X. Y is then the solution to the linear set @@ -628,9 +628,9 @@ subroutine dsaupd if (info .eq. 2) info = 3 c if (msglvl .gt. 0) then - call ivout (logfil, 1, mxiter, ndigit, + call ivout (logfil, 1, [mxiter], ndigit, & '_saupd: number of update iterations taken') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_saupd: number of "converged" Ritz values') call dvout (logfil, np, workl(Ritz), ndigit, & '_saupd: final Ritz values') diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsconv.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsconv.f index f31dee704cf8..82dbb6e611d3 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsconv.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsconv.f @@ -3,7 +3,7 @@ c c\Name: dsconv c -c\Description: +c\Description: c Convergence testing for the symmetric Arnoldi eigenvalue routine. c c\Usage: @@ -35,22 +35,22 @@ c c\Routines called: c arscnd ARPACK utility routine for timing. -c dlamch LAPACK routine that determines machine constants. +c dlamch LAPACK routine that determines machine constants. c c\Author c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University -c Dept. of Computational & Houston, Texas +c Richard Lehoucq CRPC / Rice University +c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sconv.F SID: 2.4 DATE OF SID: 4/19/96 RELEASE: 2 c c\Remarks c 1. Starting with version 2.4, this routine no longer uses the -c Parlett strategy using the gap conditions. +c Parlett strategy using the gap conditions. c c\EndLib c @@ -108,7 +108,7 @@ subroutine dsconv (n, ritz, bounds, tol, nconv) c call arscnd (t0) c - eps23 = dlamch('Epsilon-Machine') + eps23 = dlamch('Epsilon-Machine') eps23 = eps23**(2.0D+0 / 3.0D+0) c nconv = 0 @@ -125,10 +125,10 @@ subroutine dsconv (n, ritz, bounds, tol, nconv) end if c 10 continue -c +c call arscnd (t1) tsconv = tsconv + (t1 - t0) -c +c return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dseigt.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dseigt.f index cb78a894acf3..5e20c805bf1c 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dseigt.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dseigt.f @@ -3,7 +3,7 @@ c c\Name: dseigt c -c\Description: +c\Description: c Compute the eigenvalues of the current symmetric tridiagonal matrix c and the corresponding error bounds given the current residual norm. c @@ -20,16 +20,16 @@ c Size of the symmetric tridiagonal matrix H. c c H Double precision N by 2 array. (INPUT) -c H contains the symmetric tridiagonal matrix with the -c subdiagonal in the first column starting at H(2,1) and the +c H contains the symmetric tridiagonal matrix with the +c subdiagonal in the first column starting at H(2,1) and the c main diagonal in second column. c c LDH Integer. (INPUT) -c Leading dimension of H exactly as declared in the calling +c Leading dimension of H exactly as declared in the calling c program. c c EIG Double precision array of length N. (OUTPUT) -c On output, EIG contains the N eigenvalues of H possibly +c On output, EIG contains the N eigenvalues of H possibly c unsorted. The BOUNDS arrays are returned in the c same sorted order as EIG. c @@ -65,16 +65,16 @@ c c\Author c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University -c Dept. of Computational & Houston, Texas +c Richard Lehoucq CRPC / Rice University +c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.4' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: seigt.F SID: 2.4 DATE OF SID: 8/27/96 RELEASE: 2 c c\Remarks @@ -84,7 +84,7 @@ c c----------------------------------------------------------------------- c - subroutine dseigt + subroutine dseigt & ( rnorm, n, h, ldh, eig, bounds, workl, ierr ) c c %----------------------------------------------------% @@ -136,7 +136,7 @@ subroutine dseigt c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | -c %-------------------------------% +c %-------------------------------% c call arscnd (t0) msglvl = mseigt @@ -167,7 +167,7 @@ subroutine dseigt do 30 k = 1, n bounds(k) = rnorm*abs(bounds(k)) 30 continue -c +c call arscnd (t1) tseigt = tseigt + (t1 - t0) c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsesrt.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsesrt.f index 2b4ca8cbc03c..833fba4e6c54 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsesrt.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsesrt.f @@ -4,7 +4,7 @@ c\Name: dsesrt c c\Description: -c Sort the array X in the order specified by WHICH and optionally +c Sort the array X in the order specified by WHICH and optionally c apply the permutation to the columns of the matrix A. c c\Usage: @@ -32,7 +32,7 @@ c Number of rows of the matrix A. c c A Double precision array of length NA by N. (INPUT/OUTPUT) -c +c c LDA Integer. (INPUT) c Leading dimension of A. c @@ -47,18 +47,18 @@ c c\Authors c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University -c Dept. of Computational & Houston, Texas +c Richard Lehoucq CRPC / Rice University +c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c 12/15/93: Version ' 2.1'. -c Adapted from the sort routine in LANSO and +c Adapted from the sort routine in LANSO and c the ARPACK code dsortr c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sesrt.F SID: 2.3 DATE OF SID: 4/19/96 RELEASE: 2 c c\EndLib @@ -101,7 +101,7 @@ subroutine dsesrt (which, apply, n, x, na, a, lda) c %-----------------------% c igap = n / 2 -c +c if (which .eq. 'SA') then c c X is sorted into decreasing order of algebraic. @@ -165,7 +165,7 @@ subroutine dsesrt (which, apply, n, x, na, a, lda) 80 continue c if (j.lt.0) go to 90 -c +c if (x(j).gt.x(j+igap)) then temp = x(j) x(j) = x(j+igap) @@ -179,7 +179,7 @@ subroutine dsesrt (which, apply, n, x, na, a, lda) 90 continue igap = igap / 2 go to 70 -c +c else if (which .eq. 'LM') then c c X is sorted into increasing order of magnitude. diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dseupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dseupd.f index 4f5c5967bb19..ae123a207e61 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dseupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dseupd.f @@ -1,8 +1,8 @@ c\BeginDoc c -c\Name: dseupd +c\Name: dseupd c -c\Description: +c\Description: c c This subroutine returns the converged approximations to eigenvalues c of A*z = lambda*B*z and (optionally): @@ -15,22 +15,22 @@ c (3) Both. c c There is negligible additional cost to obtain eigenvectors. An orthonormal -c (Lanczos) basis is always computed. There is an additional storage cost -c of n*nev if both are requested (in this case a separate array Z must be +c (Lanczos) basis is always computed. There is an additional storage cost +c of n*nev if both are requested (in this case a separate array Z must be c supplied). c c These quantities are obtained from the Lanczos factorization computed c by DSAUPD for the linear operator OP prescribed by the MODE selection c (see IPARAM(7) in DSAUPD documentation.) DSAUPD must be called before -c this routine is called. These approximate eigenvalues and vectors are -c commonly called Ritz values and Ritz vectors respectively. They are -c referred to as such in the comments that follow. The computed orthonormal -c basis for the invariant subspace corresponding to these Ritz values is +c this routine is called. These approximate eigenvalues and vectors are +c commonly called Ritz values and Ritz vectors respectively. They are +c referred to as such in the comments that follow. The computed orthonormal +c basis for the invariant subspace corresponding to these Ritz values is c referred to as a Lanczos basis. c -c See documentation in the header of the subroutine DSAUPD for a definition -c of OP as well as other terms and the relation of computed Ritz values -c and vectors of OP with respect to the given problem A*z = lambda*B*z. +c See documentation in the header of the subroutine DSAUPD for a definition +c of OP as well as other terms and the relation of computed Ritz values +c and vectors of OP with respect to the given problem A*z = lambda*B*z. c c The approximate eigenvalues of the original problem are returned in c ascending algebraic order. The user may elect to call this routine @@ -39,19 +39,19 @@ c with a single call. c c\Usage: -c call dseupd +c call dseupd c ( RVEC, HOWMNY, SELECT, D, Z, LDZ, SIGMA, BMAT, N, WHICH, NEV, TOL, c RESID, NCV, V, LDV, IPARAM, IPNTR, WORKD, WORKL, LWORKL, INFO ) c -c RVEC LOGICAL (INPUT) -c Specifies whether Ritz vectors corresponding to the Ritz value +c RVEC LOGICAL (INPUT) +c Specifies whether Ritz vectors corresponding to the Ritz value c approximations to the eigenproblem A*z = lambda*B*z are computed. c c RVEC = .FALSE. Compute Ritz values only. c c RVEC = .TRUE. Compute Ritz vectors. c -c HOWMNY Character*1 (INPUT) +c HOWMNY Character*1 (INPUT) c Specifies how many Ritz vectors are wanted and the form of Z c the matrix of Ritz vectors. See remark 1 below. c = 'A': compute NEV Ritz vectors; @@ -61,7 +61,7 @@ c SELECT Logical array of dimension NCV. (INPUT/WORKSPACE) c If HOWMNY = 'S', SELECT specifies the Ritz vectors to be c computed. To select the Ritz vector corresponding to a -c Ritz value D(j), SELECT(j) must be set to .TRUE.. +c Ritz value D(j), SELECT(j) must be set to .TRUE.. c If HOWMNY = 'A' , SELECT is used as a workspace for c reordering the Ritz values. c @@ -70,8 +70,8 @@ c eigenvalues of A*z = lambda*B*z. The values are returned c in ascending order. If IPARAM(7) = 3,4,5 then D represents c the Ritz values of OP computed by dsaupd transformed to -c those of the original eigensystem A*z = lambda*B*z. If -c IPARAM(7) = 1,2 then the Ritz values of OP are the same +c those of the original eigensystem A*z = lambda*B*z. If +c IPARAM(7) = 1,2 then the Ritz values of OP are the same c as the those of A*z = lambda*B*z. c c Z Double precision N by NEV array if HOWMNY = 'A'. (OUTPUT) @@ -79,7 +79,7 @@ c eigensystem A*z = lambda*B*z corresponding to the Ritz c value approximations. c If RVEC = .FALSE. then Z is not referenced. -c NOTE: The array Z may be set equal to first NEV columns of the +c NOTE: The array Z may be set equal to first NEV columns of the c Arnoldi/Lanczos basis array V computed by DSAUPD . c c LDZ Integer. (INPUT) @@ -144,7 +144,7 @@ c = -17: DSEUPD got a different count of the number of converged c Ritz values than DSAUPD got. This indicates the user c probably made an error in passing data from DSAUPD to -c DSEUPD or that the data was modified before entering +c DSEUPD or that the data was modified before entering c DSEUPD . c c\BeginLib @@ -153,7 +153,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c 3. B.N. Parlett, "The Symmetric Eigenvalue Problem". Prentice-Hall, @@ -163,19 +163,19 @@ c 5. B. Nour-Omid, B.N. Parlett, T. Ericson, P.S. Jensen, "How to c Implement the Spectral Transformation", Math. Comp., 48 (1987), c pp 663-673. -c 6. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos -c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", +c 6. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos +c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", c SIAM J. Matr. Anal. Apps., January (1993). c 7. L. Reichel, W.B. Gragg, "Algorithm 686: FORTRAN Subroutines c for Updating the QR decomposition", ACM TOMS, December 1990, c Volume 16 Number 4, pp 369-377. c c\Remarks -c 1. The converged Ritz values are always returned in increasing +c 1. The converged Ritz values are always returned in increasing c (algebraic) order. c c 2. Currently only HOWMNY = 'A' is implemented. It is included at this -c stage for the user who wants to incorporate it. +c stage for the user who wants to incorporate it. c c\Routines called: c dsesrt ARPACK routine that sorts an array X, and applies the @@ -201,15 +201,15 @@ c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Chao Yang Houston, Texas -c Dept. of Computational & +c Dept. of Computational & c Applied Mathematics -c Rice University -c Houston, Texas -c +c Rice University +c Houston, Texas +c c\Revision history: c 12/15/93: Version ' 2.1' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: seupd.F SID: 2.11 DATE OF SID: 04/10/01 RELEASE: 2 c c\EndLib @@ -236,7 +236,7 @@ subroutine dseupd (rvec , howmny, select, d , character bmat, howmny, which*2 logical rvec integer info, ldz, ldv, lworkl, n, ncv, nev - Double precision + Double precision & sigma, tol c c %-----------------% @@ -245,7 +245,7 @@ subroutine dseupd (rvec , howmny, select, d , c integer iparam(7), ipntr(11) logical select(ncv) - Double precision + Double precision & d(nev) , resid(n) , v(ldv,ncv), & z(ldz, nev), workd(2*n), workl(lworkl) c @@ -253,7 +253,7 @@ subroutine dseupd (rvec , howmny, select, d , c | Parameters | c %------------% c - Double precision + Double precision & one, zero parameter (one = 1.0D+0 , zero = 0.0D+0 ) c @@ -267,7 +267,7 @@ subroutine dseupd (rvec , howmny, select, d , & ldq , mode , msglvl, nconv , next , & ritz , irz , ibd , np , ishift, & leftptr, rghtptr, numcnv, jj - Double precision + Double precision & bnorm2 , rnorm, temp, temp1, eps23 logical reord c @@ -275,16 +275,16 @@ subroutine dseupd (rvec , howmny, select, d , c | External Subroutines | c %----------------------% c - external dcopy , dger , dgeqr2 , dlacpy , dorm2r , dscal , - & dsesrt , dsteqr , dswap , dvout , ivout , dsortr + external dcopy , dger , dgeqr2 , dlacpy , dorm2r , dscal , + & dsesrt , dsteqr , dswap , dvout , ivout , dsortr c c %--------------------% c | External Functions | c %--------------------% c - Double precision - & dnrm2 , dlamch - external dnrm2 , dlamch + Double precision + & dnrm2 , dlamch + external dnrm2 , dlamch c c %---------------------% c | Intrinsic Functions | @@ -295,7 +295,7 @@ subroutine dseupd (rvec , howmny, select, d , c %-----------------------% c | Executable Statements | c %-----------------------% -c +c c %------------------------% c | Set default parameters | c %------------------------% @@ -312,7 +312,7 @@ subroutine dseupd (rvec , howmny, select, d , if (nconv .eq. 0) go to 9000 ierr = 0 c - if (nconv .le. 0) ierr = -14 + if (nconv .le. 0) ierr = -14 if (n .le. 0) ierr = -1 if (nev .le. 0) ierr = -2 if (ncv .le. nev .or. ncv .gt. n) ierr = -3 @@ -324,12 +324,12 @@ subroutine dseupd (rvec , howmny, select, d , if (bmat .ne. 'I' .and. bmat .ne. 'G') ierr = -6 if ( (howmny .ne. 'A' .and. & howmny .ne. 'P' .and. - & howmny .ne. 'S') .and. rvec ) + & howmny .ne. 'S') .and. rvec ) & ierr = -15 if (rvec .and. howmny .eq. 'S') ierr = -16 c if (rvec .and. lworkl .lt. ncv**2+8*ncv) ierr = -7 -c +c if (mode .eq. 1 .or. mode .eq. 2) then type = 'REGULR' else if (mode .eq. 3 ) then @@ -338,7 +338,7 @@ subroutine dseupd (rvec , howmny, select, d , type = 'BUCKLE' else if (mode .eq. 5 ) then type = 'CAYLEY' - else + else ierr = -10 end if if (mode .eq. 1 .and. bmat .eq. 'G') ierr = -11 @@ -352,7 +352,7 @@ subroutine dseupd (rvec , howmny, select, d , info = ierr go to 9000 end if -c +c c %-------------------------------------------------------% c | Pointer into WORKL for address of H, RITZ, BOUNDS, Q | c | etc... and the remaining workspace. | @@ -427,7 +427,7 @@ subroutine dseupd (rvec , howmny, select, d , c | Set machine dependent constant. | c %---------------------------------% c - eps23 = dlamch ('Epsilon-Machine') + eps23 = dlamch ('Epsilon-Machine') eps23 = eps23**(2.0D+0 / 3.0D+0 ) c c %---------------------------------------% @@ -513,9 +513,9 @@ subroutine dseupd (rvec , howmny, select, d , c %-----------------------------------------------------------% c if (msglvl .gt. 2) then - call ivout(logfil, 1, numcnv, ndigit, + call ivout(logfil, 1, [numcnv], ndigit, & '_seupd: Number of specified eigenvalues') - call ivout(logfil, 1, nconv, ndigit, + call ivout(logfil, 1, [nconv], ndigit, & '_seupd: Number of "converged" eigenvalues') end if c @@ -652,8 +652,8 @@ subroutine dseupd (rvec , howmny, select, d , call dcopy (ncv, workl(bounds), 1, workl(ihb), 1) end if c - else -c + else +c c %-------------------------------------------------------------% c | * Make a copy of all the Ritz values. | c | * Transform the Ritz values back to the original system. | @@ -670,13 +670,13 @@ subroutine dseupd (rvec , howmny, select, d , c %-------------------------------------------------------------% c call dcopy (ncv, workl(ihd), 1, workl(iw), 1) - if (type .eq. 'SHIFTI') then + if (type .eq. 'SHIFTI') then do 40 k=1, ncv workl(ihd+k-1) = one / workl(ihd+k-1) + sigma 40 continue else if (type .eq. 'BUCKLE') then do 50 k=1, ncv - workl(ihd+k-1) = sigma * workl(ihd+k-1) / + workl(ihd+k-1) = sigma * workl(ihd+k-1) / & (workl(ihd+k-1) - one) 50 continue else if (type .eq. 'CAYLEY') then @@ -685,7 +685,7 @@ subroutine dseupd (rvec , howmny, select, d , & (workl(ihd+k-1) - one) 60 continue end if -c +c c %-------------------------------------------------------------% c | * Store the wanted NCONV lambda values into D. | c | * Sort the NCONV wanted lambda in WORKL(IHD:IHD+NCONV-1) | @@ -711,8 +711,8 @@ subroutine dseupd (rvec , howmny, select, d , call dsortr ('LA', .true., nconv, d, workl(ihb)) end if c - end if -c + end if +c c %------------------------------------------------% c | Compute the Ritz vectors. Transform the wanted | c | eigenvectors of the symmetric tridiagonal H by | @@ -720,25 +720,25 @@ subroutine dseupd (rvec , howmny, select, d , c %------------------------------------------------% c if (rvec .and. howmny .eq. 'A') then -c +c c %----------------------------------------------------------% c | Compute the QR factorization of the matrix representing | c | the wanted invariant subspace located in the first NCONV | c | columns of workl(iq,ldq). | c %----------------------------------------------------------% -c +c call dgeqr2 (ncv, nconv , workl(iq) , & ldq, workl(iw+ncv), workl(ihb), & ierr) c c %--------------------------------------------------------% -c | * Postmultiply V by Q. | +c | * Postmultiply V by Q. | c | * Copy the first NCONV columns of VQ into Z. | c | The N by NCONV matrix Z is now a matrix representation | c | of the approximate invariant subspace associated with | c | the Ritz values in workl(ihd). | c %--------------------------------------------------------% -c +c call dorm2r ('Right', 'Notranspose', n , & ncv , nconv , workl(iq), & ldq , workl(iw+ncv), v , @@ -752,7 +752,7 @@ subroutine dseupd (rvec , howmny, select, d , c %-----------------------------------------------------% c do 65 j = 1, ncv-1 - workl(ihb+j-1) = zero + workl(ihb+j-1) = zero 65 continue workl(ihb+ncv-1) = one call dorm2r ('Left', 'Transpose' , ncv , @@ -794,10 +794,10 @@ subroutine dseupd (rvec , howmny, select, d , c %-------------------------------------------------% c call dscal (ncv, bnorm2, workl(ihb), 1) - if (type .eq. 'SHIFTI') then + if (type .eq. 'SHIFTI') then c do 80 k=1, ncv - workl(ihb+k-1) = abs( workl(ihb+k-1) ) + workl(ihb+k-1) = abs( workl(ihb+k-1) ) & / workl(iw+k-1)**2 80 continue c @@ -822,15 +822,15 @@ subroutine dseupd (rvec , howmny, select, d , if (type .ne. 'REGULR' .and. msglvl .gt. 1) then call dvout (logfil, nconv, d, ndigit, & '_seupd: Untransformed converged Ritz values') - call dvout (logfil, nconv, workl(ihb), ndigit, + call dvout (logfil, nconv, workl(ihb), ndigit, & '_seupd: Ritz estimates of the untransformed Ritz values') else if (msglvl .gt. 1) then call dvout (logfil, nconv, d, ndigit, & '_seupd: Converged Ritz values') - call dvout (logfil, nconv, workl(ihb), ndigit, + call dvout (logfil, nconv, workl(ihb), ndigit, & '_seupd: Associated Ritz estimates') end if -c +c c %-------------------------------------------------% c | Ritz vector purification step. Formally perform | c | one of inverse subspace iteration. Only used | @@ -851,7 +851,7 @@ subroutine dseupd (rvec , howmny, select, d , & / (workl(iw+k)-one) 120 continue c - end if + end if c if (rvec .and. type .ne. 'REGULR') & call dger (n, nconv, one, resid, 1, workl(iw), 1, z, ldz) diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsgets.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsgets.f index 61129f907105..436a4fe848db 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsgets.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsgets.f @@ -3,13 +3,13 @@ c c\Name: dsgets c -c\Description: +c\Description: c Given the eigenvalues of the symmetric tridiagonal matrix H, -c computes the NP shifts AMU that are zeros of the polynomial of -c degree NP which filters out components of the unwanted eigenvectors +c computes the NP shifts AMU that are zeros of the polynomial of +c degree NP which filters out components of the unwanted eigenvectors c corresponding to the AMU's based on some given criteria. c -c NOTE: This is called even in the case of user specified shifts in +c NOTE: This is called even in the case of user specified shifts in c order to sort the eigenvalues, and error bounds of H for later use. c c\Usage: @@ -39,8 +39,8 @@ c c RITZ Double precision array of length KEV+NP. (INPUT/OUTPUT) c On INPUT, RITZ contains the eigenvalues of H. -c On OUTPUT, RITZ are sorted so that the unwanted eigenvalues -c are in the first NP locations and the wanted part is in +c On OUTPUT, RITZ are sorted so that the unwanted eigenvalues +c are in the first NP locations and the wanted part is in c the last KEV locations. When exact shifts are selected, the c unwanted part corresponds to the shifts to be applied. c @@ -49,7 +49,7 @@ c c SHIFTS Double precision array of length NP. (INPUT/OUTPUT) c On INPUT: contains the user specified shifts if ISHIFT = 0. -c On OUTPUT: contains the shifts sorted into decreasing order +c On OUTPUT: contains the shifts sorted into decreasing order c of magnitude with respect to the Ritz estimates contained in c BOUNDS. If ISHIFT = 0, SHIFTS is not modified on exit. c @@ -75,13 +75,13 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/93: Version ' 2.1' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sgets.F SID: 2.4 DATE OF SID: 4/19/96 RELEASE: 2 c c\Remarks @@ -142,7 +142,7 @@ subroutine dsgets ( ishift, which, kev, np, ritz, bounds, shifts ) c %-----------------------% c | Executable Statements | c %-----------------------% -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -150,7 +150,7 @@ subroutine dsgets ( ishift, which, kev, np, ritz, bounds, shifts ) c call arscnd (t0) msglvl = msgets -c +c if (which .eq. 'BE') then c c %-----------------------------------------------------% @@ -163,11 +163,11 @@ subroutine dsgets ( ishift, which, kev, np, ritz, bounds, shifts ) c %-----------------------------------------------------% c call dsortr ('LA', .true., kev+np, ritz, bounds) - kevd2 = kev / 2 + kevd2 = kev / 2 if ( kev .gt. 1 ) then - call dswap ( min(kevd2,np), ritz, 1, + call dswap ( min(kevd2,np), ritz, 1, & ritz( max(kevd2,np)+1 ), 1) - call dswap ( min(kevd2,np), bounds, 1, + call dswap ( min(kevd2,np), bounds, 1, & bounds( max(kevd2,np)+1 ), 1) end if c @@ -185,7 +185,7 @@ subroutine dsgets ( ishift, which, kev, np, ritz, bounds, shifts ) end if c if (ishift .eq. 1 .and. np .gt. 0) then -c +c c %-------------------------------------------------------% c | Sort the unwanted Ritz values used as shifts so that | c | the ones with largest Ritz estimates are first. | @@ -193,23 +193,23 @@ subroutine dsgets ( ishift, which, kev, np, ritz, bounds, shifts ) c | forward instability of the iteration when the shifts | c | are applied in subroutine dsapps. | c %-------------------------------------------------------% -c +c call dsortr ('SM', .true., np, bounds, ritz) call dcopy (np, ritz, 1, shifts, 1) end if -c +c call arscnd (t1) tsgets = tsgets + (t1 - t0) c if (msglvl .gt. 0) then - call ivout (logfil, 1, kev, ndigit, '_sgets: KEV is') - call ivout (logfil, 1, np, ndigit, '_sgets: NP is') + call ivout (logfil, 1, [kev], ndigit, '_sgets: KEV is') + call ivout (logfil, 1, [np], ndigit, '_sgets: NP is') call dvout (logfil, kev+np, ritz, ndigit, & '_sgets: Eigenvalues of current H matrix') - call dvout (logfil, kev+np, bounds, ndigit, + call dvout (logfil, kev+np, bounds, ndigit, & '_sgets: Associated Ritz estimates') end if -c +c return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsortc.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsortc.f index 91af30f8ae13..42baae2ba4d8 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsortc.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsortc.f @@ -4,7 +4,7 @@ c\Name: dsortc c c\Description: -c Sorts the complex array in XREAL and XIMAG into the order +c Sorts the complex array in XREAL and XIMAG into the order c specified by WHICH and optionally applies the permutation to the c real array Y. It is assumed that if an element of XIMAG is c nonzero, then its negative is also an element. In other words, @@ -49,14 +49,14 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.1' c Adapted from the sort routine in LANSO. c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sortc.F SID: 2.3 DATE OF SID: 4/20/96 RELEASE: 2 c c\EndLib @@ -77,7 +77,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) c | Array Arguments | c %-----------------% c - Double precision + Double precision & xreal(0:n-1), ximag(0:n-1), y(0:n-1) c c %---------------% @@ -85,14 +85,14 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) c %---------------% c integer i, igap, j - Double precision + Double precision & temp, temp1, temp2 c c %--------------------% c | External Functions | c %--------------------% c - Double precision + Double precision & dlapy2 external dlapy2 c @@ -101,7 +101,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) c %-----------------------% c igap = n / 2 -c +c if (which .eq. 'LM') then c c %------------------------------------------------------% @@ -169,7 +169,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) temp = ximag(j) ximag(j) = ximag(j+igap) ximag(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -183,7 +183,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) 60 continue igap = igap / 2 go to 40 -c +c else if (which .eq. 'LR') then c c %------------------------------------------------% @@ -207,7 +207,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) temp = ximag(j) ximag(j) = ximag(j+igap) ximag(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -221,7 +221,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) 90 continue igap = igap / 2 go to 70 -c +c else if (which .eq. 'SR') then c c %------------------------------------------------% @@ -244,7 +244,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) temp = ximag(j) ximag(j) = ximag(j+igap) ximag(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -258,7 +258,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) 120 continue igap = igap / 2 go to 100 -c +c else if (which .eq. 'LI') then c c %------------------------------------------------% @@ -281,7 +281,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) temp = ximag(j) ximag(j) = ximag(j+igap) ximag(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -295,7 +295,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) 150 continue igap = igap / 2 go to 130 -c +c else if (which .eq. 'SI') then c c %------------------------------------------------% @@ -318,7 +318,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) temp = ximag(j) ximag(j) = ximag(j+igap) ximag(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -333,7 +333,7 @@ subroutine dsortc (which, apply, n, xreal, ximag, y) igap = igap / 2 go to 160 end if -c +c 9000 continue return c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsortr.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsortr.f index 3903b81c5a9e..b44f916cf21c 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsortr.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dsortr.f @@ -4,7 +4,7 @@ c\Name: dsortr c c\Description: -c Sort the array X1 in the order specified by WHICH and optionally +c Sort the array X1 in the order specified by WHICH and optionally c applies the permutation to the array X2. c c\Usage: @@ -39,17 +39,17 @@ c c\Author c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University -c Dept. of Computational & Houston, Texas +c Richard Lehoucq CRPC / Rice University +c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c 12/16/93: Version ' 2.1'. c Adapted from the sort routine in LANSO. c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sortr.F SID: 2.3 DATE OF SID: 4/19/96 RELEASE: 2 c c\EndLib @@ -86,7 +86,7 @@ subroutine dsortr (which, apply, n, x1, x2) c %-----------------------% c igap = n / 2 -c +c if (which .eq. 'SA') then c c X1 is sorted into decreasing order of algebraic. @@ -158,7 +158,7 @@ subroutine dsortr (which, apply, n, x1, x2) 80 continue c if (j.lt.0) go to 90 -c +c if (x1(j).gt.x1(j+igap)) then temp = x1(j) x1(j) = x1(j+igap) @@ -176,7 +176,7 @@ subroutine dsortr (which, apply, n, x1, x2) 90 continue igap = igap / 2 go to 70 -c +c else if (which .eq. 'LM') then c c X1 is sorted into increasing order of magnitude. diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstatn.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstatn.f index 244df1ae7988..d09d8a37137e 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstatn.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstatn.f @@ -9,10 +9,10 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: statn.F SID: 2.4 DATE OF SID: 4/20/96 RELEASE: 2 c subroutine dstatn @@ -22,7 +22,7 @@ subroutine dstatn c %--------------------------------% c include 'stat.h' -c +c c %-----------------------% c | Executable Statements | c %-----------------------% @@ -32,7 +32,7 @@ subroutine dstatn nrorth = 0 nitref = 0 nrstrt = 0 -c +c tnaupd = 0.0D+0 tnaup2 = 0.0D+0 tnaitr = 0.0D+0 @@ -43,14 +43,14 @@ subroutine dstatn titref = 0.0D+0 tgetv0 = 0.0D+0 trvec = 0.0D+0 -c +c c %----------------------------------------------------% c | User time including reverse communication overhead | c %----------------------------------------------------% c tmvopx = 0.0D+0 tmvbx = 0.0D+0 -c +c return c c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstats.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstats.f index 84f74b473aa4..cb1b3f38dd3e 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstats.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstats.f @@ -1,18 +1,18 @@ c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: stats.F SID: 2.1 DATE OF SID: 4/19/96 RELEASE: 2 c %---------------------------------------------% c | Initialize statistic and timing information | c | for symmetric Arnoldi code. | c %---------------------------------------------% - + subroutine dstats c %--------------------------------% c | See stat.doc for documentation | c %--------------------------------% include 'stat.h' - + c %-----------------------% c | Executable Statements | c %-----------------------% @@ -22,7 +22,7 @@ subroutine dstats nrorth = 0 nitref = 0 nrstrt = 0 - + tsaupd = 0.0D+0 tsaup2 = 0.0D+0 tsaitr = 0.0D+0 @@ -33,13 +33,13 @@ subroutine dstats titref = 0.0D+0 tgetv0 = 0.0D+0 trvec = 0.0D+0 - + c %----------------------------------------------------% c | User time including reverse communication overhead | c %----------------------------------------------------% tmvopx = 0.0D+0 tmvbx = 0.0D+0 - + return c c End of dstats diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstqrb.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstqrb.f index 9fef543ba786..d55a59a2d358 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstqrb.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/dstqrb.f @@ -32,13 +32,13 @@ c On exit, E has been destroyed. c c Z Double precision array, dimension (N). (OUTPUT) -c On exit, Z contains the last row of the orthonormal -c eigenvector matrix of the symmetric tridiagonal matrix. +c On exit, Z contains the last row of the orthonormal +c eigenvector matrix of the symmetric tridiagonal matrix. c If an error exit is made, Z contains the last row of the c eigenvector matrix associated with the stored eigenvalues. c c WORK Double precision array, dimension (max(1,2*N-2)). (WORKSPACE) -c Workspace used in accumulating the transformation for +c Workspace used in accumulating the transformation for c computing the last components of the eigenvectors. c c INFO Integer. (OUTPUT) @@ -62,9 +62,9 @@ c dcopy Level 1 BLAS that copies one vector to another. c dswap Level 1 BLAS that swaps the contents of two vectors. c lsame LAPACK character comparison routine. -c dlae2 LAPACK routine that computes the eigenvalues of a 2-by-2 +c dlae2 LAPACK routine that computes the eigenvalues of a 2-by-2 c symmetric matrix. -c dlaev2 LAPACK routine that eigendecomposition of a 2-by-2 symmetric +c dlaev2 LAPACK routine that eigendecomposition of a 2-by-2 symmetric c matrix. c dlamch LAPACK routine that determines machine constants. c dlanst LAPACK routine that computes the norm of a matrix. @@ -72,7 +72,7 @@ c dlartg LAPACK Givens rotation construction routine. c dlascl LAPACK routine for careful scaling of a matrix. c dlaset LAPACK matrix initialization routine. -c dlasr LAPACK routine that applies an orthogonal transformation to +c dlasr LAPACK routine that applies an orthogonal transformation to c a matrix. c dlasrt LAPACK sorting routine. c dsteqr LAPACK routine that computes eigenvalues and eigenvectors @@ -84,19 +84,19 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: stqrb.F SID: 2.5 DATE OF SID: 8/27/96 RELEASE: 2 c c\Remarks c 1. Starting with version 2.5, this routine is a modified version c of LAPACK version 2.0 subroutine SSTEQR. No lines are deleted, -c only commeted out and new lines inserted. +c only commented out and new lines inserted. c All lines commented out have "c$$$" at the beginning. c Note that the LAPACK version 1.0 subroutine SSTEQR contained -c bugs. +c bugs. c c\EndLib c @@ -118,9 +118,9 @@ subroutine dstqrb ( n, d, e, z, work, info ) & d( n ), e( n-1 ), z( n ), work( 2*n-2 ) c c .. parameters .. - Double precision + Double precision & zero, one, two, three - parameter ( zero = 0.0D+0, one = 1.0D+0, + parameter ( zero = 0.0D+0, one = 1.0D+0, & two = 2.0D+0, three = 3.0D+0 ) integer maxit parameter ( maxit = 30 ) @@ -129,7 +129,7 @@ subroutine dstqrb ( n, d, e, z, work, info ) integer i, icompz, ii, iscale, j, jtot, k, l, l1, lend, & lendm1, lendp1, lendsv, lm1, lsv, m, mm, mm1, & nm1, nmaxit - Double precision + Double precision & anorm, b, c, eps, eps2, f, g, p, r, rt1, rt2, & s, safmax, safmin, ssfmax, ssfmin, tst c .. @@ -380,9 +380,9 @@ subroutine dstqrb ( n, d, e, z, work, info ) c c *** New starting with version 2.5 *** c - call dlasr( 'r', 'v', 'b', 1, mm, work( l ), + call dlasr( 'r', 'v', 'b', 1, mm, work( l ), & work( n-1+l ), z( l ), 1 ) -c ************************************* +c ************************************* end if c d( l ) = d( l ) - p @@ -440,7 +440,7 @@ subroutine dstqrb ( n, d, e, z, work, info ) tst = z(l) z(l) = c*tst - s*z(l-1) z(l-1) = s*tst + c*z(l-1) -c ************************************* +c ************************************* else call dlae2( d( l-1 ), e( l-1 ), d( l ), rt1, rt2 ) end if @@ -502,7 +502,7 @@ subroutine dstqrb ( n, d, e, z, work, info ) c call dlasr( 'r', 'v', 'f', 1, mm, work( m ), work( n-1+m ), & z( m ), 1 ) -c ************************************* +c ************************************* end if c d( l ) = d( l ) - p diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sgetv0.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sgetv0.f index a60cdbef837f..d861b2d6d75a 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sgetv0.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sgetv0.f @@ -3,13 +3,13 @@ c c\Name: sgetv0 c -c\Description: +c\Description: c Generate a random initial residual vector for the Arnoldi process. -c Force the residual vector to be in the range of the operator OP. +c Force the residual vector to be in the range of the operator OP. c c\Usage: c call sgetv0 -c ( IDO, BMAT, ITRY, INITV, N, J, V, LDV, RESID, RNORM, +c ( IDO, BMAT, ITRY, INITV, N, J, V, LDV, RESID, RNORM, c IPNTR, WORKD, IERR ) c c\Arguments @@ -36,7 +36,7 @@ c B = 'G' -> generalized eigenvalue problem A*x = lambda*B*x c c ITRY Integer. (INPUT) -c ITRY counts the number of times that sgetv0 is called. +c ITRY counts the number of times that sgetv0 is called. c It should be set to 1 on the initial call to sgetv0. c c INITV Logical variable. (INPUT) @@ -55,11 +55,11 @@ c if this is a "restart". c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c RESID Real array of length N. (INPUT/OUTPUT) -c Initial residual vector to be generated. If RESID is +c Initial residual vector to be generated. If RESID is c provided, force RESID into the range of the operator OP. c c RNORM Real scalar. (OUTPUT) @@ -88,7 +88,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c @@ -98,7 +98,7 @@ c slarnv LAPACK routine for generating a random vector. c sgemv Level 2 BLAS routine for matrix vector multiplication. c scopy Level 1 BLAS that copies one vector to another. -c sdot Level 1 BLAS that computes the scalar product of two vectors. +c sdot Level 1 BLAS that computes the scalar product of two vectors. c snrm2 Level 1 BLAS that computes the norm of a vector. c c\Author @@ -106,20 +106,20 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: getv0.F SID: 2.7 DATE OF SID: 04/07/99 RELEASE: 2 c c\EndLib c c----------------------------------------------------------------------- c - subroutine sgetv0 - & ( ido, bmat, itry, initv, n, j, v, ldv, resid, rnorm, + subroutine sgetv0 + & ( ido, bmat, itry, initv, n, j, v, ldv, resid, rnorm, & ipntr, workd, ierr ) -c +c c %----------------------------------------------------% c | Include files for debugging and timing information | c %----------------------------------------------------% @@ -208,7 +208,7 @@ subroutine sgetv0 end if c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -216,7 +216,7 @@ subroutine sgetv0 c call arscnd (t0) msglvl = mgetv0 -c +c ierr = 0 iter = 0 first = .FALSE. @@ -235,23 +235,25 @@ subroutine sgetv0 idist = 2 call slarnv (idist, iseed, n, resid) end if -c +c c %----------------------------------------------------------% c | Force the starting vector into the range of OP to handle | c | the generalized problem when B is possibly (singular). | c %----------------------------------------------------------% c call arscnd (t2) - if (bmat .eq. 'G') then + if (itry .eq. 1) then nopx = nopx + 1 ipntr(1) = 1 ipntr(2) = n + 1 call scopy (n, resid, 1, workd, 1) ido = -1 go to 9000 + else if (itry .gt. 1 .and. bmat .eq. 'G') then + call scopy (n, resid, 1, workd(n + 1), 1) end if end if -c +c c %-----------------------------------------% c | Back from computing OP*(initial-vector) | c %-----------------------------------------% @@ -259,16 +261,16 @@ subroutine sgetv0 if (first) go to 20 c c %-----------------------------------------------% -c | Back from computing B*(orthogonalized-vector) | +c | Back from computing OP*(orthogonalized-vector) | c %-----------------------------------------------% c if (orth) go to 40 -c +c if (bmat .eq. 'G') then call arscnd (t3) tmvopx = tmvopx + (t3 - t2) end if -c +c c %------------------------------------------------------% c | Starting vector is now in the range of OP; r = OP*r; | c | Compute B-norm of starting vector. | @@ -276,9 +278,9 @@ subroutine sgetv0 c call arscnd (t2) first = .TRUE. + if (itry .eq. 1) call scopy (n, workd(n + 1), 1, resid, 1) if (bmat .eq. 'G') then nbx = nbx + 1 - call scopy (n, workd(n+1), 1, resid, 1) ipntr(1) = n + 1 ipntr(2) = 1 ido = 2 @@ -286,14 +288,14 @@ subroutine sgetv0 else if (bmat .eq. 'I') then call scopy (n, resid, 1, workd, 1) end if -c +c 20 continue c if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c first = .FALSE. if (bmat .eq. 'G') then rnorm0 = sdot (n, resid, 1, workd, 1) @@ -308,7 +310,7 @@ subroutine sgetv0 c %---------------------------------------------% c if (j .eq. 1) go to 50 -c +c c %---------------------------------------------------------------- c | Otherwise need to B-orthogonalize the starting vector against | c | the current Arnoldi basis using Gram-Schmidt with iter. ref. | @@ -324,11 +326,11 @@ subroutine sgetv0 orth = .TRUE. 30 continue c - call sgemv ('T', n, j-1, one, v, ldv, workd, 1, + call sgemv ('T', n, j-1, one, v, ldv, workd, 1, & zero, workd(n+1), 1) - call sgemv ('N', n, j-1, -one, v, ldv, workd(n+1), 1, + call sgemv ('N', n, j-1, -one, v, ldv, workd(n+1), 1, & one, resid, 1) -c +c c %----------------------------------------------------------% c | Compute the B-norm of the orthogonalized starting vector | c %----------------------------------------------------------% @@ -344,14 +346,14 @@ subroutine sgetv0 else if (bmat .eq. 'I') then call scopy (n, resid, 1, workd, 1) end if -c +c 40 continue c if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c if (bmat .eq. 'G') then rnorm = sdot (n, resid, 1, workd, 1) rnorm = sqrt(abs(rnorm)) @@ -364,14 +366,14 @@ subroutine sgetv0 c %--------------------------------------% c if (msglvl .gt. 2) then - call svout (logfil, 1, rnorm0, ndigit, + call svout (logfil, 1, [rnorm0], ndigit, & '_getv0: re-orthonalization ; rnorm0 is') - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_getv0: re-orthonalization ; rnorm is') end if c if (rnorm .gt. 0.717*rnorm0) go to 50 -c +c iter = iter + 1 if (iter .le. 5) then c @@ -393,11 +395,11 @@ subroutine sgetv0 rnorm = zero ierr = -1 end if -c +c 50 continue c if (msglvl .gt. 0) then - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_getv0: B-norm of initial / restarted starting vector') end if if (msglvl .gt. 3) then @@ -405,10 +407,10 @@ subroutine sgetv0 & '_getv0: initial / restarted starting vector') end if ido = 99 -c +c call arscnd (t1) tgetv0 = tgetv0 + (t1 - t0) -c +c 9000 continue return c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaitr.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaitr.f index 1a35d44087d7..8a5d795be35f 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaitr.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaitr.f @@ -3,8 +3,8 @@ c c\Name: snaitr c -c\Description: -c Reverse communication interface for applying NP additional steps to +c\Description: +c Reverse communication interface for applying NP additional steps to c a K step nonsymmetric Arnoldi factorization. c c Input: OP*V_{k} - V_{k}*H = r_{k}*e_{k}^T @@ -20,7 +20,7 @@ c c\Usage: c call snaitr -c ( IDO, BMAT, N, K, NP, NB, RESID, RNORM, V, LDV, H, LDH, +c ( IDO, BMAT, N, K, NP, NB, RESID, RNORM, V, LDV, H, LDH, c IPNTR, WORKD, INFO ) c c\Arguments @@ -62,8 +62,8 @@ c Number of additional Arnoldi steps to take. c c NB Integer. (INPUT) -c Blocksize to be used in the recurrence. -c Only work for NB = 1 right now. The goal is to have a +c Blocksize to be used in the recurrence. +c Only work for NB = 1 right now. The goal is to have a c program that implement both the block and non-block method. c c RESID Real array of length N. (INPUT/OUTPUT) @@ -75,37 +75,37 @@ c B-norm of the updated residual r_{k+p} on output. c c V Real N by K+NP array. (INPUT/OUTPUT) -c On INPUT: V contains the Arnoldi vectors in the first K +c On INPUT: V contains the Arnoldi vectors in the first K c columns. c On OUTPUT: V contains the new NP Arnoldi vectors in the next c NP columns. The first K columns are unchanged. c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c H Real (K+NP) by (K+NP) array. (INPUT/OUTPUT) c H is used to store the generated upper Hessenberg matrix. c c LDH Integer. (INPUT) -c Leading dimension of H exactly as declared in the calling +c Leading dimension of H exactly as declared in the calling c program. c c IPNTR Integer array of length 3. (OUTPUT) -c Pointer to mark the starting locations in the WORK for +c Pointer to mark the starting locations in the WORK for c vectors used by the Arnoldi iteration. c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X. c IPNTR(2): pointer to the current result vector Y. -c IPNTR(3): pointer to the vector B * X when used in the +c IPNTR(3): pointer to the vector B * X when used in the c shift-and-invert mode. X is the current operand. c ------------------------------------------------------------- -c +c c WORKD Real work array of length 3*N. (REVERSE COMMUNICATION) c Distributed array to be used in the basic Arnoldi iteration -c for reverse communication. The calling program should not +c for reverse communication. The calling program should not c use WORKD as temporary workspace during the iteration !!!!!! -c On input, WORKD(1:N) = B*RESID and is used to save some +c On input, WORKD(1:N) = B*RESID and is used to save some c computation at the first step. c c INFO Integer. (OUTPUT) @@ -125,7 +125,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c @@ -143,7 +143,7 @@ c saxpy Level 1 BLAS that computes a vector triad. c sscal Level 1 BLAS that scales a vector. c scopy Level 1 BLAS that copies one vector to another . -c sdot Level 1 BLAS that computes the scalar product of two vectors. +c sdot Level 1 BLAS that computes the scalar product of two vectors. c snrm2 Level 1 BLAS that computes the norm of a vector. c c\Author @@ -151,22 +151,22 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas -c +c Rice University +c Houston, Texas +c c\Revision history: c xx/xx/92: Version ' 2.4' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: naitr.F SID: 2.4 DATE OF SID: 8/27/96 RELEASE: 2 c c\Remarks c The algorithm implemented is: -c +c c restart = .false. -c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; +c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; c r_{k} contains the initial residual vector even for k = 0; -c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already +c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already c computed by the calling program. c c betaj = rnorm ; p_{k+1} = B*r_{k} ; @@ -174,7 +174,7 @@ c 1) if ( betaj < tol ) stop or restart depending on j. c ( At present tol is zero ) c if ( restart ) generate a new starting vector. -c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; +c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; c p_{j} = p_{j}/betaj c 3) r_{j} = OP*v_{j} where OP is defined as in snaupd c For shift-invert mode p_{j} = B*v_{j} is already available. @@ -189,7 +189,7 @@ c 5) Re-orthogonalization step: c s = V_{j}'*B*r_{j} c r_{j} = r_{j} - V_{j}*s; rnorm1 = || r_{j} || -c alphaj = alphaj + s_{j}; +c alphaj = alphaj + s_{j}; c 6) Iterative refinement step: c If (rnorm1 > 0.717*rnorm) then c rnorm = rnorm1 @@ -199,7 +199,7 @@ c If this is the first time in step 6), go to 5) c Else r_{j} lies in the span of V_{j} numerically. c Set r_{j} = 0 and rnorm = 0; go to 1) -c EndIf +c EndIf c End Do c c\EndLib @@ -207,7 +207,7 @@ c----------------------------------------------------------------------- c subroutine snaitr - & (ido, bmat, n, k, np, nb, resid, rnorm, v, ldv, h, ldh, + & (ido, bmat, n, k, np, nb, resid, rnorm, v, ldv, h, ldh, & ipntr, workd, info) c c %----------------------------------------------------% @@ -250,14 +250,14 @@ subroutine snaitr integer ierr, i, infol, ipj, irj, ivj, iter, itry, j, msglvl, & jj Real - & betaj, ovfl, temp1, rnorm1, smlnum, tst1, ulp, unfl, + & betaj, ovfl, temp1, rnorm1, smlnum, tst1, ulp, unfl, & wnorm save first, orth1, orth2, rstart, step3, step4, & ierr, ipj, irj, ivj, iter, itry, j, msglvl, ovfl, & betaj, rnorm1, smlnum, ulp, unfl, wnorm c c %-----------------------% -c | Local Array Arguments | +c | Local Array Arguments | c %-----------------------% c Real @@ -267,7 +267,7 @@ subroutine snaitr c | External Subroutines | c %----------------------% c - external saxpy, scopy, sscal, sgemv, sgetv0, slabad, + external saxpy, scopy, sscal, sgemv, sgetv0, slabad, & svout, smout, ivout, arscnd c c %--------------------% @@ -313,7 +313,7 @@ subroutine snaitr end if c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -321,7 +321,7 @@ subroutine snaitr c call arscnd (t0) msglvl = mnaitr -c +c c %------------------------------% c | Initial call to this routine | c %------------------------------% @@ -337,7 +337,7 @@ subroutine snaitr irj = ipj + n ivj = irj + n end if -c +c c %-------------------------------------------------% c | When in reverse communication mode one of: | c | STEP3, STEP4, ORTH1, ORTH2, RSTART | @@ -367,19 +367,19 @@ subroutine snaitr c | | c | Note: B*r_{j-1} is already in WORKD(1:N)=WORKD(IPJ:IPJ+N-1) | c %--------------------------------------------------------------% - + 1000 continue c if (msglvl .gt. 1) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: generating Arnoldi vector number') - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_naitr: B-norm of the current residual is') end if -c +c c %---------------------------------------------------% c | STEP 1: Check if the B norm of j-th residual | -c | vector is zero. Equivalent to determing whether | +c | vector is zero. Equivalent to determining whether | c | an exact j-step Arnoldi factorization is present. | c %---------------------------------------------------% c @@ -393,16 +393,16 @@ subroutine snaitr c %---------------------------------------------------% c if (msglvl .gt. 0) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: ****** RESTART AT STEP ******') end if -c +c c %---------------------------------------------% c | ITRY is the loop variable that controls the | c | maximum amount of times that a restart is | c | attempted. NRSTRT is used by stat.h | c %---------------------------------------------% -c +c betaj = zero nrstrt = nrstrt + 1 itry = 1 @@ -416,7 +416,7 @@ subroutine snaitr c | RSTART = .true. flow returns here. | c %--------------------------------------% c - call sgetv0 (ido, bmat, itry, .false., n, j, v, ldv, + call sgetv0 (ido, bmat, itry, .false., n, j, v, ldv, & resid, rnorm, ipntr, workd, ierr) if (ido .ne. 99) go to 9000 if (ierr .lt. 0) then @@ -435,7 +435,7 @@ subroutine snaitr ido = 99 go to 9000 end if -c +c 40 continue c c %---------------------------------------------------------% @@ -457,9 +457,9 @@ subroutine snaitr c | use LAPACK routine SLASCL | c %-----------------------------------------% c - call slascl ('General', i, i, rnorm, one, n, 1, + call slascl ('General', i, i, rnorm, one, n, 1, & v(1,j), n, infol) - call slascl ('General', i, i, rnorm, one, n, 1, + call slascl ('General', i, i, rnorm, one, n, 1, & workd(ipj), n, infol) end if c @@ -476,14 +476,14 @@ subroutine snaitr ipntr(2) = irj ipntr(3) = ipj ido = 1 -c +c c %-----------------------------------% c | Exit in order to compute OP*v_{j} | c %-----------------------------------% -c - go to 9000 +c + go to 9000 50 continue -c +c c %----------------------------------% c | Back from reverse communication; | c | WORKD(IRJ:IRJ+N-1) := OP*v_{j} | @@ -492,7 +492,7 @@ subroutine snaitr c call arscnd (t3) tmvopx = tmvopx + (t3 - t2) - + step3 = .false. c c %------------------------------------------% @@ -500,7 +500,7 @@ subroutine snaitr c %------------------------------------------% c call scopy (n, workd(irj), 1, resid, 1) -c +c c %---------------------------------------% c | STEP 4: Finish extending the Arnoldi | c | factorization to length j. | @@ -513,17 +513,17 @@ subroutine snaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-------------------------------------% c | Exit in order to compute B*OP*v_{j} | c %-------------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call scopy (n, resid, 1, workd(ipj), 1) end if 60 continue -c +c c %----------------------------------% c | Back from reverse communication; | c | WORKD(IPJ:IPJ+N-1) := B*OP*v_{j} | @@ -534,7 +534,7 @@ subroutine snaitr call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c step4 = .false. c c %-------------------------------------% @@ -542,7 +542,7 @@ subroutine snaitr c | Compute the B-norm of OP*v_{j}. | c %-------------------------------------% c - if (bmat .eq. 'G') then + if (bmat .eq. 'G') then wnorm = sdot (n, resid, 1, workd(ipj), 1) wnorm = sqrt(abs(wnorm)) else if (bmat .eq. 'I') then @@ -562,13 +562,13 @@ subroutine snaitr c | Compute the j Fourier coefficients w_{j} | c | WORKD(IPJ:IPJ+N-1) contains B*OP*v_{j}. | c %------------------------------------------% -c +c call sgemv ('T', n, j, one, v, ldv, workd(ipj), 1, & zero, h(1,j), 1) c c %--------------------------------------% c | Orthogonalize r_{j} against V_{j}. | -c | RESID contains OP*v_{j}. See STEP 3. | +c | RESID contains OP*v_{j}. See STEP 3. | c %--------------------------------------% c call sgemv ('N', n, j, -one, v, ldv, h(1,j), 1, @@ -577,7 +577,7 @@ subroutine snaitr if (j .gt. 1) h(j,j-1) = betaj c call arscnd (t4) -c +c orth1 = .true. c call arscnd (t2) @@ -587,17 +587,17 @@ subroutine snaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %----------------------------------% c | Exit in order to compute B*r_{j} | c %----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call scopy (n, resid, 1, workd(ipj), 1) - end if + end if 70 continue -c +c c %---------------------------------------------------% c | Back from reverse communication if ORTH1 = .true. | c | WORKD(IPJ:IPJ+N-1) := B*r_{j}. | @@ -607,20 +607,20 @@ subroutine snaitr call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c orth1 = .false. c c %------------------------------% c | Compute the B-norm of r_{j}. | c %------------------------------% c - if (bmat .eq. 'G') then + if (bmat .eq. 'G') then rnorm = sdot (n, resid, 1, workd(ipj), 1) rnorm = sqrt(abs(rnorm)) else if (bmat .eq. 'I') then rnorm = snrm2(n, resid, 1) end if -c +c c %-----------------------------------------------------------% c | STEP 5: Re-orthogonalization / Iterative refinement phase | c | Maximum NITER_ITREF tries. | @@ -642,20 +642,20 @@ subroutine snaitr if (rnorm .gt. 0.717*wnorm) go to 100 iter = 0 nrorth = nrorth + 1 -c +c c %---------------------------------------------------% c | Enter the Iterative refinement phase. If further | c | refinement is necessary, loop back here. The loop | c | variable is ITER. Perform a step of Classical | c | Gram-Schmidt using all the Arnoldi vectors V_{j} | c %---------------------------------------------------% -c +c 80 continue c if (msglvl .gt. 2) then xtemp(1) = wnorm xtemp(2) = rnorm - call svout (logfil, 2, xtemp, ndigit, + call svout (logfil, 2, xtemp, ndigit, & '_naitr: re-orthonalization; wnorm and rnorm are') call svout (logfil, j, h(1,j), ndigit, & '_naitr: j-th column of H') @@ -666,7 +666,7 @@ subroutine snaitr c | WORKD(IRJ:IRJ+J-1) = v(:,1:J)'*WORKD(IPJ:IPJ+N-1). | c %----------------------------------------------------% c - call sgemv ('T', n, j, one, v, ldv, workd(ipj), 1, + call sgemv ('T', n, j, one, v, ldv, workd(ipj), 1, & zero, workd(irj), 1) c c %---------------------------------------------% @@ -676,10 +676,10 @@ subroutine snaitr c | + v(:,1:J)*WORKD(IRJ:IRJ+J-1)*e'_j. | c %---------------------------------------------% c - call sgemv ('N', n, j, -one, v, ldv, workd(irj), 1, + call sgemv ('N', n, j, -one, v, ldv, workd(irj), 1, & one, resid, 1) call saxpy (j, one, workd(irj), 1, h(1,j), 1) -c +c orth2 = .true. call arscnd (t2) if (bmat .eq. 'G') then @@ -688,16 +688,16 @@ subroutine snaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-----------------------------------% c | Exit in order to compute B*r_{j}. | c | r_{j} is the corrected residual. | c %-----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call scopy (n, resid, 1, workd(ipj), 1) - end if + end if 90 continue c c %---------------------------------------------------% @@ -712,8 +712,8 @@ subroutine snaitr c %-----------------------------------------------------% c | Compute the B-norm of the corrected residual r_{j}. | c %-----------------------------------------------------% -c - if (bmat .eq. 'G') then +c + if (bmat .eq. 'G') then rnorm1 = sdot (n, resid, 1, workd(ipj), 1) rnorm1 = sqrt(abs(rnorm1)) else if (bmat .eq. 'I') then @@ -721,7 +721,7 @@ subroutine snaitr end if c if (msglvl .gt. 0 .and. iter .gt. 0) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: Iterative refinement for Arnoldi residual') if (msglvl .gt. 2) then xtemp(1) = rnorm @@ -749,7 +749,7 @@ subroutine snaitr c %---------------------------------------% c rnorm = rnorm1 -c +c else c c %-------------------------------------------% @@ -771,21 +771,21 @@ subroutine snaitr 95 continue rnorm = zero end if -c +c c %----------------------------------------------% c | Branch here directly if iterative refinement | c | wasn't necessary or after at most NITER_REF | c | steps of iterative refinement. | c %----------------------------------------------% -c +c 100 continue -c +c rstart = .false. orth2 = .false. -c +c call arscnd (t5) titref = titref + (t5 - t4) -c +c c %------------------------------------% c | STEP 6: Update j = j+1; Continue | c %------------------------------------% @@ -796,25 +796,25 @@ subroutine snaitr tnaitr = tnaitr + (t1 - t0) ido = 99 do 110 i = max(1,k), k+np-1 -c +c c %--------------------------------------------% c | Check for splitting and deflation. | c | Use a standard test as in the QR algorithm | c | REFERENCE: LAPACK subroutine slahqr | c %--------------------------------------------% -c +c tst1 = abs( h( i, i ) ) + abs( h( i+1, i+1 ) ) if( tst1.eq.zero ) & tst1 = slanhs( '1', k+np, h, ldh, workd(n+1) ) - if( abs( h( i+1,i ) ).le.max( ulp*tst1, smlnum ) ) + if( abs( h( i+1,i ) ).le.max( ulp*tst1, smlnum ) ) & h(i+1,i) = zero 110 continue -c +c if (msglvl .gt. 2) then - call smout (logfil, k+np, k+np, h, ldh, ndigit, + call smout (logfil, k+np, k+np, h, ldh, ndigit, & '_naitr: Final upper Hessenberg matrix H of order K+NP') end if -c +c go to 9000 end if c @@ -823,7 +823,7 @@ subroutine snaitr c %--------------------------------------------------------% c go to 1000 -c +c c %---------------------------------------------------------------% c | | c | E N D O F M A I N I T E R A T I O N L O O P | diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snapps.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snapps.f index 1a88dc355fbc..33b0361084a9 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snapps.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snapps.f @@ -13,14 +13,14 @@ c A*(V_{k}*Q) - (V_{k}*Q)*(Q^T* H_{k}*Q) = r_{k+p}*e_{k+p}^T * Q c c where Q is an orthogonal matrix which is the product of rotations -c and reflections resulting from the NP bulge chage sweeps. +c and reflections resulting from the NP bulge change sweeps. c The updated Arnoldi factorization becomes: c c A*VNEW_{k} - VNEW_{k}*HNEW_{k} = rnew_{k}*e_{k}^T. c c\Usage: c call snapps -c ( N, KEV, NP, SHIFTR, SHIFTI, V, LDV, H, LDH, RESID, Q, LDQ, +c ( N, KEV, NP, SHIFTR, SHIFTI, V, LDV, H, LDH, RESID, Q, LDQ, c WORKL, WORKD ) c c\Arguments @@ -29,8 +29,8 @@ c c KEV Integer. (INPUT/OUTPUT) c KEV+NP is the size of the input matrix H. -c KEV is the size of the updated matrix HNEW. KEV is only -c updated on ouput when fewer than NP shifts are applied in +c KEV is the size of the updated matrix HNEW. KEV is only +c updated on output when fewer than NP shifts are applied in c order to keep the conjugate pair together. c c NP Integer. (INPUT) @@ -38,7 +38,7 @@ c c SHIFTR, Real array of length NP. (INPUT) c SHIFTI Real and imaginary part of the shifts to be applied. -c Upon, entry to snapps, the shifts must be sorted so that the +c Upon, entry to snapps, the shifts must be sorted so that the c conjugate pairs are in consecutive locations. c c V Real N by (KEV+NP) array. (INPUT/OUTPUT) @@ -51,7 +51,7 @@ c program. c c H Real (KEV+NP) by (KEV+NP) array. (INPUT/OUTPUT) -c On INPUT, H contains the current KEV+NP by KEV+NP upper +c On INPUT, H contains the current KEV+NP by KEV+NP upper c Hessenber matrix of the Arnoldi factorization. c On OUTPUT, H contains the updated KEV by KEV upper Hessenberg c matrix in the KEV leading submatrix. @@ -62,7 +62,7 @@ c c RESID Real array of length N. (INPUT/OUTPUT) c On INPUT, RESID contains the the residual vector r_{k+p}. -c On OUTPUT, RESID is the update residual vector rnew_{k} +c On OUTPUT, RESID is the update residual vector rnew_{k} c in the first KEV locations. c c Q Real KEV+NP by KEV+NP work array. (WORKSPACE) @@ -102,7 +102,7 @@ c svout ARPACK utility routine that prints vectors. c slabad LAPACK routine that computes machine constants. c slacpy LAPACK matrix copy routine. -c slamch LAPACK routine that determines machine constants. +c slamch LAPACK routine that determines machine constants. c slanhs LAPACK routine that computes various norms of a matrix. c slapy2 LAPACK routine to compute sqrt(x**2+y**2) carefully. c slarf LAPACK routine that applies Householder reflection to @@ -120,13 +120,13 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.4' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: napps.F SID: 2.4 DATE OF SID: 3/28/97 RELEASE: 2 c c\Remarks @@ -141,7 +141,7 @@ c----------------------------------------------------------------------- c subroutine snapps - & ( n, kev, np, shiftr, shifti, v, ldv, h, ldh, resid, q, ldq, + & ( n, kev, np, shiftr, shifti, v, ldv, h, ldh, resid, q, ldq, & workl, workd ) c c %----------------------------------------------------% @@ -162,7 +162,7 @@ subroutine snapps c %-----------------% c Real - & h(ldh,kev+np), resid(n), shifti(np), shiftr(np), + & h(ldh,kev+np), resid(n), shifti(np), shiftr(np), & v(ldv,kev+np), q(ldq,kev+np), workd(2*n), workl(kev+np) c c %------------% @@ -180,9 +180,9 @@ subroutine snapps integer i, iend, ir, istart, j, jj, kplusp, msglvl, nr logical cconj, first Real - & c, f, g, h11, h12, h21, h22, h32, ovfl, r, s, sigmai, + & c, f, g, h11, h12, h21, h22, h32, ovfl, r, s, sigmai, & sigmar, smlnum, ulp, unfl, u(3), t, tau, tst1 - save first, ovfl, smlnum, ulp, unfl + save first, ovfl, smlnum, ulp, unfl c c %----------------------% c | External Subroutines | @@ -206,7 +206,7 @@ subroutine snapps intrinsic abs, max, min c c %----------------% -c | Data statments | +c | Data statements | c %----------------% c data first / .true. / @@ -239,8 +239,8 @@ subroutine snapps c call arscnd (t0) msglvl = mnapps - kplusp = kev + np -c + kplusp = kev + np +c c %--------------------------------------------% c | Initialize Q to the identity to accumulate | c | the rotations and reflections | @@ -266,11 +266,11 @@ subroutine snapps sigmai = shifti(jj) c if (msglvl .gt. 2 ) then - call ivout (logfil, 1, jj, ndigit, + call ivout (logfil, 1, [jj], ndigit, & '_napps: shift number.') - call svout (logfil, 1, sigmar, ndigit, + call svout (logfil, 1, [sigmar], ndigit, & '_napps: The real part of the shift ') - call svout (logfil, 1, sigmai, ndigit, + call svout (logfil, 1, [sigmai], ndigit, & '_napps: The imaginary part of the shift ') end if c @@ -335,11 +335,11 @@ subroutine snapps & tst1 = slanhs( '1', kplusp-jj+1, h, ldh, workl ) if( abs( h( i+1,i ) ).le.max( ulp*tst1, smlnum ) ) then if (msglvl .gt. 0) then - call ivout (logfil, 1, i, ndigit, + call ivout (logfil, 1, [i], ndigit, & '_napps: matrix splitting at row/column no.') - call ivout (logfil, 1, jj, ndigit, + call ivout (logfil, 1, [jj], ndigit, & '_napps: matrix splitting with shift number.') - call svout (logfil, 1, h(i+1,i), ndigit, + call svout (logfil, 1, h(i+1,i), ndigit, & '_napps: off diagonal element.') end if iend = i @@ -351,9 +351,9 @@ subroutine snapps 40 continue c if (msglvl .gt. 2) then - call ivout (logfil, 1, istart, ndigit, + call ivout (logfil, 1, [istart], ndigit, & '_napps: Start of current block ') - call ivout (logfil, 1, iend, ndigit, + call ivout (logfil, 1, [iend], ndigit, & '_napps: End of current block ') end if c @@ -368,7 +368,7 @@ subroutine snapps c | complex conjugate pair of shifts on a 2 by 2 matrix. | c %------------------------------------------------------% c - if ( istart + 1 .eq. iend .and. abs( sigmai ) .gt. zero ) + if ( istart + 1 .eq. iend .and. abs( sigmai ) .gt. zero ) & go to 100 c h11 = h(istart,istart) @@ -381,12 +381,12 @@ subroutine snapps c f = h11 - sigmar g = h21 -c +c do 80 i = istart, iend-1 c -c %------------------------------------------------------% +c %-----------------------------------------------------% c | Construct the plane rotation G to zero out the bulge | -c %------------------------------------------------------% +c %-----------------------------------------------------% c call slartg (f, g, c, s, r) if (i .gt. istart) then @@ -413,7 +413,7 @@ subroutine snapps do 50 j = i, kplusp t = c*h(i,j) + s*h(i+1,j) h(i+1,j) = -s*h(i,j) + c*h(i+1,j) - h(i,j) = t + h(i,j) = t 50 continue c c %---------------------------------------------% @@ -423,17 +423,17 @@ subroutine snapps do 60 j = 1, min(i+2,iend) t = c*h(j,i) + s*h(j,i+1) h(j,i+1) = -s*h(j,i) + c*h(j,i+1) - h(j,i) = t + h(j,i) = t 60 continue c c %----------------------------------------------------% c | Accumulate the rotation in the matrix Q; Q <- Q*G | c %----------------------------------------------------% c - do 70 j = 1, min( i+jj, kplusp ) + do 70 j = 1, min( i+jj, kplusp ) t = c*q(j,i) + s*q(j,i+1) q(j,i+1) = - s*q(j,i) + c*q(j,i+1) - q(j,i) = t + q(j,i) = t 70 continue c c %---------------------------% @@ -449,7 +449,7 @@ subroutine snapps c %-----------------------------------% c | Finished applying the real shift. | c %-----------------------------------% -c +c else c c %----------------------------------------------------% @@ -465,9 +465,9 @@ subroutine snapps c %---------------------------------------------------------% c s = 2.0*sigmar - t = slapy2 ( sigmar, sigmai ) + t = slapy2 ( sigmar, sigmai ) u(1) = ( h11 * (h11 - s) + t * t ) / h21 + h12 - u(2) = h11 + h22 - s + u(2) = h11 + h22 - s u(3) = h32 c do 90 i = istart, iend-1 @@ -507,7 +507,7 @@ subroutine snapps c | Accumulate the reflector in the matrix Q; Q <- Q*G | c %-----------------------------------------------------% c - call slarf ('Right', kplusp, nr, u, 1, tau, + call slarf ('Right', kplusp, nr, u, 1, tau, & q(1,i), ldq, workl) c c %----------------------------% @@ -526,7 +526,7 @@ subroutine snapps c | Finished applying a complex pair of shifts | c | to the current block | c %--------------------------------------------% -c +c end if c 100 continue @@ -568,7 +568,7 @@ subroutine snapps tst1 = abs( h( i, i ) ) + abs( h( i+1, i+1 ) ) if( tst1.eq.zero ) & tst1 = slanhs( '1', kev, h, ldh, workl ) - if( h( i+1,i ) .le. max( ulp*tst1, smlnum ) ) + if( h( i+1,i ) .le. max( ulp*tst1, smlnum ) ) & h(i+1,i) = zero 130 continue c @@ -581,9 +581,9 @@ subroutine snapps c %-------------------------------------------------% c if (h(kev+1,kev) .gt. zero) - & call sgemv ('N', n, kplusp, one, v, ldv, q(1,kev+1), 1, zero, + & call sgemv ('N', n, kplusp, one, v, ldv, q(1,kev+1), 1, zero, & workd(n+1), 1) -c +c c %----------------------------------------------------------% c | Compute column 1 to kev of (V*Q) in backward order | c | taking advantage of the upper Hessenberg structure of Q. | @@ -600,14 +600,14 @@ subroutine snapps c %-------------------------------------------------% c call slacpy ('A', n, kev, v(1,kplusp-kev+1), ldv, v, ldv) -c +c c %--------------------------------------------------------------% c | Copy the (kev+1)-st column of (V*Q) in the appropriate place | c %--------------------------------------------------------------% c if (h(kev+1,kev) .gt. zero) & call scopy (n, workd(n+1), 1, v(1,kev+1), 1) -c +c c %-------------------------------------% c | Update the residual vector: | c | r <- sigmak*r + betak*v(:,kev+1) | @@ -625,7 +625,7 @@ subroutine snapps & '_napps: sigmak = (e_{kev+p}^T*Q)*e_{kev}') call svout (logfil, 1, h(kev+1,kev), ndigit, & '_napps: betak = e_{kev+1}^T*H*e_{kev}') - call ivout (logfil, 1, kev, ndigit, + call ivout (logfil, 1, [kev], ndigit, & '_napps: Order of the final Hessenberg matrix ') if (msglvl .gt. 2) then call smout (logfil, kev, kev, h, ldh, ndigit, @@ -633,11 +633,11 @@ subroutine snapps end if c end if -c +c 9000 continue call arscnd (t1) tnapps = tnapps + (t1 - t0) -c +c return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaup2.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaup2.f index fbe951ba0408..e3be754eacd3 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaup2.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaup2.f @@ -26,7 +26,7 @@ c The logic for adjusting is contained within the current c subroutine. c If ISHIFT=0, NP is the number of shifts the user needs -c to provide via reverse comunication. 0 < NP < NCV-NEV. +c to provide via reverse communication. 0 < NP < NCV-NEV. c NP may be less than NCV-NEV for two reasons. The first, is c to keep complex conjugate pairs of "wanted" Ritz values c together. The second, is that a leading block of the current @@ -388,7 +388,7 @@ subroutine snaup2 iter = iter + 1 c if (msglvl .gt. 0) then - call ivout (logfil, 1, iter, ndigit, + call ivout (logfil, 1, [iter], ndigit, & '_naup2: **** Start of major iteration number ****') end if c @@ -401,9 +401,9 @@ subroutine snaup2 np = kplusp - nev c if (msglvl .gt. 1) then - call ivout (logfil, 1, nev, ndigit, + call ivout (logfil, 1, [nev], ndigit, & '_naup2: The length of the current Arnoldi factorization') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_naup2: Extend the Arnoldi factorization by') end if c @@ -435,7 +435,7 @@ subroutine snaup2 update = .false. c if (msglvl .gt. 1) then - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_naup2: Corresponding B-norm of the residual') end if c @@ -598,7 +598,7 @@ subroutine snaup2 c c %----------------------------------------------------% c | Sort the Ritz values according to the scaled Ritz | -c | esitmates. This will push all the converged ones | +c | estimates. This will push all the converged ones | c | towards the front of ritzr, ritzi, bounds | c | (in the case when NCONV < NEV.) | c %----------------------------------------------------% @@ -690,7 +690,7 @@ subroutine snaup2 end if c if (msglvl .gt. 0) then - call ivout (logfil, 1, nconv, ndigit, + call ivout (logfil, 1, [nconv], ndigit, & '_naup2: no. of "converged" Ritz values at this iter.') if (msglvl .gt. 1) then kp(1) = nev @@ -709,7 +709,7 @@ subroutine snaup2 if (ishift .eq. 0) then c c %-------------------------------------------------------% -c | User specified shifts: reverse comminucation to | +c | User specified shifts: reverse communication to | c | compute the shifts. They are returned in the first | c | 2*NP locations of WORKL. | c %-------------------------------------------------------% @@ -742,7 +742,7 @@ subroutine snaup2 end if c if (msglvl .gt. 2) then - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_naup2: The number of shifts to apply ') call svout (logfil, np, ritzr, ndigit, & '_naup2: Real part of the shifts') @@ -808,7 +808,7 @@ subroutine snaup2 cnorm = .false. c if (msglvl .gt. 2) then - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_naup2: B-norm of residual for compressed factorization') call smout (logfil, nev, nev, h, ldh, ndigit, & '_naup2: Compressed upper Hessenberg matrix H') diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaupd.f index 3d0232ddc78e..d6fad33863f5 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snaupd.f @@ -2,19 +2,19 @@ c c\Name: snaupd c -c\Description: +c\Description: c Reverse communication interface for the Implicitly Restarted Arnoldi -c iteration. This subroutine computes approximations to a few eigenpairs -c of a linear operator "OP" with respect to a semi-inner product defined by -c a symmetric positive semi-definite real matrix B. B may be the identity -c matrix. NOTE: If the linear operator "OP" is real and symmetric -c with respect to the real positive semi-definite symmetric matrix B, +c iteration. This subroutine computes approximations to a few eigenpairs +c of a linear operator "OP" with respect to a semi-inner product defined by +c a symmetric positive semi-definite real matrix B. B may be the identity +c matrix. NOTE: If the linear operator "OP" is real and symmetric +c with respect to the real positive semi-definite symmetric matrix B, c i.e. B*OP = (OP`)*B, then subroutine ssaupd should be used instead. c c The computed approximate eigenvalues are called Ritz values and c the corresponding approximate eigenvectors are called Ritz vectors. c -c snaupd is usually called iteratively to solve one of the +c snaupd is usually called iteratively to solve one of the c following problems: c c Mode 1: A*x = lambda*x. @@ -25,18 +25,18 @@ c ===> (If M can be factored see remark 3 below) c c Mode 3: A*x = lambda*M*x, M symmetric semi-definite -c ===> OP = Real_Part{ inv[A - sigma*M]*M } and B = M. +c ===> OP = Real_Part{ inv[A - sigma*M]*M } and B = M. c ===> shift-and-invert mode (in real arithmetic) -c If OP*x = amu*x, then +c If OP*x = amu*x, then c amu = 1/2 * [ 1/(lambda-sigma) + 1/(lambda-conjg(sigma)) ]. c Note: If sigma is real, i.e. imaginary part of sigma is zero; -c Real_Part{ inv[A - sigma*M]*M } == inv[A - sigma*M]*M -c amu == 1/(lambda-sigma). -c +c Real_Part{ inv[A - sigma*M]*M } == inv[A - sigma*M]*M +c amu == 1/(lambda-sigma). +c c Mode 4: A*x = lambda*M*x, M symmetric semi-definite -c ===> OP = Imaginary_Part{ inv[A - sigma*M]*M } and B = M. +c ===> OP = Imaginary_Part{ inv[A - sigma*M]*M } and B = M. c ===> shift-and-invert mode (in real arithmetic) -c If OP*x = amu*x, then +c If OP*x = amu*x, then c amu = 1/2i * [ 1/(lambda-sigma) - 1/(lambda-conjg(sigma)) ]. c c Both mode 3 and 4 give the same enhancement to eigenvalues close to @@ -63,7 +63,7 @@ c c\Arguments c IDO Integer. (INPUT/OUTPUT) -c Reverse communication flag. IDO must be zero on the first +c Reverse communication flag. IDO must be zero on the first c call to snaupd. IDO will be set internally to c indicate the type of operation to be performed. Control is c then given back to the calling routine which has the @@ -86,13 +86,13 @@ c IDO = 2: compute Y = B * X where c IPNTR(1) is the pointer into WORKD for X, c IPNTR(2) is the pointer into WORKD for Y. -c IDO = 3: compute the IPARAM(8) real and imaginary parts +c IDO = 3: compute the IPARAM(8) real and imaginary parts c of the shifts where INPTR(14) is the pointer c into WORKL for placing the shifts. See Remark c 5 below. c IDO = 99: done c ------------------------------------------------------------- -c +c c BMAT Character*1. (INPUT) c BMAT specifies the type of the matrix B that defines the c semi-inner product for the operator OP. @@ -114,14 +114,14 @@ c Number of eigenvalues of OP to be computed. 0 < NEV < N-1. c c TOL Real scalar. (INPUT) -c Stopping criterion: the relative accuracy of the Ritz value +c Stopping criterion: the relative accuracy of the Ritz value c is considered acceptable if BOUNDS(I) .LE. TOL*ABS(RITZ(I)) c where ABS(RITZ(I)) is the magnitude when RITZ(I) is complex. -c DEFAULT = slamch('EPS') (machine precision as computed -c by the LAPACK auxiliary subroutine slamch). +c DEFAULT = SLAMCH('EPS') (machine precision as computed +c by the LAPACK auxiliary subroutine SLAMCH). c c RESID Real array of length N. (INPUT/OUTPUT) -c On INPUT: +c On INPUT: c If INFO .EQ. 0, a random initial residual vector is used. c If INFO .NE. 0, RESID contains the initial residual vector, c possibly from a previous run. @@ -131,17 +131,17 @@ c NCV Integer. (INPUT) c Number of columns of the matrix V. NCV must satisfy the two c inequalities 2 <= NCV-NEV and NCV <= N. -c This will indicate how many Arnoldi vectors are generated -c at each iteration. After the startup phase in which NEV -c Arnoldi vectors are generated, the algorithm generates -c approximately NCV-NEV Arnoldi vectors at each subsequent update -c iteration. Most of the cost in generating each Arnoldi vector is -c in the matrix-vector operation OP*x. -c NOTE: 2 <= NCV-NEV in order that complex conjugate pairs of Ritz +c This will indicate how many Arnoldi vectors are generated +c at each iteration. After the startup phase in which NEV +c Arnoldi vectors are generated, the algorithm generates +c approximately NCV-NEV Arnoldi vectors at each subsequent update +c iteration. Most of the cost in generating each Arnoldi vector is +c in the matrix-vector operation OP*x. +c NOTE: 2 <= NCV-NEV in order that complex conjugate pairs of Ritz c values are kept together. (See remark 4 below) c c V Real array N by NCV. (OUTPUT) -c Contains the final set of Arnoldi basis vectors. +c Contains the final set of Arnoldi basis vectors. c c LDV Integer. (INPUT) c Leading dimension of V exactly as declared in the calling program. @@ -154,11 +154,11 @@ c ISHIFT = 0: the shifts are provided by the user via c reverse communication. The real and imaginary c parts of the NCV eigenvalues of the Hessenberg -c matrix H are returned in the part of the WORKL -c array corresponding to RITZR and RITZI. See remark +c matrix H are returned in the part of the WORKL +c array corresponding to RITZR and RITZI. See remark c 5 below. c ISHIFT = 1: exact shifts with respect to the current -c Hessenberg matrix H. This is equivalent to +c Hessenberg matrix H. This is equivalent to c restarting the iteration with a starting vector c that is a linear combination of approximate Schur c vectors associated with the "wanted" Ritz values. @@ -167,8 +167,8 @@ c IPARAM(2) = No longer referenced. c c IPARAM(3) = MXITER -c On INPUT: maximum number of Arnoldi update iterations allowed. -c On OUTPUT: actual number of Arnoldi update iterations taken. +c On INPUT: maximum number of Arnoldi update iterations allowed. +c On OUTPUT: actual number of Arnoldi update iterations taken. c c IPARAM(4) = NB: blocksize to be used in the recurrence. c The code currently works only for NB = 1. @@ -178,11 +178,11 @@ c the convergence criterion. c c IPARAM(6) = IUPD -c No longer referenced. Implicit restarting is ALWAYS used. +c No longer referenced. Implicit restarting is ALWAYS used. c c IPARAM(7) = MODE c On INPUT determines what type of eigenproblem is being solved. -c Must be 1,2,3,4; See under \Description of snaupd for the +c Must be 1,2,3,4; See under \Description of snaupd for the c four modes available. c c IPARAM(8) = NP @@ -194,7 +194,7 @@ c IPARAM(9) = NUMOP, IPARAM(10) = NUMOPB, IPARAM(11) = NUMREO, c OUTPUT: NUMOP = total number of OP*x operations, c NUMOPB = total number of B*x operations if BMAT='G', -c NUMREO = total number of steps of re-orthogonalization. +c NUMREO = total number of steps of re-orthogonalization. c c IPNTR Integer array of length 14. (OUTPUT) c Pointer to mark the starting locations in the WORKD and WORKL @@ -202,13 +202,13 @@ c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X in WORKD. c IPNTR(2): pointer to the current result vector Y in WORKD. -c IPNTR(3): pointer to the vector B * X in WORKD when used in +c IPNTR(3): pointer to the vector B * X in WORKD when used in c the shift-and-invert mode. c IPNTR(4): pointer to the next available location in WORKL c that is untouched by the program. c IPNTR(5): pointer to the NCV by NCV upper Hessenberg matrix c H in WORKL. -c IPNTR(6): pointer to the real part of the ritz value array +c IPNTR(6): pointer to the real part of the ritz value array c RITZR in WORKL. c IPNTR(7): pointer to the imaginary part of the ritz value array c RITZI in WORKL. @@ -219,9 +219,9 @@ c c Note: IPNTR(9:13) is only referenced by sneupd. See Remark 2 below. c -c IPNTR(9): pointer to the real part of the NCV RITZ values of the +c IPNTR(9): pointer to the real part of the NCV RITZ values of the c original system. -c IPNTR(10): pointer to the imaginary part of the NCV RITZ values of +c IPNTR(10): pointer to the imaginary part of the NCV RITZ values of c the original system. c IPNTR(11): pointer to the NCV corresponding error bounds. c IPNTR(12): pointer to the NCV by NCV upper quasi-triangular @@ -230,15 +230,15 @@ c of the upper Hessenberg matrix H. Only referenced by c sneupd if RVEC = .TRUE. See Remark 2 below. c ------------------------------------------------------------- -c +c c WORKD Real work array of length 3*N. (REVERSE COMMUNICATION) c Distributed array to be used in the basic Arnoldi iteration -c for reverse communication. The user should not use WORKD +c for reverse communication. The user should not use WORKD c as temporary workspace during the iteration. Upon termination c WORKD(1:N) contains B*RESID(1:N). If an invariant subspace c associated with the converged Ritz values is desired, see remark c 2 below, subroutine sneupd uses this output. -c See Data Distribution Note below. +c See Data Distribution Note below. c c WORKL Real work array of length LWORKL. (OUTPUT/WORKSPACE) c Private (replicated) array on each PE or array allocated on @@ -254,18 +254,18 @@ c Error flag on output. c = 0: Normal exit. c = 1: Maximum number of iterations taken. -c All possible eigenvalues of OP has been found. IPARAM(5) +c All possible eigenvalues of OP has been found. IPARAM(5) c returns the number of wanted converged Ritz values. c = 2: No longer an informational error. Deprecated starting c with release 2 of ARPACK. -c = 3: No shifts could be applied during a cycle of the -c Implicitly restarted Arnoldi iteration. One possibility -c is to increase the size of NCV relative to NEV. +c = 3: No shifts could be applied during a cycle of the +c Implicitly restarted Arnoldi iteration. One possibility +c is to increase the size of NCV relative to NEV. c See remark 4 below. c = -1: N must be positive. c = -2: NEV must be positive. c = -3: NCV-NEV >= 2 and less than or equal to N. -c = -4: The maximum number of Arnoldi update iteration +c = -4: The maximum number of Arnoldi update iteration c must be greater than zero. c = -5: WHICH must be one of 'LM', 'SM', 'LR', 'SR', 'LI', 'SI' c = -6: BMAT must be one of 'I' or 'G'. @@ -273,7 +273,7 @@ c = -8: Error return from LAPACK eigenvalue calculation; c = -9: Starting vector is zero. c = -10: IPARAM(7) must be 1,2,3,4. -c = -11: IPARAM(7) = 1 and BMAT = 'G' are incompatable. +c = -11: IPARAM(7) = 1 and BMAT = 'G' are incompatible. c = -12: IPARAM(1) must be equal to 0 or 1. c = -9999: Could not build an Arnoldi factorization. c IPARAM(5) returns the size of the current Arnoldi @@ -285,31 +285,31 @@ c Mode = 3 and 4. After convergence, approximate eigenvalues of the c original problem may be obtained with the ARPACK subroutine sneupd. c -c 2. If a basis for the invariant subspace corresponding to the converged Ritz -c values is needed, the user must call sneupd immediately following +c 2. If a basis for the invariant subspace corresponding to the converged Ritz +c values is needed, the user must call sneupd immediately following c completion of snaupd. This is new starting with release 2 of ARPACK. c c 3. If M can be factored into a Cholesky factorization M = LL` c then Mode = 2 should not be selected. Instead one should use -c Mode = 1 with OP = inv(L)*A*inv(L`). Appropriate triangular +c Mode = 1 with OP = inv(L)*A*inv(L`). Appropriate triangular c linear systems should be solved with L and L` rather c than computing inverses. After convergence, an approximate c eigenvector z of the original problem is recovered by solving c L`z = x where x is a Ritz vector of OP. c c 4. At present there is no a-priori analysis to guide the selection -c of NCV relative to NEV. The only formal requrement is that NCV > NEV + 2. +c of NCV relative to NEV. The only formal requirement is that NCV > NEV + 2. c However, it is recommended that NCV .ge. 2*NEV+1. If many problems of c the same type are to be solved, one should experiment with increasing -c NCV while keeping NEV fixed for a given test problem. This will +c NCV while keeping NEV fixed for a given test problem. This will c usually decrease the required number of OP*x operations but it c also increases the work and storage required to maintain the orthogonal c basis vectors. The optimal "cross-over" with respect to CPU time -c is problem dependent and must be determined empirically. +c is problem dependent and must be determined empirically. c See Chapter 8 of Reference 2 for further information. c -c 5. When IPARAM(1) = 0, and IDO = 3, the user needs to provide the -c NP = IPARAM(8) real and imaginary parts of the shifts in locations +c 5. When IPARAM(1) = 0, and IDO = 3, the user needs to provide the +c NP = IPARAM(8) real and imaginary parts of the shifts in locations c real part imaginary part c ----------------------- -------------- c 1 WORKL(IPNTR(14)) WORKL(IPNTR(14)+NP) @@ -319,10 +319,10 @@ c . . c NP WORKL(IPNTR(14)+NP-1) WORKL(IPNTR(14)+2*NP-1). c -c Only complex conjugate pairs of shifts may be applied and the pairs -c must be placed in consecutive locations. The real part of the -c eigenvalues of the current upper Hessenberg matrix are located in -c WORKL(IPNTR(6)) through WORKL(IPNTR(6)+NCV-1) and the imaginary part +c Only complex conjugate pairs of shifts may be applied and the pairs +c must be placed in consecutive locations. The real part of the +c eigenvalues of the current upper Hessenberg matrix are located in +c WORKL(IPNTR(6)) through WORKL(IPNTR(6)+NCV-1) and the imaginary part c in WORKL(IPNTR(7)) through WORKL(IPNTR(7)+NCV-1). They are ordered c according to the order defined by WHICH. The complex conjugate c pairs are kept together and the associated Ritz estimates are located in @@ -330,7 +330,7 @@ c c----------------------------------------------------------------------- c -c\Data Distribution Note: +c\Data Distribution Note: c c Fortran-D syntax: c ================ @@ -349,10 +349,10 @@ c Real resid(n), v(ldv,ncv), workd(n,3), workl(lworkl) c shared resid(block), v(block,:), workd(block,:) c replicated workl(lworkl) -c +c c CM2/CM5 syntax: c ============== -c +c c----------------------------------------------------------------------- c c include 'ex-nonsym.doc' @@ -368,7 +368,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c 3. B.N. Parlett & Y. Saad, "Complex Shift and Invert Strategies for @@ -388,13 +388,13 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas -c +c Rice University +c Houston, Texas +c c\Revision history: c 12/16/93: Version '1.1' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: naupd.F SID: 2.8 DATE OF SID: 04/10/01 RELEASE: 2 c c\Remarks @@ -404,7 +404,7 @@ c----------------------------------------------------------------------- c subroutine snaupd - & ( ido, bmat, n, which, nev, tol, resid, ncv, v, ldv, iparam, + & ( ido, bmat, n, which, nev, tol, resid, ncv, v, ldv, iparam, & ipntr, workd, workl, lworkl, info ) c c %----------------------------------------------------% @@ -420,7 +420,7 @@ subroutine snaupd c character bmat*1, which*2 integer ido, info, ldv, lworkl, n, ncv, nev - Real + Real & tol c c %-----------------% @@ -428,14 +428,14 @@ subroutine snaupd c %-----------------% c integer iparam(11), ipntr(14) - Real + Real & resid(n), v(ldv,ncv), workd(3*n), workl(lworkl) c c %------------% c | Parameters | c %------------% c - Real + Real & one, zero parameter (one = 1.0E+0 , zero = 0.0E+0 ) c @@ -443,7 +443,7 @@ subroutine snaupd c | Local Scalars | c %---------------% c - integer bounds, ierr, ih, iq, ishift, iupd, iw, + integer bounds, ierr, ih, iq, ishift, iupd, iw, & ldh, ldq, levec, mode, msglvl, mxiter, nb, & nev0, next, np, ritzi, ritzr, j save bounds, ih, iq, ishift, iupd, iw, ldh, ldq, @@ -460,16 +460,16 @@ subroutine snaupd c | External Functions | c %--------------------% c - Real + Real & slamch external slamch c c %-----------------------% c | Executable Statements | c %-----------------------% -c +c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -523,7 +523,7 @@ subroutine snaupd else if (ishift .lt. 0 .or. ishift .gt. 1) then ierr = -12 end if -c +c c %------------% c | Error Exit | c %------------% @@ -533,7 +533,7 @@ subroutine snaupd ido = 99 go to 9000 end if -c +c c %------------------------% c | Set default parameters | c %------------------------% @@ -549,8 +549,8 @@ subroutine snaupd c %----------------------------------------------% c np = ncv - nev - nev0 = nev -c + nev0 = nev +c c %-----------------------------% c | Zero out internal workspace | c %-----------------------------% @@ -558,7 +558,7 @@ subroutine snaupd do 10 j = 1, 3*ncv**2 + 6*ncv workl(j) = zero 10 continue -c +c c %-------------------------------------------------------------% c | Pointer into WORKL for address of H, RITZ, BOUNDS, Q | c | etc... and the remaining workspace. | @@ -591,7 +591,7 @@ subroutine snaupd ipntr(6) = ritzr ipntr(7) = ritzi ipntr(8) = bounds - ipntr(14) = iw + ipntr(14) = iw c end if c @@ -599,12 +599,12 @@ subroutine snaupd c | Carry out the Implicitly restarted Arnoldi Iteration. | c %-------------------------------------------------------% c - call snaup2 + call snaup2 & ( ido, bmat, n, which, nev0, np, tol, resid, mode, iupd, - & ishift, mxiter, v, ldv, workl(ih), ldh, workl(ritzr), - & workl(ritzi), workl(bounds), workl(iq), ldq, workl(iw), + & ishift, mxiter, v, ldv, workl(ih), ldh, workl(ritzr), + & workl(ritzi), workl(bounds), workl(iq), ldq, workl(iw), & ipntr, workd, info ) -c +c c %--------------------------------------------------% c | ido .ne. 99 implies use of reverse communication | c | to compute operations involving OP or shifts. | @@ -612,7 +612,7 @@ subroutine snaupd c if (ido .eq. 3) iparam(8) = np if (ido .ne. 99) go to 9000 -c +c iparam(3) = mxiter iparam(5) = np iparam(9) = nopx @@ -628,15 +628,15 @@ subroutine snaupd if (info .eq. 2) info = 3 c if (msglvl .gt. 0) then - call ivout (logfil, 1, mxiter, ndigit, + call ivout (logfil, 1, [mxiter], ndigit, & '_naupd: Number of update iterations taken') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_naupd: Number of wanted "converged" Ritz values') - call svout (logfil, np, workl(ritzr), ndigit, + call svout (logfil, np, workl(ritzr), ndigit, & '_naupd: Real part of the final Ritz values') - call svout (logfil, np, workl(ritzi), ndigit, + call svout (logfil, np, workl(ritzi), ndigit, & '_naupd: Imaginary part of the final Ritz values') - call svout (logfil, np, workl(bounds), ndigit, + call svout (logfil, np, workl(bounds), ndigit, & '_naupd: Associated Ritz estimates') end if c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snconv.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snconv.f index 2ea7b1ee220a..af94700a9358 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snconv.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/snconv.f @@ -3,7 +3,7 @@ c c\Name: snconv c -c\Description: +c\Description: c Convergence testing for the nonsymmetric Arnoldi eigenvalue routine. c c\Usage: @@ -44,16 +44,16 @@ c c\Author c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University +c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.1' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: nconv.F SID: 2.3 DATE OF SID: 4/20/96 RELEASE: 2 c c\Remarks @@ -106,7 +106,7 @@ subroutine snconv (n, ritzr, ritzi, bounds, tol, nconv) c %-----------------------% c | Executable Statements | c %-----------------------% -c +c c %-------------------------------------------------------------% c | Convergence test: unlike in the symmetric code, I am not | c | using things like refined error bounds and gap condition | @@ -133,10 +133,10 @@ subroutine snconv (n, ritzr, ritzi, bounds, tol, nconv) temp = max( eps23, slapy2( ritzr(i), ritzi(i) ) ) if (bounds(i) .le. tol*temp) nconv = nconv + 1 20 continue -c +c call arscnd (t1) tnconv = tnconv + (t1 - t0) -c +c return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sneigh.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sneigh.f index edb307d75e24..7ffb48658ec3 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sneigh.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sneigh.f @@ -13,7 +13,7 @@ c c\Arguments c RNORM Real scalar. (INPUT) -c Residual norm corresponding to the current upper Hessenberg +c Residual norm corresponding to the current upper Hessenberg c matrix H. c c N Integer. (INPUT) @@ -27,13 +27,13 @@ c program. c c RITZR, Real arrays of length N. (OUTPUT) -c RITZI On output, RITZR(1:N) (resp. RITZI(1:N)) contains the real +c RITZI On output, RITZR(1:N) (resp. RITZI(1:N)) contains the real c (respectively imaginary) parts of the eigenvalues of H. c c BOUNDS Real array of length N. (OUTPUT) c On output, BOUNDS contains the Ritz estimates associated with -c the eigenvalues RITZR and RITZI. This is equal to RNORM -c times the last components of the eigenvectors corresponding +c the eigenvalues RITZR and RITZI. This is equal to RNORM +c times the last components of the eigenvectors corresponding c to the eigenvalues in RITZR and RITZI. c c Q Real N by N array. (WORKSPACE) @@ -61,7 +61,7 @@ c xxxxxx real c c\Routines called: -c slahqr ARPACK routine to compute the real Schur form of an +c slahqr LAPACK routine to compute the real Schur form of an c upper Hessenberg matrix and last row of the Schur vectors. c arscnd ARPACK utility routine for timing. c smout ARPACK utility routine that prints matrices @@ -74,20 +74,20 @@ c scopy Level 1 BLAS that copies one vector to another . c snrm2 Level 1 BLAS that computes the norm of a vector. c sscal Level 1 BLAS that scales a vector. -c +c c c\Author c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.1' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: neigh.F SID: 2.3 DATE OF SID: 4/20/96 RELEASE: 2 c c\Remarks @@ -97,7 +97,7 @@ c c----------------------------------------------------------------------- c - subroutine sneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, + subroutine sneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, & q, ldq, workl, ierr) c c %----------------------------------------------------% @@ -112,32 +112,32 @@ subroutine sneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, c %------------------% c integer ierr, n, ldh, ldq - Real + Real & rnorm c c %-----------------% c | Array Arguments | c %-----------------% c - Real + Real & bounds(n), h(ldh,n), q(ldq,n), ritzi(n), ritzr(n), & workl(n*(n+3)) -c +c c %------------% c | Parameters | c %------------% c - Real + Real & one, zero parameter (one = 1.0E+0, zero = 0.0E+0) -c +c c %------------------------% c | Local Scalars & Arrays | c %------------------------% c logical select(1) integer i, iconj, msglvl - Real + Real & temp, vl(1) c c %----------------------% @@ -172,12 +172,12 @@ subroutine sneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, c call arscnd (t0) msglvl = mneigh -c +c if (msglvl .gt. 2) then - call smout (logfil, n, n, h, ldh, ndigit, + call smout (logfil, n, n, h, ldh, ndigit, & '_neigh: Entering upper Hessenberg matrix H ') end if -c +c c %-----------------------------------------------------------% c | 1. Compute the eigenvalues, the last components of the | c | corresponding Schur vectors and the full Schur form T | @@ -231,7 +231,7 @@ subroutine sneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, c %----------------------% c | Real eigenvalue case | c %----------------------% -c +c temp = snrm2( n, q(1,i), 1 ) call sscal ( n, one / temp, q(1,i), 1 ) else @@ -245,7 +245,7 @@ subroutine sneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, c %-------------------------------------------% c if (iconj .eq. 0) then - temp = slapy2( snrm2( n, q(1,i), 1 ), + temp = slapy2( snrm2( n, q(1,i), 1 ), & snrm2( n, q(1,i+1), 1 ) ) call sscal ( n, one / temp, q(1,i), 1 ) call sscal ( n, one / temp, q(1,i+1), 1 ) @@ -253,7 +253,7 @@ subroutine sneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, else iconj = 0 end if - end if + end if 10 continue c call sgemv ('T', n, n, one, q, ldq, bounds, 1, zero, workl, 1) @@ -274,7 +274,7 @@ subroutine sneigh (rnorm, n, h, ldh, ritzr, ritzi, bounds, c %----------------------% c | Real eigenvalue case | c %----------------------% -c +c bounds(i) = rnorm * abs( workl(i) ) else c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sneupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sneupd.f index 1f86d11fd9dd..1c2c7ce16ce7 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sneupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sneupd.f @@ -2,7 +2,7 @@ c c\Name: sneupd c -c\Description: +c\Description: c c This subroutine returns the converged approximations to eigenvalues c of A*z = lambda*B*z and (optionally): @@ -28,34 +28,34 @@ c invariant subspace corresponding to these Ritz values is referred to as a c Schur basis. c -c See documentation in the header of the subroutine SNAUPD for +c See documentation in the header of the subroutine SNAUPD for c definition of OP as well as other terms and the relation of computed c Ritz values and Ritz vectors of OP with respect to the given problem -c A*z = lambda*B*z. For a brief description, see definitions of +c A*z = lambda*B*z. For a brief description, see definitions of c IPARAM(7), MODE and WHICH in the documentation of SNAUPD. c c\Usage: -c call sneupd -c ( RVEC, HOWMNY, SELECT, DR, DI, Z, LDZ, SIGMAR, SIGMAI, WORKEV, BMAT, -c N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, WORKD, WORKL, +c call sneupd +c ( RVEC, HOWMNY, SELECT, DR, DI, Z, LDZ, SIGMAR, SIGMAI, WORKEV, BMAT, +c N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, WORKD, WORKL, c LWORKL, INFO ) c c\Arguments: -c RVEC LOGICAL (INPUT) -c Specifies whether a basis for the invariant subspace corresponding -c to the converged Ritz value approximations for the eigenproblem +c RVEC LOGICAL (INPUT) +c Specifies whether a basis for the invariant subspace corresponding +c to the converged Ritz value approximations for the eigenproblem c A*z = lambda*B*z is computed. c c RVEC = .FALSE. Compute Ritz values only. c c RVEC = .TRUE. Compute the Ritz vectors or Schur vectors. -c See Remarks below. -c -c HOWMNY Character*1 (INPUT) -c Specifies the form of the basis for the invariant subspace +c See Remarks below. +c +c HOWMNY Character*1 (INPUT) +c Specifies the form of the basis for the invariant subspace c corresponding to the converged Ritz values that is to be computed. c -c = 'A': Compute NEV Ritz vectors; +c = 'A': Compute NEV Ritz vectors; c = 'P': Compute NEV Schur vectors; c = 'S': compute some of the Ritz vectors, specified c by the logical array SELECT. @@ -63,43 +63,43 @@ c SELECT Logical array of dimension NCV. (INPUT) c If HOWMNY = 'S', SELECT specifies the Ritz vectors to be c computed. To select the Ritz vector corresponding to a -c Ritz value (DR(j), DI(j)), SELECT(j) must be set to .TRUE.. +c Ritz value (DR(j), DI(j)), SELECT(j) must be set to .TRUE.. c If HOWMNY = 'A' or 'P', SELECT is used as internal workspace. c c DR Real array of dimension NEV+1. (OUTPUT) -c If IPARAM(7) = 1,2 or 3 and SIGMAI=0.0 then on exit: DR contains -c the real part of the Ritz approximations to the eigenvalues of -c A*z = lambda*B*z. +c If IPARAM(7) = 1,2 or 3 and SIGMAI=0.0 then on exit: DR contains +c the real part of the Ritz approximations to the eigenvalues of +c A*z = lambda*B*z. c If IPARAM(7) = 3, 4 and SIGMAI is not equal to zero, then on exit: -c DR contains the real part of the Ritz values of OP computed by +c DR contains the real part of the Ritz values of OP computed by c SNAUPD. A further computation must be performed by the user c to transform the Ritz values computed for OP by SNAUPD to those c of the original system A*z = lambda*B*z. See remark 3 below. c c DI Real array of dimension NEV+1. (OUTPUT) -c On exit, DI contains the imaginary part of the Ritz value +c On exit, DI contains the imaginary part of the Ritz value c approximations to the eigenvalues of A*z = lambda*B*z associated c with DR. c -c NOTE: When Ritz values are complex, they will come in complex -c conjugate pairs. If eigenvectors are requested, the -c corresponding Ritz vectors will also come in conjugate -c pairs and the real and imaginary parts of these are -c represented in two consecutive columns of the array Z +c NOTE: When Ritz values are complex, they will come in complex +c conjugate pairs. If eigenvectors are requested, the +c corresponding Ritz vectors will also come in conjugate +c pairs and the real and imaginary parts of these are +c represented in two consecutive columns of the array Z c (see below). c c Z Real N by NEV+1 array if RVEC = .TRUE. and HOWMNY = 'A'. (OUTPUT) -c On exit, if RVEC = .TRUE. and HOWMNY = 'A', then the columns of -c Z represent approximate eigenvectors (Ritz vectors) corresponding -c to the NCONV=IPARAM(5) Ritz values for eigensystem -c A*z = lambda*B*z. -c -c The complex Ritz vector associated with the Ritz value -c with positive imaginary part is stored in two consecutive -c columns. The first column holds the real part of the Ritz -c vector and the second column holds the imaginary part. The -c Ritz vector associated with the Ritz value with negative -c imaginary part is simply the complex conjugate of the Ritz vector +c On exit, if RVEC = .TRUE. and HOWMNY = 'A', then the columns of +c Z represent approximate eigenvectors (Ritz vectors) corresponding +c to the NCONV=IPARAM(5) Ritz values for eigensystem +c A*z = lambda*B*z. +c +c The complex Ritz vector associated with the Ritz value +c with positive imaginary part is stored in two consecutive +c columns. The first column holds the real part of the Ritz +c vector and the second column holds the imaginary part. The +c Ritz vector associated with the Ritz value with negative +c imaginary part is simply the complex conjugate of the Ritz vector c associated with the positive imaginary part. c c If RVEC = .FALSE. or HOWMNY = 'P', then Z is not referenced. @@ -114,11 +114,11 @@ c desired, then LDZ >= max( 1, N ). In any case, LDZ >= 1. c c SIGMAR Real (INPUT) -c If IPARAM(7) = 3 or 4, represents the real part of the shift. +c If IPARAM(7) = 3 or 4, represents the real part of the shift. c Not referenced if IPARAM(7) = 1 or 2. c c SIGMAI Real (INPUT) -c If IPARAM(7) = 3 or 4, represents the imaginary part of the shift. +c If IPARAM(7) = 3 or 4, represents the imaginary part of the shift. c Not referenced if IPARAM(7) = 1 or 2. See remark 3 below. c c WORKEV Real work array of dimension 3*NCV. (WORKSPACE) @@ -183,10 +183,10 @@ c c = 1: The Schur form computed by LAPACK routine slahqr c could not be reordered by LAPACK routine strsen. -c Re-enter subroutine sneupd with IPARAM(5)=NCV and -c increase the size of the arrays DR and DI to have -c dimension at least dimension NCV and allocate at least NCV -c columns for Z. NOTE: Not necessary if Z and V share +c Re-enter subroutine sneupd with IPARAM(5)=NCV and +c increase the size of the arrays DR and DI to have +c dimension at least dimension NCV and allocate at least NCV +c columns for Z. NOTE: Not necessary if Z and V share c the same space. Please notify the authors if this error c occurs. c @@ -218,7 +218,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c 3. B.N. Parlett & Y. Saad, "Complex Shift and Invert Strategies for @@ -229,7 +229,7 @@ c ivout ARPACK utility routine that prints integers. c smout ARPACK utility routine that prints matrices c svout ARPACK utility routine that prints vectors. -c sgeqr2 LAPACK routine that computes the QR factorization of +c sgeqr2 LAPACK routine that computes the QR factorization of c a matrix. c slacpy LAPACK matrix copy routine. c slahqr LAPACK routine to compute the real Schur form of an @@ -237,7 +237,7 @@ c slamch LAPACK routine that determines machine constants. c slapy2 LAPACK routine to compute sqrt(x**2+y**2) carefully. c slaset LAPACK matrix initialization routine. -c sorm2r LAPACK routine that applies an orthogonal matrix in +c sorm2r LAPACK routine that applies an orthogonal matrix in c factored form. c strevc LAPACK routine to compute the eigenvectors of a matrix c in upper quasi-triangular form. @@ -259,10 +259,10 @@ c Ritz vectors. Thus, their numerical properties are often superior. c If RVEC = .TRUE. then the relationship c A * V(:,1:IPARAM(5)) = V(:,1:IPARAM(5)) * T, and -c trans(V(:,1:IPARAM(5))) * V(:,1:IPARAM(5)) = I are approximately -c satisfied. Here T is the leading submatrix of order IPARAM(5) of the +c trans(V(:,1:IPARAM(5))) * V(:,1:IPARAM(5)) = I are approximately +c satisfied. Here T is the leading submatrix of order IPARAM(5) of the c real upper quasi-triangular matrix stored workl(ipntr(12)). That is, -c T is block upper triangular with 1-by-1 and 2-by-2 diagonal blocks; +c T is block upper triangular with 1-by-1 and 2-by-2 diagonal blocks; c each 2-by-2 diagonal block has its diagonal elements equal and its c off-diagonal elements of opposite sign. Corresponding to each 2-by-2 c diagonal block is a complex conjugate pair of Ritz values. The real @@ -270,14 +270,14 @@ c c 3. If IPARAM(7) = 3 or 4 and SIGMAI is not equal zero, then the user must c form the IPARAM(5) Rayleigh quotients in order to transform the Ritz -c values computed by SNAUPD for OP to those of A*z = lambda*B*z. +c values computed by SNAUPD for OP to those of A*z = lambda*B*z. c Set RVEC = .true. and HOWMNY = 'A', and -c compute +c compute c trans(Z(:,I)) * A * Z(:,I) if DI(I) = 0. -c If DI(I) is not equal to zero and DI(I+1) = - D(I), +c If DI(I) is not equal to zero and DI(I+1) = - D(I), c then the desired real and imaginary parts of the Ritz value are c trans(Z(:,I)) * A * Z(:,I) + trans(Z(:,I+1)) * A * Z(:,I+1), -c trans(Z(:,I)) * A * Z(:,I+1) - trans(Z(:,I+1)) * A * Z(:,I), +c trans(Z(:,I)) * A * Z(:,I+1) - trans(Z(:,I+1)) * A * Z(:,I), c respectively. c Another possibility is to set RVEC = .true. and HOWMNY = 'P' and c compute trans(V(:,1:IPARAM(5))) * A * V(:,1:IPARAM(5)) and then an upper @@ -286,20 +286,20 @@ c c\Authors c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University +c Richard Lehoucq CRPC / Rice University c Chao Yang Houston, Texas c Dept. of Computational & -c Applied Mathematics -c Rice University -c Houston, Texas -c -c\SCCS Information: @(#) -c FILE: neupd.F SID: 2.7 DATE OF SID: 09/20/00 RELEASE: 2 +c Applied Mathematics +c Rice University +c Houston, Texas +c +c\SCCS Information: @(#) +c FILE: neupd.F SID: 2.7 DATE OF SID: 09/20/00 RELEASE: 2 c c\EndLib c c----------------------------------------------------------------------- - subroutine sneupd(rvec , howmny, select, dr , di, + subroutine sneupd(rvec , howmny, select, dr , di, & z , ldz , sigmar, sigmai, workev, & bmat , n , which , nev , tol, & resid, ncv , v , ldv , iparam, @@ -319,7 +319,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, character bmat, howmny, which*2 logical rvec integer info, ldz, ldv, lworkl, n, ncv, nev - Real + Real & sigmar, sigmai, tol c c %-----------------% @@ -328,16 +328,16 @@ subroutine sneupd(rvec , howmny, select, dr , di, c integer iparam(11), ipntr(14) logical select(ncv) - Real - & dr(nev+1) , di(nev+1), resid(n) , - & v(ldv,ncv) , z(ldz,*) , workd(3*n), + Real + & dr(nev+1) , di(nev+1), resid(n) , + & v(ldv,ncv) , z(ldz,*) , workd(3*n), & workl(lworkl), workev(3*ncv) c c %------------% c | Parameters | c %------------% c - Real + Real & one, zero parameter (one = 1.0E+0 , zero = 0.0E+0 ) c @@ -346,8 +346,8 @@ subroutine sneupd(rvec , howmny, select, dr , di, c %---------------% c character type*6 - integer bounds, ierr , ih , ihbds , - & iheigr, iheigi, iconj , nconv , + integer bounds, ierr , ih , ihbds , + & iheigr, iheigi, iconj , nconv , & invsub, iuptri, iwev , iwork(1), & j , k , ldh , ldq , & mode , msglvl, outncv, ritzr , @@ -355,7 +355,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, & iri , ibd , ishift, numcnv , & np , jj , nconv2 logical reord - Real + Real & conds , rnorm, sep , temp, & vl(1,1), temp1, eps23 c @@ -363,16 +363,16 @@ subroutine sneupd(rvec , howmny, select, dr , di, c | External Subroutines | c %----------------------% c - external scopy , sger , sgeqr2, slacpy, - & slahqr, slaset, smout , sorm2r, - & strevc, strmm , strsen, sscal , + external scopy , sger , sgeqr2, slacpy, + & slahqr, slaset, smout , sorm2r, + & strevc, strmm , strsen, sscal , & svout , ivout c c %--------------------% c | External Functions | c %--------------------% c - Real + Real & slapy2, snrm2, slamch, sdot external slapy2, snrm2, slamch, sdot c @@ -385,7 +385,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, c %-----------------------% c | Executable Statements | c %-----------------------% -c +c c %------------------------% c | Set default parameters | c %------------------------% @@ -434,7 +434,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, else if (howmny .eq. 'S' ) then ierr = -12 end if -c +c if (mode .eq. 1 .or. mode .eq. 2) then type = 'REGULR' else if (mode .eq. 3 .and. sigmai .eq. zero) then @@ -443,7 +443,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, type = 'REALPT' else if (mode .eq. 4 ) then type = 'IMAGPT' - else + else ierr = -10 end if if (mode .eq. 1 .and. bmat .eq. 'G') ierr = -11 @@ -456,7 +456,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, info = ierr go to 9000 end if -c +c c %--------------------------------------------------------% c | Pointer into WORKL for address of H, RITZ, BOUNDS, Q | c | etc... and the remaining workspace. | @@ -483,7 +483,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, c | subspace for H. | c | GRAND total of NCV * ( 3 * NCV + 6 ) locations. | c %-----------------------------------------------------------% -c +c ih = ipntr(5) ritzr = ipntr(6) ritzi = ipntr(7) @@ -537,7 +537,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, end if c if (rvec) then -c +c reord = .false. c c %---------------------------------------------------% @@ -562,7 +562,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, c np = ncv - nev ishift = 0 - call sngets(ishift , which , nev , + call sngets(ishift , which , nev , & np , workl(irr), workl(iri), & workl(bounds), workl , workl(np+1)) c @@ -601,9 +601,9 @@ subroutine sneupd(rvec , howmny, select, dr , di, c %-----------------------------------------------------------% c if (msglvl .gt. 2) then - call ivout(logfil, 1, numcnv, ndigit, + call ivout(logfil, 1, [numcnv], ndigit, & '_neupd: Number of specified eigenvalues') - call ivout(logfil, 1, nconv, ndigit, + call ivout(logfil, 1, [nconv], ndigit, & '_neupd: Number of "converged" eigenvalues') end if c @@ -618,24 +618,24 @@ subroutine sneupd(rvec , howmny, select, dr , di, c | Make a copy of the upper Hessenberg matrix. | c | Initialize the Schur vector matrix Q to the identity. | c %-----------------------------------------------------------% -c +c call scopy(ldh*ncv, workl(ih), 1, workl(iuptri), 1) - call slaset('All', ncv, ncv, + call slaset('All', ncv, ncv, & zero , one, workl(invsub), & ldq) - call slahqr(.true., .true. , ncv, - & 1 , ncv , workl(iuptri), + call slahqr(.true., .true. , ncv, + & 1 , ncv , workl(iuptri), & ldh , workl(iheigr), workl(iheigi), - & 1 , ncv , workl(invsub), + & 1 , ncv , workl(invsub), & ldq , ierr) - call scopy(ncv , workl(invsub+ncv-1), ldq, + call scopy(ncv , workl(invsub+ncv-1), ldq, & workl(ihbds), 1) -c +c if (ierr .ne. 0) then info = -8 go to 9000 end if -c +c if (msglvl .gt. 1) then call svout(logfil, ncv, workl(iheigr), ndigit, & '_neupd: Real part of the eigenvalues of H') @@ -644,25 +644,25 @@ subroutine sneupd(rvec , howmny, select, dr , di, call svout(logfil, ncv, workl(ihbds), ndigit, & '_neupd: Last row of the Schur vector matrix') if (msglvl .gt. 3) then - call smout(logfil , ncv, ncv , + call smout(logfil , ncv, ncv , & workl(iuptri), ldh, ndigit, & '_neupd: The upper quasi-triangular matrix ') end if - end if + end if c if (reord) then -c +c c %-----------------------------------------------------% -c | Reorder the computed upper quasi-triangular matrix. | +c | Reorder the computed upper quasi-triangular matrix. | c %-----------------------------------------------------% -c - call strsen('None' , 'V' , +c + call strsen('None' , 'V' , & select , ncv , - & workl(iuptri), ldh , - & workl(invsub), ldq , - & workl(iheigr), workl(iheigi), + & workl(iuptri), ldh , + & workl(invsub), ldq , + & workl(iheigr), workl(iheigi), & nconv2 , conds , - & sep , workl(ihbds) , + & sep , workl(ihbds) , & ncv , iwork , & 1 , ierr) c @@ -681,12 +681,12 @@ subroutine sneupd(rvec , howmny, select, dr , di, call svout(logfil, ncv, workl(iheigi), ndigit, & '_neupd: Imag part of the eigenvalues of H--reordered') if (msglvl .gt. 3) then - call smout(logfil , ncv, ncv , + call smout(logfil , ncv, ncv , & workl(iuptri), ldq, ndigit, & '_neupd: Quasi-triangular matrix after re-ordering') end if end if -c +c end if c c %---------------------------------------% @@ -703,23 +703,23 @@ subroutine sneupd(rvec , howmny, select, dr , di, c | if a spectral transformation was not used. | c %----------------------------------------------------% c - if (type .eq. 'REGULR') then + if (type .eq. 'REGULR') then call scopy(nconv, workl(iheigr), 1, dr, 1) call scopy(nconv, workl(iheigi), 1, di, 1) end if -c +c c %----------------------------------------------------------% c | Compute the QR factorization of the matrix representing | c | the wanted invariant subspace located in the first NCONV | c | columns of workl(invsub,ldq). | c %----------------------------------------------------------% -c - call sgeqr2(ncv, nconv , workl(invsub), +c + call sgeqr2(ncv, nconv , workl(invsub), & ldq, workev, workev(ncv+1), & ierr) c c %---------------------------------------------------------% -c | * Postmultiply V by Q using sorm2r. | +c | * Postmultiply V by Q using sorm2r. | c | * Copy the first NCONV columns of VQ into Z. | c | * Postmultiply Z by R. | c | The N by NCONV matrix Z is now a matrix representation | @@ -729,15 +729,15 @@ subroutine sneupd(rvec , howmny, select, dr , di, c | vectors associated with the real upper quasi-triangular | c | matrix of order NCONV in workl(iuptri) | c %---------------------------------------------------------% -c - call sorm2r('Right', 'Notranspose', n , +c + call sorm2r('Right', 'Notranspose', n , & ncv , nconv , workl(invsub), - & ldq , workev , v , + & ldq , workev , v , & ldv , workd(n+1) , ierr) call slacpy('All', n, nconv, v, ldv, z, ldz) c do 20 j=1, nconv -c +c c %---------------------------------------------------% c | Perform both a column and row scaling if the | c | diagonal element of workl(invsub,ldq) is negative | @@ -746,21 +746,21 @@ subroutine sneupd(rvec , howmny, select, dr , di, c | Note that since Q is orthogonal, R is a diagonal | c | matrix consisting of plus or minus ones | c %---------------------------------------------------% -c +c if (workl(invsub+(j-1)*ldq+j-1) .lt. zero) then call sscal(nconv, -one, workl(iuptri+j-1), ldq) call sscal(nconv, -one, workl(iuptri+(j-1)*ldq), 1) end if -c +c 20 continue -c +c if (howmny .eq. 'A') then -c +c c %--------------------------------------------% -c | Compute the NCONV wanted eigenvectors of T | +c | Compute the NCONV wanted eigenvectors of T | c | located in workl(iuptri,ldq). | c %--------------------------------------------% -c +c do 30 j=1, ncv if (j .le. nconv) then select(j) = .true. @@ -769,8 +769,8 @@ subroutine sneupd(rvec , howmny, select, dr , di, end if 30 continue c - call strevc('Right', 'Select' , select , - & ncv , workl(iuptri), ldq , + call strevc('Right', 'Select' , select , + & ncv , workl(iuptri), ldq , & vl , 1 , workl(invsub), & ldq , ncv , outncv , & workev , ierr) @@ -779,7 +779,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, info = -9 go to 9000 end if -c +c c %------------------------------------------------% c | Scale the returning eigenvectors so that their | c | Euclidean norms are all one. LAPACK subroutine | @@ -787,22 +787,22 @@ subroutine sneupd(rvec , howmny, select, dr , di, c | that the element of largest magnitude has | c | magnitude 1; | c %------------------------------------------------% -c +c iconj = 0 do 40 j=1, nconv c if ( workl(iheigi+j-1) .eq. zero ) then -c +c c %----------------------% c | real eigenvalue case | c %----------------------% -c +c temp = snrm2( ncv, workl(invsub+(j-1)*ldq), 1 ) - call sscal( ncv, one / temp, + call sscal( ncv, one / temp, & workl(invsub+(j-1)*ldq), 1 ) c else -c +c c %-------------------------------------------% c | Complex conjugate pair case. Note that | c | since the real and imaginary part of | @@ -812,15 +812,15 @@ subroutine sneupd(rvec , howmny, select, dr , di, c %-------------------------------------------% c if (iconj .eq. 0) then - temp = slapy2(snrm2(ncv, - & workl(invsub+(j-1)*ldq), + temp = slapy2(snrm2(ncv, + & workl(invsub+(j-1)*ldq), & 1), - & snrm2(ncv, + & snrm2(ncv, & workl(invsub+j*ldq), - & 1)) - call sscal(ncv, one/temp, + & 1)) + call sscal(ncv, one/temp, & workl(invsub+(j-1)*ldq), 1 ) - call sscal(ncv, one/temp, + call sscal(ncv, one/temp, & workl(invsub+j*ldq), 1 ) iconj = 1 else @@ -860,7 +860,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, call svout(logfil, ncv, workl(ihbds), ndigit, & '_neupd: Last row of the eigenvector matrix for T') if (msglvl .gt. 3) then - call smout(logfil, ncv, ncv, workl(invsub), ldq, + call smout(logfil, ncv, ncv, workl(invsub), ldq, & ndigit, '_neupd: The eigenvector matrix for T') end if end if @@ -876,32 +876,32 @@ subroutine sneupd(rvec , howmny, select, dr , di, c | associated with leading portion of T in the first NCONV | c | columns of workl(invsub,ldq). | c %---------------------------------------------------------% -c - call sgeqr2(ncv, nconv , workl(invsub), +c + call sgeqr2(ncv, nconv , workl(invsub), & ldq, workev, workev(ncv+1), & ierr) -c +c c %----------------------------------------------% -c | * Postmultiply Z by Q. | +c | * Postmultiply Z by Q. | c | * Postmultiply Z by R. | -c | The N by NCONV matrix Z is now contains the | +c | The N by NCONV matrix Z is now contains the | c | Ritz vectors associated with the Ritz values | c | in workl(iheigr) and workl(iheigi). | c %----------------------------------------------% -c +c call sorm2r('Right', 'Notranspose', n , & ncv , nconv , workl(invsub), & ldq , workev , z , & ldz , workd(n+1) , ierr) -c +c call strmm('Right' , 'Upper' , 'No transpose', & 'Non-unit', n , nconv , & one , workl(invsub), ldq , & z , ldz) -c +c end if -c - else +c + else c c %------------------------------------------------------% c | An approximate invariant subspace is not needed. | @@ -914,7 +914,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, call scopy(nconv, workl(ritzi), 1, workl(iheigi), 1) call scopy(nconv, workl(bounds), 1, workl(ihbds), 1) end if -c +c c %------------------------------------------------% c | Transform the Ritz values and possibly vectors | c | and corresponding error bounds of OP to those | @@ -923,26 +923,26 @@ subroutine sneupd(rvec , howmny, select, dr , di, c if (type .eq. 'REGULR') then c - if (rvec) - & call sscal(ncv, rnorm, workl(ihbds), 1) -c - else -c + if (rvec) + & call sscal(ncv, rnorm, workl(ihbds), 1) +c + else +c c %---------------------------------------% c | A spectral transformation was used. | c | * Determine the Ritz estimates of the | c | Ritz values in the original system. | c %---------------------------------------% -c +c if (type .eq. 'SHIFTI') then c - if (rvec) + if (rvec) & call sscal(ncv, rnorm, workl(ihbds), 1) c do 50 k=1, ncv - temp = slapy2( workl(iheigr+k-1), + temp = slapy2( workl(iheigr+k-1), & workl(iheigi+k-1) ) - workl(ihbds+k-1) = abs( workl(ihbds+k-1) ) + workl(ihbds+k-1) = abs( workl(ihbds+k-1) ) & / temp / temp 50 continue c @@ -957,26 +957,26 @@ subroutine sneupd(rvec , howmny, select, dr , di, 70 continue c end if -c +c c %-----------------------------------------------------------% c | * Transform the Ritz values back to the original system. | c | For TYPE = 'SHIFTI' the transformation is | c | lambda = 1/theta + sigma | c | For TYPE = 'REALPT' or 'IMAGPT' the user must from | -c | Rayleigh quotients or a projection. See remark 3 above.| +c | Rayleigh quotients or a projection. See remark 3 above.| c | NOTES: | c | *The Ritz vectors are not affected by the transformation. | c %-----------------------------------------------------------% -c - if (type .eq. 'SHIFTI') then +c + if (type .eq. 'SHIFTI') then c do 80 k=1, ncv - temp = slapy2( workl(iheigr+k-1), + temp = slapy2( workl(iheigr+k-1), & workl(iheigi+k-1) ) - workl(iheigr+k-1) = workl(iheigr+k-1)/temp/temp - & + sigmar + workl(iheigr+k-1) = workl(iheigr+k-1)/temp/temp + & + sigmar workl(iheigi+k-1) = -workl(iheigi+k-1)/temp/temp - & + sigmai + & + sigmai 80 continue c call scopy(nconv, workl(iheigr), 1, dr, 1) @@ -993,9 +993,9 @@ subroutine sneupd(rvec , howmny, select, dr , di, c if (type .eq. 'SHIFTI' .and. msglvl .gt. 1) then call svout(logfil, nconv, dr, ndigit, - & '_neupd: Untransformed real part of the Ritz valuess.') + & '_neupd: Untransformed real part of the Ritz values.') call svout (logfil, nconv, di, ndigit, - & '_neupd: Untransformed imag part of the Ritz valuess.') + & '_neupd: Untransformed imag part of the Ritz values.') call svout(logfil, nconv, workl(ihbds), ndigit, & '_neupd: Ritz estimates of untransformed Ritz values.') else if (type .eq. 'REGULR' .and. msglvl .gt. 1) then @@ -1006,7 +1006,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, call svout(logfil, nconv, workl(ihbds), ndigit, & '_neupd: Associated Ritz estimates.') end if -c +c c %-------------------------------------------------% c | Eigenvector Purification step. Formally perform | c | one of inverse subspace iteration. Only used | @@ -1028,19 +1028,22 @@ subroutine sneupd(rvec , howmny, select, dr , di, c iconj = 0 do 110 j=1, nconv - if (workl(iheigi+j-1) .eq. zero) then + if ((workl(iheigi+j-1) .eq. zero) .and. + & (workl(iheigr+j-1) .ne. zero)) then workev(j) = workl(invsub+(j-1)*ldq+ncv-1) / & workl(iheigr+j-1) else if (iconj .eq. 0) then temp = slapy2( workl(iheigr+j-1), workl(iheigi+j-1) ) - workev(j) = ( workl(invsub+(j-1)*ldq+ncv-1) * - & workl(iheigr+j-1) + - & workl(invsub+j*ldq+ncv-1) * - & workl(iheigi+j-1) ) / temp / temp - workev(j+1) = ( workl(invsub+j*ldq+ncv-1) * - & workl(iheigr+j-1) - - & workl(invsub+(j-1)*ldq+ncv-1) * - & workl(iheigi+j-1) ) / temp / temp + if (temp. ne. zero) then + workev(j) = ( workl(invsub+(j-1)*ldq+ncv-1) * + & workl(iheigr+j-1) + + & workl(invsub+j*ldq+ncv-1) * + & workl(iheigi+j-1) ) / temp / temp + workev(j+1) = ( workl(invsub+j*ldq+ncv-1) * + & workl(iheigr+j-1) - + & workl(invsub+(j-1)*ldq+ncv-1) * + & workl(iheigi+j-1) ) / temp / temp + end if iconj = 1 else iconj = 0 @@ -1059,7 +1062,7 @@ subroutine sneupd(rvec , howmny, select, dr , di, 9000 continue c return -c +c c %---------------% c | End of SNEUPD | c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sngets.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sngets.f index a0452243759b..7e48c0bb18c8 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sngets.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sngets.f @@ -3,9 +3,9 @@ c c\Name: sngets c -c\Description: +c\Description: c Given the eigenvalues of the upper Hessenberg matrix H, -c computes the NP shifts AMU that are zeros of the polynomial of +c computes the NP shifts AMU that are zeros of the polynomial of c degree NP which filters out components of the unwanted eigenvectors c corresponding to the AMU's based on some given criteria. c @@ -42,12 +42,12 @@ c pairs together. c c RITZR, Real array of length KEV+NP. (INPUT/OUTPUT) -c RITZI On INPUT, RITZR and RITZI contain the real and imaginary +c RITZI On INPUT, RITZR and RITZI contain the real and imaginary c parts of the eigenvalues of H. c On OUTPUT, RITZR and RITZI are sorted so that the unwanted c eigenvalues are in the first NP locations and the wanted -c portion is in the last KEV locations. When exact shifts are -c selected, the unwanted part corresponds to the shifts to +c portion is in the last KEV locations. When exact shifts are +c selected, the unwanted part corresponds to the shifts to c be applied. Also, if ISHIFT .eq. 1, the unwanted eigenvalues c are further sorted so that the ones with largest Ritz values c are first. @@ -56,7 +56,7 @@ c Error bounds corresponding to the ordering in RITZ. c c SHIFTR, SHIFTI *** USE deprecated as of version 2.1. *** -c +c c c\EndDoc c @@ -76,13 +76,13 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.1' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: ngets.F SID: 2.3 DATE OF SID: 4/20/96 RELEASE: 2 c c\Remarks @@ -114,7 +114,7 @@ subroutine sngets ( ishift, which, kev, np, ritzr, ritzi, bounds, c %-----------------% c Real - & bounds(kev+np), ritzr(kev+np), ritzi(kev+np), + & bounds(kev+np), ritzr(kev+np), ritzi(kev+np), & shiftr(1), shifti(1) c c %------------% @@ -151,10 +151,10 @@ subroutine sngets ( ishift, which, kev, np, ritzr, ritzi, bounds, c | Initialize timing statistics | c | & message level for debugging | c %-------------------------------% -c +c call arscnd (t0) msglvl = mngets -c +c c %----------------------------------------------------% c | LM, SM, LR, SR, LI, SI case. | c | Sort the eigenvalues of H into the desired order | @@ -178,16 +178,16 @@ subroutine sngets ( ishift, which, kev, np, ritzr, ritzi, bounds, else if (which .eq. 'SI') then call ssortc ('SM', .true., kev+np, ritzr, ritzi, bounds) end if -c +c call ssortc (which, .true., kev+np, ritzr, ritzi, bounds) -c +c c %-------------------------------------------------------% c | Increase KEV by one if the ( ritzr(np),ritzi(np) ) | c | = ( ritzr(np+1),-ritzi(np+1) ) and ritz(np) .ne. zero | c | Accordingly decrease NP by one. In other words keep | c | complex conjugate pairs together. | c %-------------------------------------------------------% -c +c if ( ( ritzr(np+1) - ritzr(np) ) .eq. zero & .and. ( ritzi(np+1) + ritzi(np) ) .eq. zero ) then np = np - 1 @@ -195,7 +195,7 @@ subroutine sngets ( ishift, which, kev, np, ritzr, ritzi, bounds, end if c if ( ishift .eq. 1 ) then -c +c c %-------------------------------------------------------% c | Sort the unwanted Ritz values used as shifts so that | c | the ones with largest Ritz estimates are first | @@ -204,28 +204,28 @@ subroutine sngets ( ishift, which, kev, np, ritzr, ritzi, bounds, c | are applied in subroutine snapps. | c | Be careful and use 'SR' since we want to sort BOUNDS! | c %-------------------------------------------------------% -c +c call ssortc ( 'SR', .true., np, bounds, ritzr, ritzi ) end if -c +c call arscnd (t1) tngets = tngets + (t1 - t0) c if (msglvl .gt. 0) then - call ivout (logfil, 1, kev, ndigit, '_ngets: KEV is') - call ivout (logfil, 1, np, ndigit, '_ngets: NP is') + call ivout (logfil, 1, [kev], ndigit, '_ngets: KEV is') + call ivout (logfil, 1, [np], ndigit, '_ngets: NP is') call svout (logfil, kev+np, ritzr, ndigit, & '_ngets: Eigenvalues of current H matrix -- real part') call svout (logfil, kev+np, ritzi, ndigit, & '_ngets: Eigenvalues of current H matrix -- imag part') - call svout (logfil, kev+np, bounds, ndigit, + call svout (logfil, kev+np, bounds, ndigit, & '_ngets: Ritz estimates of the current KEV+NP Ritz values') end if -c +c return -c +c c %---------------% c | End of sngets | c %---------------% -c +c end diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaitr.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaitr.f index bcce17fc7e4e..a5df2c2ec713 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaitr.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaitr.f @@ -3,8 +3,8 @@ c c\Name: ssaitr c -c\Description: -c Reverse communication interface for applying NP additional steps to +c\Description: +c Reverse communication interface for applying NP additional steps to c a K step symmetric Arnoldi factorization. c c Input: OP*V_{k} - V_{k}*H = r_{k}*e_{k}^T @@ -20,7 +20,7 @@ c c\Usage: c call ssaitr -c ( IDO, BMAT, N, K, NP, MODE, RESID, RNORM, V, LDV, H, LDH, +c ( IDO, BMAT, N, K, NP, MODE, RESID, RNORM, V, LDV, H, LDH, c IPNTR, WORKD, INFO ) c c\Arguments @@ -76,13 +76,13 @@ c On OUTPUT the B-norm of the updated residual r_{k+p}. c c V Real N by K+NP array. (INPUT/OUTPUT) -c On INPUT: V contains the Arnoldi vectors in the first K +c On INPUT: V contains the Arnoldi vectors in the first K c columns. c On OUTPUT: V contains the new NP Arnoldi vectors in the next c NP columns. The first K columns are unchanged. c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c H Real (K+NP) by 2 array. (INPUT/OUTPUT) @@ -91,26 +91,26 @@ c and the main diagonal in the second column. c c LDH Integer. (INPUT) -c Leading dimension of H exactly as declared in the calling +c Leading dimension of H exactly as declared in the calling c program. c c IPNTR Integer array of length 3. (OUTPUT) -c Pointer to mark the starting locations in the WORK for +c Pointer to mark the starting locations in the WORK for c vectors used by the Arnoldi iteration. c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X. c IPNTR(2): pointer to the current result vector Y. -c IPNTR(3): pointer to the vector B * X when used in the +c IPNTR(3): pointer to the vector B * X when used in the c shift-and-invert mode. X is the current operand. c ------------------------------------------------------------- -c +c c WORKD Real work array of length 3*N. (REVERSE COMMUNICATION) c Distributed array to be used in the basic Arnoldi iteration -c for reverse communication. The calling program should not +c for reverse communication. The calling program should not c use WORKD as temporary workspace during the iteration !!!!!! c On INPUT, WORKD(1:N) = B*RESID where RESID is associated -c with the K step Arnoldi factorization. Used to save some -c computation at the first step. +c with the K step Arnoldi factorization. Used to save some +c computation at the first step. c On OUTPUT, WORKD(1:N) = B*RESID where RESID is associated c with the K+NP step Arnoldi factorization. c @@ -139,7 +139,7 @@ c saxpy Level 1 BLAS that computes a vector triad. c sscal Level 1 BLAS that scales a vector. c scopy Level 1 BLAS that copies one vector to another . -c sdot Level 1 BLAS that computes the scalar product of two vectors. +c sdot Level 1 BLAS that computes the scalar product of two vectors. c snrm2 Level 1 BLAS that computes the norm of a vector. c c\Author @@ -147,29 +147,29 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas -c +c Rice University +c Houston, Texas +c c\Revision history: c xx/xx/93: Version ' 2.4' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: saitr.F SID: 2.6 DATE OF SID: 8/28/96 RELEASE: 2 c c\Remarks c The algorithm implemented is: -c +c c restart = .false. -c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; +c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; c r_{k} contains the initial residual vector even for k = 0; -c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already +c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already c computed by the calling program. c c betaj = rnorm ; p_{k+1} = B*r_{k} ; c For j = k+1, ..., k+np Do c 1) if ( betaj < tol ) stop or restart depending on j. c if ( restart ) generate a new starting vector. -c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; +c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; c p_{j} = p_{j}/betaj c 3) r_{j} = OP*v_{j} where OP is defined as in ssaupd c For shift-invert mode p_{j} = B*v_{j} is already available. @@ -184,7 +184,7 @@ c 5) Re-orthogonalization step: c s = V_{j}'*B*r_{j} c r_{j} = r_{j} - V_{j}*s; rnorm1 = || r_{j} || -c alphaj = alphaj + s_{j}; +c alphaj = alphaj + s_{j}; c 6) Iterative refinement step: c If (rnorm1 > 0.717*rnorm) then c rnorm = rnorm1 @@ -194,7 +194,7 @@ c If this is the first time in step 6), go to 5) c Else r_{j} lies in the span of V_{j} numerically. c Set r_{j} = 0 and rnorm = 0; go to 1) -c EndIf +c EndIf c End Do c c\EndLib @@ -202,7 +202,7 @@ c----------------------------------------------------------------------- c subroutine ssaitr - & (ido, bmat, n, k, np, mode, resid, rnorm, v, ldv, h, ldh, + & (ido, bmat, n, k, np, mode, resid, rnorm, v, ldv, h, ldh, & ipntr, workd, info) c c %----------------------------------------------------% @@ -242,7 +242,7 @@ subroutine ssaitr c %---------------% c logical first, orth1, orth2, rstart, step3, step4 - integer i, ierr, ipj, irj, ivj, iter, itry, j, msglvl, + integer i, ierr, ipj, irj, ivj, iter, itry, j, msglvl, & infol, jj Real & rnorm1, wnorm, safmin, temp1 @@ -251,7 +251,7 @@ subroutine ssaitr & rnorm1, safmin, wnorm c c %-----------------------% -c | Local Array Arguments | +c | Local Array Arguments | c %-----------------------% c Real @@ -294,7 +294,7 @@ subroutine ssaitr end if c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -302,7 +302,7 @@ subroutine ssaitr c call arscnd (t0) msglvl = msaitr -c +c c %------------------------------% c | Initial call to this routine | c %------------------------------% @@ -313,14 +313,14 @@ subroutine ssaitr rstart = .false. orth1 = .false. orth2 = .false. -c +c c %--------------------------------% c | Pointer to the current step of | c | the factorization to build | c %--------------------------------% c j = k + 1 -c +c c %------------------------------------------% c | Pointers used for reverse communication | c | when using WORKD. | @@ -330,7 +330,7 @@ subroutine ssaitr irj = ipj + n ivj = irj + n end if -c +c c %-------------------------------------------------% c | When in reverse communication mode one of: | c | STEP3, STEP4, ORTH1, ORTH2, RSTART | @@ -353,7 +353,7 @@ subroutine ssaitr c %------------------------------% c | Else this is the first step. | c %------------------------------% -c +c c %--------------------------------------------------------------% c | | c | A R N O L D I I T E R A T I O N L O O P | @@ -364,15 +364,15 @@ subroutine ssaitr 1000 continue c if (msglvl .gt. 2) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_saitr: generating Arnoldi vector no.') - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_saitr: B-norm of the current residual =') end if -c +c c %---------------------------------------------------------% -c | Check for exact zero. Equivalent to determing whether a | -c | j-step Arnoldi factorization is present. | +c | Check for exact zero. Equivalent to determining whether | +c | a j-step Arnoldi factorization is present. | c %---------------------------------------------------------% c if (rnorm .gt. zero) go to 40 @@ -384,10 +384,10 @@ subroutine ssaitr c %---------------------------------------------------% c if (msglvl .gt. 0) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_saitr: ****** restart at step ******') end if -c +c c %---------------------------------------------% c | ITRY is the loop variable that controls the | c | maximum amount of times that a restart is | @@ -406,7 +406,7 @@ subroutine ssaitr c | RSTART = .true. flow returns here. | c %--------------------------------------% c - call sgetv0 (ido, bmat, itry, .false., n, j, v, ldv, + call sgetv0 (ido, bmat, itry, .false., n, j, v, ldv, & resid, rnorm, ipntr, workd, ierr) if (ido .ne. 99) go to 9000 if (ierr .lt. 0) then @@ -425,7 +425,7 @@ subroutine ssaitr ido = 99 go to 9000 end if -c +c 40 continue c c %---------------------------------------------------------% @@ -447,12 +447,12 @@ subroutine ssaitr c | use LAPACK routine SLASCL | c %-----------------------------------------% c - call slascl ('General', i, i, rnorm, one, n, 1, + call slascl ('General', i, i, rnorm, one, n, 1, & v(1,j), n, infol) - call slascl ('General', i, i, rnorm, one, n, 1, + call slascl ('General', i, i, rnorm, one, n, 1, & workd(ipj), n, infol) end if -c +c c %------------------------------------------------------% c | STEP 3: r_{j} = OP*v_{j}; Note that p_{j} = B*v_{j} | c | Note that this is not quite yet r_{j}. See STEP 4 | @@ -466,14 +466,14 @@ subroutine ssaitr ipntr(2) = irj ipntr(3) = ipj ido = 1 -c +c c %-----------------------------------% c | Exit in order to compute OP*v_{j} | c %-----------------------------------% -c +c go to 9000 50 continue -c +c c %-----------------------------------% c | Back from reverse communication; | c | WORKD(IRJ:IRJ+N-1) := OP*v_{j}. | @@ -481,7 +481,7 @@ subroutine ssaitr c call arscnd (t3) tmvopx = tmvopx + (t3 - t2) -c +c step3 = .false. c c %------------------------------------------% @@ -489,7 +489,7 @@ subroutine ssaitr c %------------------------------------------% c call scopy (n, workd(irj), 1, resid, 1) -c +c c %-------------------------------------------% c | STEP 4: Finish extending the symmetric | c | Arnoldi to length j. If MODE = 2 | @@ -507,17 +507,17 @@ subroutine ssaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-------------------------------------% c | Exit in order to compute B*OP*v_{j} | c %-------------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call scopy(n, resid, 1 , workd(ipj), 1) end if 60 continue -c +c c %-----------------------------------% c | Back from reverse communication; | c | WORKD(IPJ:IPJ+N-1) := B*OP*v_{j}. | @@ -526,7 +526,7 @@ subroutine ssaitr if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) - end if + end if c step4 = .false. c @@ -545,7 +545,7 @@ subroutine ssaitr c wnorm = sdot (n, resid, 1, workd(ivj), 1) wnorm = sqrt(abs(wnorm)) - else if (bmat .eq. 'G') then + else if (bmat .eq. 'G') then wnorm = sdot (n, resid, 1, workd(ipj), 1) wnorm = sqrt(abs(wnorm)) else if (bmat .eq. 'I') then @@ -567,19 +567,19 @@ subroutine ssaitr c %------------------------------------------% c if (mode .ne. 2 ) then - call sgemv('T', n, j, one, v, ldv, workd(ipj), 1, zero, + call sgemv('T', n, j, one, v, ldv, workd(ipj), 1, zero, & workd(irj), 1) else if (mode .eq. 2) then - call sgemv('T', n, j, one, v, ldv, workd(ivj), 1, zero, + call sgemv('T', n, j, one, v, ldv, workd(ivj), 1, zero, & workd(irj), 1) end if c c %--------------------------------------% c | Orthgonalize r_{j} against V_{j}. | -c | RESID contains OP*v_{j}. See STEP 3. | +c | RESID contains OP*v_{j}. See STEP 3. | c %--------------------------------------% c - call sgemv('N', n, j, -one, v, ldv, workd(irj), 1, one, + call sgemv('N', n, j, -one, v, ldv, workd(irj), 1, one, & resid, 1) c c %--------------------------------------% @@ -593,10 +593,10 @@ subroutine ssaitr h(j,1) = rnorm end if call arscnd (t4) -c +c orth1 = .true. iter = 0 -c +c call arscnd (t2) if (bmat .eq. 'G') then nbx = nbx + 1 @@ -604,17 +604,17 @@ subroutine ssaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %----------------------------------% c | Exit in order to compute B*r_{j} | c %----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call scopy (n, resid, 1, workd(ipj), 1) end if 70 continue -c +c c %---------------------------------------------------% c | Back from reverse communication if ORTH1 = .true. | c | WORKD(IPJ:IPJ+N-1) := B*r_{j}. | @@ -624,14 +624,14 @@ subroutine ssaitr call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c orth1 = .false. c c %------------------------------% c | Compute the B-norm of r_{j}. | c %------------------------------% c - if (bmat .eq. 'G') then + if (bmat .eq. 'G') then rnorm = sdot (n, resid, 1, workd(ipj), 1) rnorm = sqrt(abs(rnorm)) else if (bmat .eq. 'I') then @@ -655,7 +655,7 @@ subroutine ssaitr c if (rnorm .gt. 0.717*wnorm) go to 100 nrorth = nrorth + 1 -c +c c %---------------------------------------------------% c | Enter the Iterative refinement phase. If further | c | refinement is necessary, loop back here. The loop | @@ -668,7 +668,7 @@ subroutine ssaitr if (msglvl .gt. 2) then xtemp(1) = wnorm xtemp(2) = rnorm - call svout (logfil, 2, xtemp, ndigit, + call svout (logfil, 2, xtemp, ndigit, & '_saitr: re-orthonalization ; wnorm and rnorm are') end if c @@ -677,7 +677,7 @@ subroutine ssaitr c | WORKD(IRJ:IRJ+J-1) = v(:,1:J)'*WORKD(IPJ:IPJ+N-1). | c %----------------------------------------------------% c - call sgemv ('T', n, j, one, v, ldv, workd(ipj), 1, + call sgemv ('T', n, j, one, v, ldv, workd(ipj), 1, & zero, workd(irj), 1) c c %----------------------------------------------% @@ -688,12 +688,12 @@ subroutine ssaitr c | H(j,j) is updated. | c %----------------------------------------------% c - call sgemv ('N', n, j, -one, v, ldv, workd(irj), 1, + call sgemv ('N', n, j, -one, v, ldv, workd(irj), 1, & one, resid, 1) c if (j .eq. 1 .or. rstart) h(j,1) = zero h(j,2) = h(j,2) + workd(irj + j - 1) -c +c orth2 = .true. call arscnd (t2) if (bmat .eq. 'G') then @@ -702,12 +702,12 @@ subroutine ssaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-----------------------------------% c | Exit in order to compute B*r_{j}. | c | r_{j} is the corrected residual. | c %-----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call scopy (n, resid, 1, workd(ipj), 1) @@ -726,8 +726,8 @@ subroutine ssaitr c %-----------------------------------------------------% c | Compute the B-norm of the corrected residual r_{j}. | c %-----------------------------------------------------% -c - if (bmat .eq. 'G') then +c + if (bmat .eq. 'G') then rnorm1 = sdot (n, resid, 1, workd(ipj), 1) rnorm1 = sqrt(abs(rnorm1)) else if (bmat .eq. 'I') then @@ -735,7 +735,7 @@ subroutine ssaitr end if c if (msglvl .gt. 0 .and. iter .gt. 0) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_saitr: Iterative refinement for Arnoldi residual') if (msglvl .gt. 2) then xtemp(1) = rnorm @@ -744,7 +744,7 @@ subroutine ssaitr & '_saitr: iterative refinement ; rnorm and rnorm1 are') end if end if -c +c c %-----------------------------------------% c | Determine if we need to perform another | c | step of re-orthogonalization. | @@ -757,7 +757,7 @@ subroutine ssaitr c %--------------------------------% c rnorm = rnorm1 -c +c else c c %-------------------------------------------% @@ -779,7 +779,7 @@ subroutine ssaitr 95 continue rnorm = zero end if -c +c c %----------------------------------------------% c | Branch here directly if iterative refinement | c | wasn't necessary or after at most NITER_REF | @@ -787,13 +787,13 @@ subroutine ssaitr c %----------------------------------------------% c 100 continue -c +c rstart = .false. orth2 = .false. -c +c call arscnd (t5) titref = titref + (t5 - t4) -c +c c %----------------------------------------------------------% c | Make sure the last off-diagonal element is non negative | c | If not perform a similarity transformation on H(1:j,1:j) | @@ -802,13 +802,13 @@ subroutine ssaitr c if (h(j,1) .lt. zero) then h(j,1) = -h(j,1) - if ( j .lt. k+np) then + if ( j .lt. k+np) then call sscal(n, -one, v(1,j+1), 1) else call sscal(n, -one, resid, 1) end if end if -c +c c %------------------------------------% c | STEP 6: Update j = j+1; Continue | c %------------------------------------% @@ -820,10 +820,10 @@ subroutine ssaitr ido = 99 c if (msglvl .gt. 1) then - call svout (logfil, k+np, h(1,2), ndigit, + call svout (logfil, k+np, h(1,2), ndigit, & '_saitr: main diagonal of matrix H of step K+NP.') if (k+np .gt. 1) then - call svout (logfil, k+np-1, h(2,1), ndigit, + call svout (logfil, k+np-1, h(2,1), ndigit, & '_saitr: sub diagonal of matrix H of step K+NP.') end if end if @@ -836,7 +836,7 @@ subroutine ssaitr c %--------------------------------------------------------% c go to 1000 -c +c c %---------------------------------------------------------------% c | | c | E N D O F M A I N I T E R A T I O N L O O P | diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssapps.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssapps.f index 6012dbf39cec..77bd9d52c48e 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssapps.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssapps.f @@ -12,8 +12,8 @@ c c A*(V_{k}*Q) - (V_{k}*Q)*(Q^T* H_{k}*Q) = r_{k+p}*e_{k+p}^T * Q c -c where Q is an orthogonal matrix of order KEV+NP. Q is the product of -c rotations resulting from the NP bulge chasing sweeps. The updated Arnoldi +c where Q is an orthogonal matrix of order KEV+NP. Q is the product of +c rotations resulting from the NP bulge chasing sweeps. The updated Arnoldi c factorization becomes: c c A*VNEW_{k} - VNEW_{k}*HNEW_{k} = rnew_{k}*e_{k}^T. @@ -49,7 +49,7 @@ c INPUT: H contains the symmetric tridiagonal matrix of the c Arnoldi factorization with the subdiagonal in the 1st column c starting at H(2,1) and the main diagonal in the 2nd column. -c OUTPUT: H contains the updated tridiagonal matrix in the +c OUTPUT: H contains the updated tridiagonal matrix in the c KEV leading submatrix. c c LDH Integer. (INPUT) @@ -85,12 +85,12 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c c\Routines called: -c ivout ARPACK utility routine that prints integers. +c ivout ARPACK utility routine that prints integers. c arscnd ARPACK utility routine for timing. c svout ARPACK utility routine that prints vectors. c slamch LAPACK routine that determines machine constants. @@ -107,19 +107,19 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c 12/16/93: Version ' 2.4' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sapps.F SID: 2.6 DATE OF SID: 3/28/97 RELEASE: 2 c c\Remarks c 1. In this version, each shift is applied to all the subblocks of -c the tridiagonal matrix H and not just to the submatrix that it -c comes from. This routine assumes that the subdiagonal elements +c the tridiagonal matrix H and not just to the submatrix that it +c comes from. This routine assumes that the subdiagonal elements c of H that are stored in h(1:kev+np,1) are nonegative upon input c and enforce this condition upon output. This version incorporates c deflation. See code for documentation. @@ -149,7 +149,7 @@ subroutine ssapps c %-----------------% c Real - & h(ldh,2), q(ldq,kev+np), resid(n), shift(np), + & h(ldh,2), q(ldq,kev+np), resid(n), shift(np), & v(ldv,kev+np), workd(2*n) c c %------------% @@ -175,7 +175,7 @@ subroutine ssapps c | External Subroutines | c %----------------------% c - external saxpy, scopy, sscal, slacpy, slartg, slaset, svout, + external saxpy, scopy, sscal, slacpy, slartg, slaset, svout, & ivout, arscnd, sgemv c c %--------------------% @@ -193,7 +193,7 @@ subroutine ssapps intrinsic abs c c %----------------% -c | Data statments | +c | Data statements | c %----------------% c data first / .true. / @@ -215,9 +215,9 @@ subroutine ssapps c call arscnd (t0) msglvl = msapps -c - kplusp = kev + np -c +c + kplusp = kev + np +c c %----------------------------------------------% c | Initialize Q to the identity matrix of order | c | kplusp used to accumulate the rotations. | @@ -230,7 +230,7 @@ subroutine ssapps c %----------------------------------------------% c if (np .eq. 0) go to 9000 -c +c c %----------------------------------------------------------% c | Apply the np shifts implicitly. Apply each shift to the | c | whole matrix and not just to the submatrix from which it | @@ -238,7 +238,7 @@ subroutine ssapps c %----------------------------------------------------------% c do 90 jj = 1, np -c +c istart = itop c c %----------------------------------------------------------% @@ -261,11 +261,11 @@ subroutine ssapps big = abs(h(i,2)) + abs(h(i+1,2)) if (h(i+1,1) .le. epsmch*big) then if (msglvl .gt. 0) then - call ivout (logfil, 1, i, ndigit, + call ivout (logfil, 1, [i], ndigit, & '_sapps: deflation at row/column no.') - call ivout (logfil, 1, jj, ndigit, - & '_sapps: occured before shift number.') - call svout (logfil, 1, h(i+1,1), ndigit, + call ivout (logfil, 1, [jj], ndigit, + & '_sapps: occurred before shift number.') + call svout (logfil, 1, h(i+1,1), ndigit, & '_sapps: the corresponding off diagonal element') end if h(i+1,1) = zero @@ -277,7 +277,7 @@ subroutine ssapps 40 continue c if (istart .lt. iend) then -c +c c %--------------------------------------------------------% c | Construct the plane rotation G'(istart,istart+1,theta) | c | that attempts to drive h(istart+1,1) to zero. | @@ -286,7 +286,7 @@ subroutine ssapps f = h(istart,2) - shift(jj) g = h(istart+1,1) call slartg (f, g, c, s, r) -c +c c %-------------------------------------------------------% c | Apply rotation to the left and right of H; | c | H <- G' * H * G, where G = G(istart,istart+1,theta). | @@ -296,11 +296,11 @@ subroutine ssapps a1 = c*h(istart,2) + s*h(istart+1,1) a2 = c*h(istart+1,1) + s*h(istart+1,2) a4 = c*h(istart+1,2) - s*h(istart+1,1) - a3 = c*h(istart+1,1) - s*h(istart,2) + a3 = c*h(istart+1,1) - s*h(istart,2) h(istart,2) = c*a1 + s*a2 h(istart+1,2) = c*a4 - s*a3 h(istart+1,1) = c*a3 + s*a4 -c +c c %----------------------------------------------------% c | Accumulate the rotation in the matrix Q; Q <- Q*G | c %----------------------------------------------------% @@ -323,7 +323,7 @@ subroutine ssapps c %----------------------------------------------% c do 70 i = istart+1, iend-1 -c +c c %----------------------------------------------% c | Construct the plane rotation G'(i,i+1,theta) | c | that zeros the i-th bulge that was created | @@ -351,23 +351,23 @@ subroutine ssapps c = -c s = -s end if -c +c c %--------------------------------------------% c | Apply rotation to the left and right of H; | c | H <- G * H * G', where G = G(i,i+1,theta) | c %--------------------------------------------% c h(i,1) = r -c +c a1 = c*h(i,2) + s*h(i+1,1) a2 = c*h(i+1,1) + s*h(i+1,2) a3 = c*h(i+1,1) - s*h(i,2) a4 = c*h(i+1,2) - s*h(i+1,1) -c +c h(i,2) = c*a1 + s*a2 h(i+1,2) = c*a4 - s*a3 h(i+1,1) = c*a3 + s*a4 -c +c c %----------------------------------------------------% c | Accumulate the rotation in the matrix Q; Q <- Q*G | c %----------------------------------------------------% @@ -425,16 +425,16 @@ subroutine ssapps c %------------------------------------------% c | All shifts have been applied. Check for | c | more possible deflation that might occur | -c | after the last shift is applied. | +c | after the last shift is applied. | c %------------------------------------------% c do 100 i = itop, kplusp-1 big = abs(h(i,2)) + abs(h(i+1,2)) if (h(i+1,1) .le. epsmch*big) then if (msglvl .gt. 0) then - call ivout (logfil, 1, i, ndigit, + call ivout (logfil, 1, [i], ndigit, & '_sapps: deflation at row/column no.') - call svout (logfil, 1, h(i+1,1), ndigit, + call svout (logfil, 1, h(i+1,1), ndigit, & '_sapps: the corresponding off diagonal element') end if h(i+1,1) = zero @@ -447,13 +447,13 @@ subroutine ssapps c | This is not necessary if h(kev+1,1) = 0. | c %-------------------------------------------------% c - if ( h(kev+1,1) .gt. zero ) + if ( h(kev+1,1) .gt. zero ) & call sgemv ('N', n, kplusp, one, v, ldv, & q(1,kev+1), 1, zero, workd(n+1), 1) -c +c c %-------------------------------------------------------% c | Compute column 1 to kev of (V*Q) in backward order | -c | taking advantage that Q is an upper triangular matrix | +c | taking advantage that Q is an upper triangular matrix | c | with lower bandwidth np. | c | Place results in v(:,kplusp-kev:kplusp) temporarily. | c %-------------------------------------------------------% @@ -469,15 +469,15 @@ subroutine ssapps c %-------------------------------------------------% c call slacpy ('All', n, kev, v(1,np+1), ldv, v, ldv) -c +c c %--------------------------------------------% c | Copy the (kev+1)-st column of (V*Q) in the | c | appropriate place if h(kev+1,1) .ne. zero. | c %--------------------------------------------% c - if ( h(kev+1,1) .gt. zero ) + if ( h(kev+1,1) .gt. zero ) & call scopy (n, workd(n+1), 1, v(1,kev+1), 1) -c +c c %-------------------------------------% c | Update the residual vector: | c | r <- sigmak*r + betak*v(:,kev+1) | @@ -487,26 +487,26 @@ subroutine ssapps c %-------------------------------------% c call sscal (n, q(kplusp,kev), resid, 1) - if (h(kev+1,1) .gt. zero) + if (h(kev+1,1) .gt. zero) & call saxpy (n, h(kev+1,1), v(1,kev+1), 1, resid, 1) c if (msglvl .gt. 1) then - call svout (logfil, 1, q(kplusp,kev), ndigit, + call svout (logfil, 1, q(kplusp,kev), ndigit, & '_sapps: sigmak of the updated residual vector') - call svout (logfil, 1, h(kev+1,1), ndigit, + call svout (logfil, 1, h(kev+1,1), ndigit, & '_sapps: betak of the updated residual vector') - call svout (logfil, kev, h(1,2), ndigit, + call svout (logfil, kev, h(1,2), ndigit, & '_sapps: updated main diagonal of H for next iteration') if (kev .gt. 1) then - call svout (logfil, kev-1, h(2,1), ndigit, + call svout (logfil, kev-1, h(2,1), ndigit, & '_sapps: updated sub diagonal of H for next iteration') end if end if c call arscnd (t1) tsapps = tsapps + (t1 - t0) -c - 9000 continue +c + 9000 continue return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaup2.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaup2.f index 60e6766a4a69..504f28fb0054 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaup2.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaup2.f @@ -3,35 +3,35 @@ c c\Name: ssaup2 c -c\Description: +c\Description: c Intermediate level interface called by ssaupd. c c\Usage: -c call ssaup2 +c call ssaup2 c ( IDO, BMAT, N, WHICH, NEV, NP, TOL, RESID, MODE, IUPD, -c ISHIFT, MXITER, V, LDV, H, LDH, RITZ, BOUNDS, Q, LDQ, WORKL, +c ISHIFT, MXITER, V, LDV, H, LDH, RITZ, BOUNDS, Q, LDQ, WORKL, c IPNTR, WORKD, INFO ) c c\Arguments c c IDO, BMAT, N, WHICH, NEV, TOL, RESID: same as defined in ssaupd. c MODE, ISHIFT, MXITER: see the definition of IPARAM in ssaupd. -c +c c NP Integer. (INPUT/OUTPUT) -c Contains the number of implicit shifts to apply during -c each Arnoldi/Lanczos iteration. -c If ISHIFT=1, NP is adjusted dynamically at each iteration +c Contains the number of implicit shifts to apply during +c each Arnoldi/Lanczos iteration. +c If ISHIFT=1, NP is adjusted dynamically at each iteration c to accelerate convergence and prevent stagnation. -c This is also roughly equal to the number of matrix-vector +c This is also roughly equal to the number of matrix-vector c products (involving the operator OP) per Arnoldi iteration. c The logic for adjusting is contained within the current c subroutine. c If ISHIFT=0, NP is the number of shifts the user needs -c to provide via reverse comunication. 0 < NP < NCV-NEV. +c to provide via reverse communication. 0 < NP < NCV-NEV. c NP may be less than NCV-NEV since a leading block of the current c upper Tridiagonal matrix has split off and contains "unwanted" c Ritz values. -c Upon termination of the IRA iteration, NP contains the number +c Upon termination of the IRA iteration, NP contains the number c of "converged" wanted Ritz values. c c IUPD Integer. (INPUT) @@ -42,18 +42,18 @@ c The Lanczos basis vectors. c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c H Real (NEV+NP) by 2 array. (OUTPUT) c H is used to store the generated symmetric tridiagonal matrix -c The subdiagonal is stored in the first column of H starting +c The subdiagonal is stored in the first column of H starting c at H(2,1). The main diagonal is stored in the second column -c of H starting at H(1,2). If ssaup2 converges store the +c of H starting at H(1,2). If ssaup2 converges store the c B-norm of the final residual vector in H(1,1). c c LDH Integer. (INPUT) -c Leading dimension of H exactly as declared in the calling +c Leading dimension of H exactly as declared in the calling c program. c c RITZ Real array of length NEV+NP. (OUTPUT) @@ -63,33 +63,33 @@ c BOUNDS(1:NEV) contain the error bounds corresponding to RITZ. c c Q Real (NEV+NP) by (NEV+NP) array. (WORKSPACE) -c Private (replicated) work array used to accumulate the +c Private (replicated) work array used to accumulate the c rotation in the shift application step. c c LDQ Integer. (INPUT) c Leading dimension of Q exactly as declared in the calling c program. -c +c c WORKL Real array of length at least 3*(NEV+NP). (INPUT/WORKSPACE) c Private (replicated) array on each PE or array allocated on -c the front end. It is used in the computation of the +c the front end. It is used in the computation of the c tridiagonal eigenvalue problem, the calculation and c application of the shifts and convergence checking. c If ISHIFT .EQ. O and IDO .EQ. 3, the first NP locations -c of WORKL are used in reverse communication to hold the user +c of WORKL are used in reverse communication to hold the user c supplied shifts. c c IPNTR Integer array of length 3. (OUTPUT) -c Pointer to mark the starting locations in the WORKD for +c Pointer to mark the starting locations in the WORKD for c vectors used by the Lanczos iteration. c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X. c IPNTR(2): pointer to the current result vector Y. -c IPNTR(3): pointer to the vector B * X when used in one of +c IPNTR(3): pointer to the vector B * X when used in one of c the spectral transformation modes. X is the current c operand. c ------------------------------------------------------------- -c +c c WORKD Real work array of length 3*N. (REVERSE COMMUNICATION) c Distributed array to be used in the basic Lanczos iteration c for reverse communication. The user should not use WORKD @@ -102,9 +102,9 @@ c possibly from a previous run. c Error flag on output. c = 0: Normal return. -c = 1: All possible eigenvalues of OP has been found. +c = 1: All possible eigenvalues of OP has been found. c NP returns the size of the invariant subspace -c spanning the operator OP. +c spanning the operator OP. c = 2: No shifts could be applied. c = -8: Error return from trid. eigenvalue calculation; c This should never happen. @@ -122,7 +122,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c 3. B.N. Parlett, "The Symmetric Eigenvalue Problem". Prentice-Hall, @@ -132,15 +132,15 @@ c 5. B. Nour-Omid, B.N. Parlett, T. Ericson, P.S. Jensen, "How to c Implement the Spectral Transformation", Math. Comp., 48 (1987), c pp 663-673. -c 6. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos -c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", +c 6. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos +c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", c SIAM J. Matr. Anal. Apps., January (1993). c 7. L. Reichel, W.B. Gragg, "Algorithm 686: FORTRAN Subroutines c for Updating the QR decomposition", ACM TOMS, December 1990, c Volume 16 Number 4, pp 369-377. c c\Routines called: -c sgetv0 ARPACK initial vector generation routine. +c sgetv0 ARPACK initial vector generation routine. c ssaitr ARPACK Lanczos factorization routine. c ssapps ARPACK application of implicit shifts routine. c ssconv ARPACK convergence of Ritz values routine. @@ -152,7 +152,7 @@ c svout ARPACK utility routine that prints vectors. c slamch LAPACK routine that determines machine constants. c scopy Level 1 BLAS that copies one vector to another. -c sdot Level 1 BLAS that computes the scalar product of two vectors. +c sdot Level 1 BLAS that computes the scalar product of two vectors. c snrm2 Level 1 BLAS that computes the norm of a vector. c sscal Level 1 BLAS that scales a vector. c sswap Level 1 BLAS that swaps two vectors. @@ -162,14 +162,14 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas -c +c Rice University +c Houston, Texas +c c\Revision history: c 12/15/93: Version ' 2.4' c xx/xx/95: Version ' 2.4'. (R.B. Lehoucq) c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: saup2.F SID: 2.7 DATE OF SID: 5/19/98 RELEASE: 2 c c\EndLib @@ -177,8 +177,8 @@ c----------------------------------------------------------------------- c subroutine ssaup2 - & ( ido, bmat, n, which, nev, np, tol, resid, mode, iupd, - & ishift, mxiter, v, ldv, h, ldh, ritz, bounds, + & ( ido, bmat, n, which, nev, np, tol, resid, mode, iupd, + & ishift, mxiter, v, ldv, h, ldh, ritz, bounds, & q, ldq, workl, ipntr, workd, info ) c c %----------------------------------------------------% @@ -204,8 +204,8 @@ subroutine ssaup2 c integer ipntr(3) Real - & bounds(nev+np), h(ldh,2), q(ldq,nev+np), resid(n), - & ritz(nev+np), v(ldv,nev+np), workd(3*n), + & bounds(nev+np), h(ldh,2), q(ldq,nev+np), resid(n), + & ritz(nev+np), v(ldv,nev+np), workd(3*n), & workl(3*(nev+np)) c c %------------% @@ -222,8 +222,8 @@ subroutine ssaup2 c character wprime*2 logical cnorm, getv0, initv, update, ushift - integer ierr, iter, j, kplusp, msglvl, nconv, nevbef, nev0, - & np0, nptemp, nevd2, nevm2, kp(3) + integer ierr, iter, j, kplusp, msglvl, nconv, nevbef, nev0, + & np0, nptemp, nevd2, nevm2, kp(3) Real & rnorm, temp, eps23 save cnorm, getv0, initv, update, ushift, @@ -234,7 +234,7 @@ subroutine ssaup2 c | External Subroutines | c %----------------------% c - external scopy, sgetv0, ssaitr, sscal, ssconv, sseigt, ssgets, + external scopy, sgetv0, ssaitr, sscal, ssconv, sseigt, ssgets, & ssapps, ssortr, svout, ivout, arscnd, sswap c c %--------------------% @@ -256,7 +256,7 @@ subroutine ssaup2 c %-----------------------% c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -292,7 +292,7 @@ subroutine ssaup2 kplusp = nev0 + np0 nconv = 0 iter = 0 -c +c c %--------------------------------------------% c | Set flags for computing the first NEV steps | c | of the Lanczos factorization. | @@ -315,7 +315,7 @@ subroutine ssaup2 initv = .false. end if end if -c +c c %---------------------------------------------% c | Get a possibly random starting vector and | c | force it into the range of the operator OP. | @@ -332,7 +332,7 @@ subroutine ssaup2 if (rnorm .eq. zero) then c c %-----------------------------------------% -c | The initial vector is zero. Error exit. | +c | The initial vector is zero. Error exit. | c %-----------------------------------------% c info = -9 @@ -341,7 +341,7 @@ subroutine ssaup2 getv0 = .false. ido = 0 end if -c +c c %------------------------------------------------------------% c | Back from reverse communication: continue with update step | c %------------------------------------------------------------% @@ -360,14 +360,14 @@ subroutine ssaup2 c %-------------------------------------% c if (cnorm) go to 100 -c +c c %----------------------------------------------------------% c | Compute the first NEV steps of the Lanczos factorization | c %----------------------------------------------------------% c - call ssaitr (ido, bmat, n, 0, nev0, mode, resid, rnorm, v, ldv, + call ssaitr (ido, bmat, n, 0, nev0, mode, resid, rnorm, v, ldv, & h, ldh, ipntr, workd, info) -c +c c %---------------------------------------------------% c | ido .ne. 99 implies use of reverse communication | c | to compute operations involving OP and possibly B | @@ -388,7 +388,7 @@ subroutine ssaup2 info = -9999 go to 1200 end if -c +c c %--------------------------------------------------------------% c | | c | M A I N LANCZOS I T E R A T I O N L O O P | @@ -396,22 +396,22 @@ subroutine ssaup2 c | factorization in place. | c | | c %--------------------------------------------------------------% -c +c 1000 continue c iter = iter + 1 c if (msglvl .gt. 0) then - call ivout (logfil, 1, iter, ndigit, + call ivout (logfil, 1, [iter], ndigit, & '_saup2: **** Start of major iteration number ****') end if if (msglvl .gt. 1) then - call ivout (logfil, 1, nev, ndigit, + call ivout (logfil, 1, [nev], ndigit, & '_saup2: The length of the current Lanczos factorization') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_saup2: Extend the Lanczos factorization by') end if -c +c c %------------------------------------------------------------% c | Compute NP additional steps of the Lanczos factorization. | c %------------------------------------------------------------% @@ -420,9 +420,9 @@ subroutine ssaup2 20 continue update = .true. c - call ssaitr (ido, bmat, n, nev, np, mode, resid, rnorm, v, + call ssaitr (ido, bmat, n, nev, np, mode, resid, rnorm, v, & ldv, h, ldh, ipntr, workd, info) -c +c c %---------------------------------------------------% c | ido .ne. 99 implies use of reverse communication | c | to compute operations involving OP and possibly B | @@ -434,7 +434,7 @@ subroutine ssaup2 c c %-----------------------------------------------------% c | ssaitr was unable to build an Lanczos factorization | -c | of length NEV0+NP0. INFO is returned with the size | +c | of length NEV0+NP0. INFO is returned with the size | c | of the factorization built. Exit main loop. | c %-----------------------------------------------------% c @@ -446,10 +446,10 @@ subroutine ssaup2 update = .false. c if (msglvl .gt. 1) then - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_saup2: Current B-norm of residual for factorization') end if -c +c c %--------------------------------------------------------% c | Compute the eigenvalues and corresponding error bounds | c | of the current symmetric tridiagonal matrix. | @@ -483,7 +483,7 @@ subroutine ssaup2 nev = nev0 np = np0 call ssgets (ishift, which, nev, np, ritz, bounds, workl) -c +c c %-------------------% c | Convergence test. | c %-------------------% @@ -520,11 +520,11 @@ subroutine ssaup2 nev = nev + 1 end if 30 continue -c - if ( (nconv .ge. nev0) .or. +c + if ( (nconv .ge. nev0) .or. & (iter .gt. mxiter) .or. & (np .eq. 0) ) then -c +c c %------------------------------------------------% c | Prepare to exit. Put the converged Ritz values | c | and corresponding bounds in RITZ(1:NCONV) and | @@ -547,7 +547,7 @@ subroutine ssaup2 wprime = 'SA' call ssortr (wprime, .true., kplusp, ritz, bounds) nevd2 = nev0 / 2 - nevm2 = nev0 - nevd2 + nevm2 = nev0 - nevd2 if ( nev .gt. 1 ) then call sswap ( min(nevd2,np), ritz(nevm2+1), 1, & ritz( max(kplusp-nevd2+1,kplusp-np+1) ), 1) @@ -587,7 +587,7 @@ subroutine ssaup2 c c %----------------------------------------------------% c | Sort the Ritz values according to the scaled Ritz | -c | esitmates. This will push all the converged ones | +c | estimates. This will push all the converged ones | c | towards the front of ritzr, ritzi, bounds | c | (in the case when NCONV < NEV.) | c %----------------------------------------------------% @@ -651,13 +651,13 @@ subroutine ssaup2 end if c c %------------------------------------% -c | Max iterations have been exceeded. | +c | Max iterations have been exceeded. | c %------------------------------------% c if (iter .gt. mxiter .and. nconv .lt. nev) info = 1 c c %---------------------% -c | No shifts to apply. | +c | No shifts to apply. | c %---------------------% c if (np .eq. 0 .and. nconv .lt. nev0) info = 2 @@ -681,20 +681,20 @@ subroutine ssaup2 nev = 2 end if np = kplusp - nev -c +c c %---------------------------------------% c | If the size of NEV was just increased | c | resort the eigenvalues. | c %---------------------------------------% -c - if (nevbef .lt. nev) +c + if (nevbef .lt. nev) & call ssgets (ishift, which, nev, np, ritz, bounds, & workl) c end if c if (msglvl .gt. 0) then - call ivout (logfil, 1, nconv, ndigit, + call ivout (logfil, 1, [nconv], ndigit, & '_saup2: no. of "converged" Ritz values at this iter.') if (msglvl .gt. 1) then kp(1) = nev @@ -708,7 +708,7 @@ subroutine ssaup2 end if end if -c +c if (ishift .eq. 0) then c c %-----------------------------------------------------% @@ -731,8 +731,8 @@ subroutine ssaup2 c %------------------------------------% c ushift = .false. -c -c +c +c c %---------------------------------------------------------% c | Move the NP shifts to the first NP locations of RITZ to | c | free up WORKL. This is for the non-exact shift case; | @@ -742,7 +742,7 @@ subroutine ssaup2 if (ishift .eq. 0) call scopy (np, workl, 1, ritz, 1) c if (msglvl .gt. 2) then - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_saup2: The number of shifts to apply ') call svout (logfil, np, workl, ndigit, & '_saup2: shifts selected') @@ -751,7 +751,7 @@ subroutine ssaup2 & '_saup2: corresponding Ritz estimates') end if end if -c +c c %---------------------------------------------------------% c | Apply the NP0 implicit shifts by QR bulge chasing. | c | Each shift is applied to the entire tridiagonal matrix. | @@ -777,18 +777,18 @@ subroutine ssaup2 ipntr(1) = n + 1 ipntr(2) = 1 ido = 2 -c +c c %----------------------------------% c | Exit in order to compute B*RESID | c %----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call scopy (n, resid, 1, workd, 1) end if -c +c 100 continue -c +c c %----------------------------------% c | Back from reverse communication; | c | WORKD(1:N) := B*RESID | @@ -798,8 +798,8 @@ subroutine ssaup2 call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c - if (bmat .eq. 'G') then +c + if (bmat .eq. 'G') then rnorm = sdot (n, resid, 1, workd, 1) rnorm = sqrt(abs(rnorm)) else if (bmat .eq. 'I') then @@ -809,14 +809,14 @@ subroutine ssaup2 130 continue c if (msglvl .gt. 2) then - call svout (logfil, 1, rnorm, ndigit, + call svout (logfil, 1, [rnorm], ndigit, & '_saup2: B-norm of residual for NEV factorization') call svout (logfil, nev, h(1,2), ndigit, & '_saup2: main diagonal of compressed H matrix') call svout (logfil, nev-1, h(2,1), ndigit, & '_saup2: subdiagonal of compressed H matrix') end if -c +c go to 1000 c c %---------------------------------------------------------------% @@ -824,12 +824,12 @@ subroutine ssaup2 c | E N D O F M A I N I T E R A T I O N L O O P | c | | c %---------------------------------------------------------------% -c +c 1100 continue c mxiter = iter nev = nconv -c +c 1200 continue ido = 99 c @@ -839,7 +839,7 @@ subroutine ssaup2 c call arscnd (t1) tsaup2 = t1 - t0 -c +c 9000 continue return c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaupd.f index b55663964b59..975681527045 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssaupd.f @@ -3,31 +3,31 @@ c c\Name: ssaupd c -c\Description: +c\Description: c -c Reverse communication interface for the Implicitly Restarted Arnoldi -c Iteration. For symmetric problems this reduces to a variant of the Lanczos -c method. This method has been designed to compute approximations to a -c few eigenpairs of a linear operator OP that is real and symmetric -c with respect to a real positive semi-definite symmetric matrix B, +c Reverse communication interface for the Implicitly Restarted Arnoldi +c Iteration. For symmetric problems this reduces to a variant of the Lanczos +c method. This method has been designed to compute approximations to a +c few eigenpairs of a linear operator OP that is real and symmetric +c with respect to a real positive semi-definite symmetric matrix B, c i.e. -c -c B*OP = (OP`)*B. c -c Another way to express this condition is +c B*OP = (OP`)*B. +c +c Another way to express this condition is c c < x,OPy > = < OPx,y > where < z,w > = z`Bw . -c -c In the standard eigenproblem B is the identity matrix. +c +c In the standard eigenproblem B is the identity matrix. c ( A` denotes transpose of A) c c The computed approximate eigenvalues are called Ritz values and c the corresponding approximate eigenvectors are called Ritz vectors. c -c ssaupd is usually called iteratively to solve one of the +c ssaupd is usually called iteratively to solve one of the c following problems: c -c Mode 1: A*x = lambda*x, A symmetric +c Mode 1: A*x = lambda*x, A symmetric c ===> OP = A and B = I. c c Mode 2: A*x = lambda*M*x, A symmetric, M symmetric positive definite @@ -35,10 +35,10 @@ c ===> (If M can be factored see remark 3 below) c c Mode 3: K*x = lambda*M*x, K symmetric, M symmetric positive semi-definite -c ===> OP = (inv[K - sigma*M])*M and B = M. +c ===> OP = (inv[K - sigma*M])*M and B = M. c ===> Shift-and-Invert mode c -c Mode 4: K*x = lambda*KG*x, K symmetric positive semi-definite, +c Mode 4: K*x = lambda*KG*x, K symmetric positive semi-definite, c KG symmetric indefinite c ===> OP = (inv[K - sigma*KG])*K and B = K. c ===> Buckling mode @@ -60,13 +60,13 @@ c approximations. c c\Usage: -c call ssaupd +c call ssaupd c ( IDO, BMAT, N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, c IPNTR, WORKD, WORKL, LWORKL, INFO ) c c\Arguments c IDO Integer. (INPUT/OUTPUT) -c Reverse communication flag. IDO must be zero on the first +c Reverse communication flag. IDO must be zero on the first c call to ssaupd. IDO will be set internally to c indicate the type of operation to be performed. Control is c then given back to the calling routine which has the @@ -95,7 +95,7 @@ c placing the shifts. See remark 6 below. c IDO = 99: done c ------------------------------------------------------------- -c +c c BMAT Character*1. (INPUT) c BMAT specifies the type of the matrix B that defines the c semi-inner product for the operator OP. @@ -111,7 +111,7 @@ c 'LA' - compute the NEV largest (algebraic) eigenvalues. c 'SA' - compute the NEV smallest (algebraic) eigenvalues. c 'LM' - compute the NEV largest (in magnitude) eigenvalues. -c 'SM' - compute the NEV smallest (in magnitude) eigenvalues. +c 'SM' - compute the NEV smallest (in magnitude) eigenvalues. c 'BE' - compute NEV eigenvalues, half from each end of the c spectrum. When NEV is odd, compute one more from the c high end than from the low end. @@ -121,27 +121,27 @@ c Number of eigenvalues of OP to be computed. 0 < NEV < N. c c TOL Real scalar. (INPUT) -c Stopping criterion: the relative accuracy of the Ritz value +c Stopping criterion: the relative accuracy of the Ritz value c is considered acceptable if BOUNDS(I) .LE. TOL*ABS(RITZ(I)). c If TOL .LE. 0. is passed a default is set: -c DEFAULT = slamch('EPS') (machine precision as computed -c by the LAPACK auxiliary subroutine slamch). +c DEFAULT = SLAMCH('EPS') (machine precision as computed +c by the LAPACK auxiliary subroutine SLAMCH). c c RESID Real array of length N. (INPUT/OUTPUT) -c On INPUT: +c On INPUT: c If INFO .EQ. 0, a random initial residual vector is used. c If INFO .NE. 0, RESID contains the initial residual vector, c possibly from a previous run. c On OUTPUT: -c RESID contains the final residual vector. +c RESID contains the final residual vector. c c NCV Integer. (INPUT) c Number of columns of the matrix V (less than or equal to N). -c This will indicate how many Lanczos vectors are generated -c at each iteration. After the startup phase in which NEV -c Lanczos vectors are generated, the algorithm generates +c This will indicate how many Lanczos vectors are generated +c at each iteration. After the startup phase in which NEV +c Lanczos vectors are generated, the algorithm generates c NCV-NEV Lanczos vectors at each subsequent update iteration. -c Most of the cost in generating each Lanczos vector is in the +c Most of the cost in generating each Lanczos vector is in the c matrix-vector product OP*x. (See remark 4 below). c c V Real N by NCV array. (OUTPUT) @@ -161,10 +161,10 @@ c the current tridiagonal matrix T are returned in c the part of WORKL array corresponding to RITZ. c See remark 6 below. -c ISHIFT = 1: exact shifts with respect to the reduced -c tridiagonal matrix T. This is equivalent to -c restarting the iteration with a starting vector -c that is a linear combination of Ritz vectors +c ISHIFT = 1: exact shifts with respect to the reduced +c tridiagonal matrix T. This is equivalent to +c restarting the iteration with a starting vector +c that is a linear combination of Ritz vectors c associated with the "wanted" Ritz values. c ------------------------------------------------------------- c @@ -172,8 +172,8 @@ c No longer referenced. See remark 2 below. c c IPARAM(3) = MXITER -c On INPUT: maximum number of Arnoldi update iterations allowed. -c On OUTPUT: actual number of Arnoldi update iterations taken. +c On INPUT: maximum number of Arnoldi update iterations allowed. +c On OUTPUT: actual number of Arnoldi update iterations taken. c c IPARAM(4) = NB: blocksize to be used in the recurrence. c The code currently works only for NB = 1. @@ -183,11 +183,11 @@ c the convergence criterion. c c IPARAM(6) = IUPD -c No longer referenced. Implicit restarting is ALWAYS used. +c No longer referenced. Implicit restarting is ALWAYS used. c c IPARAM(7) = MODE c On INPUT determines what type of eigenproblem is being solved. -c Must be 1,2,3,4,5; See under \Description of ssaupd for the +c Must be 1,2,3,4,5; See under \Description of ssaupd for the c five modes available. c c IPARAM(8) = NP @@ -199,7 +199,7 @@ c IPARAM(9) = NUMOP, IPARAM(10) = NUMOPB, IPARAM(11) = NUMREO, c OUTPUT: NUMOP = total number of OP*x operations, c NUMOPB = total number of B*x operations if BMAT='G', -c NUMREO = total number of steps of re-orthogonalization. +c NUMREO = total number of steps of re-orthogonalization. c c IPNTR Integer array of length 11. (OUTPUT) c Pointer to mark the starting locations in the WORKD and WORKL @@ -207,7 +207,7 @@ c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X in WORKD. c IPNTR(2): pointer to the current result vector Y in WORKD. -c IPNTR(3): pointer to the vector B * X in WORKD when used in +c IPNTR(3): pointer to the vector B * X in WORKD when used in c the shift-and-invert mode. c IPNTR(4): pointer to the next available location in WORKL c that is untouched by the program. @@ -224,14 +224,14 @@ c of the tridiagonal matrix T. Only referenced by c sseupd if RVEC = .TRUE. See Remarks. c ------------------------------------------------------------- -c +c c WORKD Real work array of length 3*N. (REVERSE COMMUNICATION) c Distributed array to be used in the basic Arnoldi iteration -c for reverse communication. The user should not use WORKD +c for reverse communication. The user should not use WORKD c as temporary workspace during the iteration. Upon termination c WORKD(1:N) contains B*RESID(1:N). If the Ritz vectors are desired c subroutine sseupd uses this output. -c See Data Distribution Note below. +c See Data Distribution Note below. c c WORKL Real work array of length LWORKL. (OUTPUT/WORKSPACE) c Private (replicated) array on each PE or array allocated on @@ -247,13 +247,13 @@ c Error flag on output. c = 0: Normal exit. c = 1: Maximum number of iterations taken. -c All possible eigenvalues of OP has been found. IPARAM(5) +c All possible eigenvalues of OP has been found. IPARAM(5) c returns the number of wanted converged Ritz values. c = 2: No longer an informational error. Deprecated starting c with release 2 of ARPACK. -c = 3: No shifts could be applied during a cycle of the -c Implicitly restarted Arnoldi iteration. One possibility -c is to increase the size of NCV relative to NEV. +c = 3: No shifts could be applied during a cycle of the +c Implicitly restarted Arnoldi iteration. One possibility +c is to increase the size of NCV relative to NEV. c See remark 4 below. c = -1: N must be positive. c = -2: NEV must be positive. @@ -267,9 +267,9 @@ c Informatinal error from LAPACK routine ssteqr. c = -9: Starting vector is zero. c = -10: IPARAM(7) must be 1,2,3,4,5. -c = -11: IPARAM(7) = 1 and BMAT = 'G' are incompatable. +c = -11: IPARAM(7) = 1 and BMAT = 'G' are incompatible. c = -12: IPARAM(1) must be equal to 0 or 1. -c = -13: NEV and WHICH = 'BE' are incompatable. +c = -13: NEV and WHICH = 'BE' are incompatible. c = -9999: Could not build an Arnoldi factorization. c IPARAM(5) returns the size of the current Arnoldi c factorization. The user is advised to check that @@ -277,12 +277,12 @@ c c c\Remarks -c 1. The converged Ritz values are always returned in ascending +c 1. The converged Ritz values are always returned in ascending c algebraic order. The computed Ritz values are approximate c eigenvalues of OP. The selection of WHICH should be made -c with this in mind when Mode = 3,4,5. After convergence, -c approximate eigenvalues of the original problem may be obtained -c with the ARPACK subroutine sseupd. +c with this in mind when Mode = 3,4,5. After convergence, +c approximate eigenvalues of the original problem may be obtained +c with the ARPACK subroutine sseupd. c c 2. If the Ritz vectors corresponding to the converged Ritz values c are needed, the user must call sseupd immediately following completion @@ -290,38 +290,38 @@ c c 3. If M can be factored into a Cholesky factorization M = LL` c then Mode = 2 should not be selected. Instead one should use -c Mode = 1 with OP = inv(L)*A*inv(L`). Appropriate triangular +c Mode = 1 with OP = inv(L)*A*inv(L`). Appropriate triangular c linear systems should be solved with L and L` rather c than computing inverses. After convergence, an approximate c eigenvector z of the original problem is recovered by solving c L`z = x where x is a Ritz vector of OP. c c 4. At present there is no a-priori analysis to guide the selection -c of NCV relative to NEV. The only formal requrement is that NCV > NEV. +c of NCV relative to NEV. The only formal requirement is that NCV > NEV. c However, it is recommended that NCV .ge. 2*NEV. If many problems of c the same type are to be solved, one should experiment with increasing -c NCV while keeping NEV fixed for a given test problem. This will +c NCV while keeping NEV fixed for a given test problem. This will c usually decrease the required number of OP*x operations but it c also increases the work and storage required to maintain the orthogonal c basis vectors. The optimal "cross-over" with respect to CPU time c is problem dependent and must be determined empirically. c -c 5. If IPARAM(7) = 2 then in the Reverse commuication interface the user +c 5. If IPARAM(7) = 2 then in the Reverse communication interface the user c must do the following. When IDO = 1, Y = OP * X is to be computed. c When IPARAM(7) = 2 OP = inv(B)*A. After computing A*X the user c must overwrite X with A*X. Y is then the solution to the linear set c of equations B*Y = A*X. c -c 6. When IPARAM(1) = 0, and IDO = 3, the user needs to provide the -c NP = IPARAM(8) shifts in locations: -c 1 WORKL(IPNTR(11)) -c 2 WORKL(IPNTR(11)+1) -c . -c . -c . -c NP WORKL(IPNTR(11)+NP-1). +c 6. When IPARAM(1) = 0, and IDO = 3, the user needs to provide the +c NP = IPARAM(8) shifts in locations: +c 1 WORKL(IPNTR(11)) +c 2 WORKL(IPNTR(11)+1) +c . +c . +c . +c NP WORKL(IPNTR(11)+NP-1). c -c The eigenvalues of the current tridiagonal matrix are located in +c The eigenvalues of the current tridiagonal matrix are located in c WORKL(IPNTR(6)) through WORKL(IPNTR(6)+NCV-1). They are in the c order defined by WHICH. The associated Ritz estimates are located in c WORKL(IPNTR(8)), WORKL(IPNTR(8)+1), ... , WORKL(IPNTR(8)+NCV-1). @@ -347,7 +347,7 @@ c REAL RESID(N), V(LDV,NCV), WORKD(N,3), WORKL(LWORKL) c SHARED RESID(BLOCK), V(BLOCK,:), WORKD(BLOCK,:) c REPLICATED WORKL(LWORKL) -c +c c c\BeginLib c @@ -355,7 +355,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c 3. B.N. Parlett, "The Symmetric Eigenvalue Problem". Prentice-Hall, @@ -365,8 +365,8 @@ c 5. B. Nour-Omid, B.N. Parlett, T. Ericson, P.S. Jensen, "How to c Implement the Spectral Transformation", Math. Comp., 48 (1987), c pp 663-673. -c 6. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos -c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", +c 6. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos +c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", c SIAM J. Matr. Anal. Apps., January (1993). c 7. L. Reichel, W.B. Gragg, "Algorithm 686: FORTRAN Subroutines c for Updating the QR decomposition", ACM TOMS, December 1990, @@ -389,14 +389,14 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas -c +c Rice University +c Houston, Texas +c c\Revision history: -c 12/15/93: Version ' 2.4' +c 12/15/93: Version ' 2.4' c -c\SCCS Information: @(#) -c FILE: saupd.F SID: 2.8 DATE OF SID: 04/10/01 RELEASE: 2 +c\SCCS Information: @(#) +c FILE: saupd.F SID: 2.8 DATE OF SID: 04/10/01 RELEASE: 2 c c\Remarks c 1. None @@ -406,7 +406,7 @@ c----------------------------------------------------------------------- c subroutine ssaupd - & ( ido, bmat, n, which, nev, tol, resid, ncv, v, ldv, iparam, + & ( ido, bmat, n, which, nev, tol, resid, ncv, v, ldv, iparam, & ipntr, workd, workl, lworkl, info ) c c %----------------------------------------------------% @@ -422,7 +422,7 @@ subroutine ssaupd c character bmat*1, which*2 integer ido, info, ldv, lworkl, n, ncv, nev - Real + Real & tol c c %-----------------% @@ -430,14 +430,14 @@ subroutine ssaupd c %-----------------% c integer iparam(11), ipntr(11) - Real + Real & resid(n), v(ldv,ncv), workd(3*n), workl(lworkl) c c %------------% c | Parameters | c %------------% c - Real + Real & one, zero parameter (one = 1.0E+0 , zero = 0.0E+0 ) c @@ -445,7 +445,7 @@ subroutine ssaupd c | Local Scalars | c %---------------% c - integer bounds, ierr, ih, iq, ishift, iupd, iw, + integer bounds, ierr, ih, iq, ishift, iupd, iw, & ldh, ldq, msglvl, mxiter, mode, nb, & nev0, next, np, ritz, j save bounds, ierr, ih, iq, ishift, iupd, iw, @@ -462,14 +462,14 @@ subroutine ssaupd c | External Functions | c %--------------------% c - Real + Real & slamch external slamch c c %-----------------------% c | Executable Statements | c %-----------------------% -c +c if (ido .eq. 0) then c c %-------------------------------% @@ -512,7 +512,7 @@ subroutine ssaupd c %----------------------------------------------% c np = ncv - nev -c +c if (mxiter .le. 0) ierr = -4 if (which .ne. 'LM' .and. & which .ne. 'SM' .and. @@ -531,7 +531,7 @@ subroutine ssaupd else if (nev .eq. 1 .and. which .eq. 'BE') then ierr = -13 end if -c +c c %------------% c | Error Exit | c %------------% @@ -541,7 +541,7 @@ subroutine ssaupd ido = 99 go to 9000 end if -c +c c %------------------------% c | Set default parameters | c %------------------------% @@ -557,8 +557,8 @@ subroutine ssaupd c %----------------------------------------------% c np = ncv - nev - nev0 = nev -c + nev0 = nev +c c %-----------------------------% c | Zero out internal workspace | c %-----------------------------% @@ -566,7 +566,7 @@ subroutine ssaupd do 10 j = 1, ncv**2 + 8*ncv workl(j) = zero 10 continue -c +c c %-------------------------------------------------------% c | Pointer into WORKL for address of H, RITZ, BOUNDS, Q | c | etc... and the remaining workspace. | @@ -599,7 +599,7 @@ subroutine ssaupd c | Carry out the Implicitly restarted Lanczos Iteration. | c %-------------------------------------------------------% c - call ssaup2 + call ssaup2 & ( ido, bmat, n, which, nev0, np, tol, resid, mode, iupd, & ishift, mxiter, v, ldv, workl(ih), ldh, workl(ritz), & workl(bounds), workl(iq), ldq, workl(iw), ipntr, workd, @@ -612,7 +612,7 @@ subroutine ssaupd c if (ido .eq. 3) iparam(8) = np if (ido .ne. 99) go to 9000 -c +c iparam(3) = mxiter iparam(5) = np iparam(9) = nopx @@ -628,19 +628,19 @@ subroutine ssaupd if (info .eq. 2) info = 3 c if (msglvl .gt. 0) then - call ivout (logfil, 1, mxiter, ndigit, + call ivout (logfil, 1, [mxiter], ndigit, & '_saupd: number of update iterations taken') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_saupd: number of "converged" Ritz values') - call svout (logfil, np, workl(Ritz), ndigit, + call svout (logfil, np, workl(Ritz), ndigit, & '_saupd: final Ritz values') - call svout (logfil, np, workl(Bounds), ndigit, + call svout (logfil, np, workl(Bounds), ndigit, & '_saupd: corresponding error bounds') - end if + end if c call arscnd (t1) tsaupd = t1 - t0 -c +c if (msglvl .gt. 0) then c c %--------------------------------------------------------% @@ -678,9 +678,9 @@ subroutine ssaupd & 5x, 'Total time in applying the shifts = ', f12.6,/ & 5x, 'Total time in convergence testing = ', f12.6) end if -c +c 9000 continue -c +c return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssconv.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssconv.f index 3542c8c9fd7e..11e4cab262a7 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssconv.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssconv.f @@ -3,7 +3,7 @@ c c\Name: ssconv c -c\Description: +c\Description: c Convergence testing for the symmetric Arnoldi eigenvalue routine. c c\Usage: @@ -35,22 +35,22 @@ c c\Routines called: c arscnd ARPACK utility routine for timing. -c slamch LAPACK routine that determines machine constants. +c slamch LAPACK routine that determines machine constants. c c\Author c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University -c Dept. of Computational & Houston, Texas +c Richard Lehoucq CRPC / Rice University +c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sconv.F SID: 2.4 DATE OF SID: 4/19/96 RELEASE: 2 c c\Remarks c 1. Starting with version 2.4, this routine no longer uses the -c Parlett strategy using the gap conditions. +c Parlett strategy using the gap conditions. c c\EndLib c @@ -108,7 +108,7 @@ subroutine ssconv (n, ritz, bounds, tol, nconv) c call arscnd (t0) c - eps23 = slamch('Epsilon-Machine') + eps23 = slamch('Epsilon-Machine') eps23 = eps23**(2.0E+0 / 3.0E+0) c nconv = 0 @@ -125,10 +125,10 @@ subroutine ssconv (n, ritz, bounds, tol, nconv) end if c 10 continue -c +c call arscnd (t1) tsconv = tsconv + (t1 - t0) -c +c return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sseigt.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sseigt.f index b77a6ea6489d..3ac336690b44 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sseigt.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sseigt.f @@ -3,7 +3,7 @@ c c\Name: sseigt c -c\Description: +c\Description: c Compute the eigenvalues of the current symmetric tridiagonal matrix c and the corresponding error bounds given the current residual norm. c @@ -20,16 +20,16 @@ c Size of the symmetric tridiagonal matrix H. c c H Real N by 2 array. (INPUT) -c H contains the symmetric tridiagonal matrix with the -c subdiagonal in the first column starting at H(2,1) and the +c H contains the symmetric tridiagonal matrix with the +c subdiagonal in the first column starting at H(2,1) and the c main diagonal in second column. c c LDH Integer. (INPUT) -c Leading dimension of H exactly as declared in the calling +c Leading dimension of H exactly as declared in the calling c program. c c EIG Real array of length N. (OUTPUT) -c On output, EIG contains the N eigenvalues of H possibly +c On output, EIG contains the N eigenvalues of H possibly c unsorted. The BOUNDS arrays are returned in the c same sorted order as EIG. c @@ -65,16 +65,16 @@ c c\Author c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University -c Dept. of Computational & Houston, Texas +c Richard Lehoucq CRPC / Rice University +c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.4' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: seigt.F SID: 2.4 DATE OF SID: 8/27/96 RELEASE: 2 c c\Remarks @@ -84,7 +84,7 @@ c c----------------------------------------------------------------------- c - subroutine sseigt + subroutine sseigt & ( rnorm, n, h, ldh, eig, bounds, workl, ierr ) c c %----------------------------------------------------% @@ -136,7 +136,7 @@ subroutine sseigt c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | -c %-------------------------------% +c %-------------------------------% c call arscnd (t0) msglvl = mseigt @@ -167,7 +167,7 @@ subroutine sseigt do 30 k = 1, n bounds(k) = rnorm*abs(bounds(k)) 30 continue -c +c call arscnd (t1) tseigt = tseigt + (t1 - t0) c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssesrt.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssesrt.f index 36e8787e1cf1..afc71b088236 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssesrt.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssesrt.f @@ -4,7 +4,7 @@ c\Name: ssesrt c c\Description: -c Sort the array X in the order specified by WHICH and optionally +c Sort the array X in the order specified by WHICH and optionally c apply the permutation to the columns of the matrix A. c c\Usage: @@ -32,7 +32,7 @@ c Number of rows of the matrix A. c c A Real array of length NA by N. (INPUT/OUTPUT) -c +c c LDA Integer. (INPUT) c Leading dimension of A. c @@ -47,18 +47,18 @@ c c\Authors c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University -c Dept. of Computational & Houston, Texas +c Richard Lehoucq CRPC / Rice University +c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c 12/15/93: Version ' 2.1'. -c Adapted from the sort routine in LANSO and +c Adapted from the sort routine in LANSO and c the ARPACK code ssortr c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sesrt.F SID: 2.3 DATE OF SID: 4/19/96 RELEASE: 2 c c\EndLib @@ -101,7 +101,7 @@ subroutine ssesrt (which, apply, n, x, na, a, lda) c %-----------------------% c igap = n / 2 -c +c if (which .eq. 'SA') then c c X is sorted into decreasing order of algebraic. @@ -165,7 +165,7 @@ subroutine ssesrt (which, apply, n, x, na, a, lda) 80 continue c if (j.lt.0) go to 90 -c +c if (x(j).gt.x(j+igap)) then temp = x(j) x(j) = x(j+igap) @@ -179,7 +179,7 @@ subroutine ssesrt (which, apply, n, x, na, a, lda) 90 continue igap = igap / 2 go to 70 -c +c else if (which .eq. 'LM') then c c X is sorted into increasing order of magnitude. diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sseupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sseupd.f index 9349d115aa2a..03ba7ac50853 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sseupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sseupd.f @@ -2,7 +2,7 @@ c c\Name: sseupd c -c\Description: +c\Description: c c This subroutine returns the converged approximations to eigenvalues c of A*z = lambda*B*z and (optionally): @@ -15,22 +15,22 @@ c (3) Both. c c There is negligible additional cost to obtain eigenvectors. An orthonormal -c (Lanczos) basis is always computed. There is an additional storage cost -c of n*nev if both are requested (in this case a separate array Z must be +c (Lanczos) basis is always computed. There is an additional storage cost +c of n*nev if both are requested (in this case a separate array Z must be c supplied). c c These quantities are obtained from the Lanczos factorization computed c by SSAUPD for the linear operator OP prescribed by the MODE selection c (see IPARAM(7) in SSAUPD documentation.) SSAUPD must be called before -c this routine is called. These approximate eigenvalues and vectors are -c commonly called Ritz values and Ritz vectors respectively. They are -c referred to as such in the comments that follow. The computed orthonormal -c basis for the invariant subspace corresponding to these Ritz values is +c this routine is called. These approximate eigenvalues and vectors are +c commonly called Ritz values and Ritz vectors respectively. They are +c referred to as such in the comments that follow. The computed orthonormal +c basis for the invariant subspace corresponding to these Ritz values is c referred to as a Lanczos basis. c -c See documentation in the header of the subroutine SSAUPD for a definition -c of OP as well as other terms and the relation of computed Ritz values -c and vectors of OP with respect to the given problem A*z = lambda*B*z. +c See documentation in the header of the subroutine SSAUPD for a definition +c of OP as well as other terms and the relation of computed Ritz values +c and vectors of OP with respect to the given problem A*z = lambda*B*z. c c The approximate eigenvalues of the original problem are returned in c ascending algebraic order. The user may elect to call this routine @@ -39,19 +39,19 @@ c with a single call. c c\Usage: -c call sseupd +c call sseupd c ( RVEC, HOWMNY, SELECT, D, Z, LDZ, SIGMA, BMAT, N, WHICH, NEV, TOL, c RESID, NCV, V, LDV, IPARAM, IPNTR, WORKD, WORKL, LWORKL, INFO ) c -c RVEC LOGICAL (INPUT) -c Specifies whether Ritz vectors corresponding to the Ritz value +c RVEC LOGICAL (INPUT) +c Specifies whether Ritz vectors corresponding to the Ritz value c approximations to the eigenproblem A*z = lambda*B*z are computed. c c RVEC = .FALSE. Compute Ritz values only. c c RVEC = .TRUE. Compute Ritz vectors. c -c HOWMNY Character*1 (INPUT) +c HOWMNY Character*1 (INPUT) c Specifies how many Ritz vectors are wanted and the form of Z c the matrix of Ritz vectors. See remark 1 below. c = 'A': compute NEV Ritz vectors; @@ -61,7 +61,7 @@ c SELECT Logical array of dimension NCV. (INPUT/WORKSPACE) c If HOWMNY = 'S', SELECT specifies the Ritz vectors to be c computed. To select the Ritz vector corresponding to a -c Ritz value D(j), SELECT(j) must be set to .TRUE.. +c Ritz value D(j), SELECT(j) must be set to .TRUE.. c If HOWMNY = 'A' , SELECT is used as a workspace for c reordering the Ritz values. c @@ -70,8 +70,8 @@ c eigenvalues of A*z = lambda*B*z. The values are returned c in ascending order. If IPARAM(7) = 3,4,5 then D represents c the Ritz values of OP computed by ssaupd transformed to -c those of the original eigensystem A*z = lambda*B*z. If -c IPARAM(7) = 1,2 then the Ritz values of OP are the same +c those of the original eigensystem A*z = lambda*B*z. If +c IPARAM(7) = 1,2 then the Ritz values of OP are the same c as the those of A*z = lambda*B*z. c c Z Real N by NEV array if HOWMNY = 'A'. (OUTPUT) @@ -79,7 +79,7 @@ c eigensystem A*z = lambda*B*z corresponding to the Ritz c value approximations. c If RVEC = .FALSE. then Z is not referenced. -c NOTE: The array Z may be set equal to first NEV columns of the +c NOTE: The array Z may be set equal to first NEV columns of the c Arnoldi/Lanczos basis array V computed by SSAUPD. c c LDZ Integer. (INPUT) @@ -144,7 +144,7 @@ c = -17: SSEUPD got a different count of the number of converged c Ritz values than SSAUPD got. This indicates the user c probably made an error in passing data from SSAUPD to -c SSEUPD or that the data was modified before entering +c SSEUPD or that the data was modified before entering c SSEUPD. c c\BeginLib @@ -153,7 +153,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c 3. B.N. Parlett, "The Symmetric Eigenvalue Problem". Prentice-Hall, @@ -163,19 +163,19 @@ c 5. B. Nour-Omid, B.N. Parlett, T. Ericson, P.S. Jensen, "How to c Implement the Spectral Transformation", Math. Comp., 48 (1987), c pp 663-673. -c 6. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos -c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", +c 6. R.G. Grimes, J.G. Lewis and H.D. Simon, "A Shifted Block Lanczos +c Algorithm for Solving Sparse Symmetric Generalized Eigenproblems", c SIAM J. Matr. Anal. Apps., January (1993). c 7. L. Reichel, W.B. Gragg, "Algorithm 686: FORTRAN Subroutines c for Updating the QR decomposition", ACM TOMS, December 1990, c Volume 16 Number 4, pp 369-377. c c\Remarks -c 1. The converged Ritz values are always returned in increasing +c 1. The converged Ritz values are always returned in increasing c (algebraic) order. c c 2. Currently only HOWMNY = 'A' is implemented. It is included at this -c stage for the user who wants to incorporate it. +c stage for the user who wants to incorporate it. c c\Routines called: c ssesrt ARPACK routine that sorts an array X, and applies the @@ -201,15 +201,15 @@ c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Chao Yang Houston, Texas -c Dept. of Computational & +c Dept. of Computational & c Applied Mathematics -c Rice University -c Houston, Texas -c +c Rice University +c Houston, Texas +c c\Revision history: c 12/15/93: Version ' 2.1' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: seupd.F SID: 2.11 DATE OF SID: 04/10/01 RELEASE: 2 c c\EndLib @@ -236,7 +236,7 @@ subroutine sseupd(rvec , howmny, select, d , character bmat, howmny, which*2 logical rvec integer info, ldz, ldv, lworkl, n, ncv, nev - Real + Real & sigma, tol c c %-----------------% @@ -245,7 +245,7 @@ subroutine sseupd(rvec , howmny, select, d , c integer iparam(7), ipntr(11) logical select(ncv) - Real + Real & d(nev) , resid(n) , v(ldv,ncv), & z(ldz, nev), workd(2*n), workl(lworkl) c @@ -253,7 +253,7 @@ subroutine sseupd(rvec , howmny, select, d , c | Parameters | c %------------% c - Real + Real & one, zero parameter (one = 1.0E+0 , zero = 0.0E+0 ) c @@ -267,7 +267,7 @@ subroutine sseupd(rvec , howmny, select, d , & ldq , mode , msglvl, nconv , next , & ritz , irz , ibd , np , ishift, & leftptr, rghtptr, numcnv, jj - Real + Real & bnorm2 , rnorm, temp, temp1, eps23 logical reord c @@ -275,14 +275,14 @@ subroutine sseupd(rvec , howmny, select, d , c | External Subroutines | c %----------------------% c - external scopy , sger , sgeqr2, slacpy, sorm2r, sscal, + external scopy , sger , sgeqr2, slacpy, sorm2r, sscal, & ssesrt, ssteqr, sswap , svout , ivout , ssortr c c %--------------------% c | External Functions | c %--------------------% c - Real + Real & snrm2, slamch external snrm2, slamch c @@ -295,7 +295,7 @@ subroutine sseupd(rvec , howmny, select, d , c %-----------------------% c | Executable Statements | c %-----------------------% -c +c c %------------------------% c | Set default parameters | c %------------------------% @@ -312,7 +312,7 @@ subroutine sseupd(rvec , howmny, select, d , if (nconv .eq. 0) go to 9000 ierr = 0 c - if (nconv .le. 0) ierr = -14 + if (nconv .le. 0) ierr = -14 if (n .le. 0) ierr = -1 if (nev .le. 0) ierr = -2 if (ncv .le. nev .or. ncv .gt. n) ierr = -3 @@ -324,12 +324,12 @@ subroutine sseupd(rvec , howmny, select, d , if (bmat .ne. 'I' .and. bmat .ne. 'G') ierr = -6 if ( (howmny .ne. 'A' .and. & howmny .ne. 'P' .and. - & howmny .ne. 'S') .and. rvec ) + & howmny .ne. 'S') .and. rvec ) & ierr = -15 if (rvec .and. howmny .eq. 'S') ierr = -16 c if (rvec .and. lworkl .lt. ncv**2+8*ncv) ierr = -7 -c +c if (mode .eq. 1 .or. mode .eq. 2) then type = 'REGULR' else if (mode .eq. 3 ) then @@ -338,7 +338,7 @@ subroutine sseupd(rvec , howmny, select, d , type = 'BUCKLE' else if (mode .eq. 5 ) then type = 'CAYLEY' - else + else ierr = -10 end if if (mode .eq. 1 .and. bmat .eq. 'G') ierr = -11 @@ -352,7 +352,7 @@ subroutine sseupd(rvec , howmny, select, d , info = ierr go to 9000 end if -c +c c %-------------------------------------------------------% c | Pointer into WORKL for address of H, RITZ, BOUNDS, Q | c | etc... and the remaining workspace. | @@ -427,7 +427,7 @@ subroutine sseupd(rvec , howmny, select, d , c | Set machine dependent constant. | c %---------------------------------% c - eps23 = slamch('Epsilon-Machine') + eps23 = slamch('Epsilon-Machine') eps23 = eps23**(2.0E+0 / 3.0E+0 ) c c %---------------------------------------% @@ -513,9 +513,9 @@ subroutine sseupd(rvec , howmny, select, d , c %-----------------------------------------------------------% c if (msglvl .gt. 2) then - call ivout(logfil, 1, numcnv, ndigit, + call ivout(logfil, 1, [numcnv], ndigit, & '_seupd: Number of specified eigenvalues') - call ivout(logfil, 1, nconv, ndigit, + call ivout(logfil, 1, [nconv], ndigit, & '_seupd: Number of "converged" eigenvalues') end if c @@ -652,8 +652,8 @@ subroutine sseupd(rvec , howmny, select, d , call scopy(ncv, workl(bounds), 1, workl(ihb), 1) end if c - else -c + else +c c %-------------------------------------------------------------% c | * Make a copy of all the Ritz values. | c | * Transform the Ritz values back to the original system. | @@ -670,13 +670,13 @@ subroutine sseupd(rvec , howmny, select, d , c %-------------------------------------------------------------% c call scopy (ncv, workl(ihd), 1, workl(iw), 1) - if (type .eq. 'SHIFTI') then + if (type .eq. 'SHIFTI') then do 40 k=1, ncv workl(ihd+k-1) = one / workl(ihd+k-1) + sigma 40 continue else if (type .eq. 'BUCKLE') then do 50 k=1, ncv - workl(ihd+k-1) = sigma * workl(ihd+k-1) / + workl(ihd+k-1) = sigma * workl(ihd+k-1) / & (workl(ihd+k-1) - one) 50 continue else if (type .eq. 'CAYLEY') then @@ -685,7 +685,7 @@ subroutine sseupd(rvec , howmny, select, d , & (workl(ihd+k-1) - one) 60 continue end if -c +c c %-------------------------------------------------------------% c | * Store the wanted NCONV lambda values into D. | c | * Sort the NCONV wanted lambda in WORKL(IHD:IHD+NCONV-1) | @@ -711,8 +711,8 @@ subroutine sseupd(rvec , howmny, select, d , call ssortr('LA', .true., nconv, d, workl(ihb)) end if c - end if -c + end if +c c %------------------------------------------------% c | Compute the Ritz vectors. Transform the wanted | c | eigenvectors of the symmetric tridiagonal H by | @@ -720,25 +720,25 @@ subroutine sseupd(rvec , howmny, select, d , c %------------------------------------------------% c if (rvec .and. howmny .eq. 'A') then -c +c c %----------------------------------------------------------% c | Compute the QR factorization of the matrix representing | c | the wanted invariant subspace located in the first NCONV | c | columns of workl(iq,ldq). | c %----------------------------------------------------------% -c +c call sgeqr2(ncv, nconv , workl(iq) , & ldq, workl(iw+ncv), workl(ihb), & ierr) c c %--------------------------------------------------------% -c | * Postmultiply V by Q. | +c | * Postmultiply V by Q. | c | * Copy the first NCONV columns of VQ into Z. | c | The N by NCONV matrix Z is now a matrix representation | c | of the approximate invariant subspace associated with | c | the Ritz values in workl(ihd). | c %--------------------------------------------------------% -c +c call sorm2r('Right', 'Notranspose', n , & ncv , nconv , workl(iq), & ldq , workl(iw+ncv), v , @@ -752,7 +752,7 @@ subroutine sseupd(rvec , howmny, select, d , c %-----------------------------------------------------% c do 65 j = 1, ncv-1 - workl(ihb+j-1) = zero + workl(ihb+j-1) = zero 65 continue workl(ihb+ncv-1) = one call sorm2r('Left', 'Transpose' , ncv , @@ -794,10 +794,10 @@ subroutine sseupd(rvec , howmny, select, d , c %-------------------------------------------------% c call sscal (ncv, bnorm2, workl(ihb), 1) - if (type .eq. 'SHIFTI') then + if (type .eq. 'SHIFTI') then c do 80 k=1, ncv - workl(ihb+k-1) = abs( workl(ihb+k-1) ) + workl(ihb+k-1) = abs( workl(ihb+k-1) ) & / workl(iw+k-1)**2 80 continue c @@ -822,15 +822,15 @@ subroutine sseupd(rvec , howmny, select, d , if (type .ne. 'REGULR' .and. msglvl .gt. 1) then call svout(logfil, nconv, d, ndigit, & '_seupd: Untransformed converged Ritz values') - call svout(logfil, nconv, workl(ihb), ndigit, + call svout(logfil, nconv, workl(ihb), ndigit, & '_seupd: Ritz estimates of the untransformed Ritz values') else if (msglvl .gt. 1) then call svout(logfil, nconv, d, ndigit, & '_seupd: Converged Ritz values') - call svout(logfil, nconv, workl(ihb), ndigit, + call svout(logfil, nconv, workl(ihb), ndigit, & '_seupd: Associated Ritz estimates') end if -c +c c %-------------------------------------------------% c | Ritz vector purification step. Formally perform | c | one of inverse subspace iteration. Only used | @@ -851,7 +851,7 @@ subroutine sseupd(rvec , howmny, select, d , & / (workl(iw+k)-one) 120 continue c - end if + end if c if (type .ne. 'REGULR') & call sger (n, nconv, one, resid, 1, workl(iw), 1, z, ldz) diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssgets.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssgets.f index 55915493a274..f40ca76a8a1c 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssgets.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssgets.f @@ -3,13 +3,13 @@ c c\Name: ssgets c -c\Description: +c\Description: c Given the eigenvalues of the symmetric tridiagonal matrix H, -c computes the NP shifts AMU that are zeros of the polynomial of -c degree NP which filters out components of the unwanted eigenvectors +c computes the NP shifts AMU that are zeros of the polynomial of +c degree NP which filters out components of the unwanted eigenvectors c corresponding to the AMU's based on some given criteria. c -c NOTE: This is called even in the case of user specified shifts in +c NOTE: This is called even in the case of user specified shifts in c order to sort the eigenvalues, and error bounds of H for later use. c c\Usage: @@ -39,8 +39,8 @@ c c RITZ Real array of length KEV+NP. (INPUT/OUTPUT) c On INPUT, RITZ contains the eigenvalues of H. -c On OUTPUT, RITZ are sorted so that the unwanted eigenvalues -c are in the first NP locations and the wanted part is in +c On OUTPUT, RITZ are sorted so that the unwanted eigenvalues +c are in the first NP locations and the wanted part is in c the last KEV locations. When exact shifts are selected, the c unwanted part corresponds to the shifts to be applied. c @@ -49,7 +49,7 @@ c c SHIFTS Real array of length NP. (INPUT/OUTPUT) c On INPUT: contains the user specified shifts if ISHIFT = 0. -c On OUTPUT: contains the shifts sorted into decreasing order +c On OUTPUT: contains the shifts sorted into decreasing order c of magnitude with respect to the Ritz estimates contained in c BOUNDS. If ISHIFT = 0, SHIFTS is not modified on exit. c @@ -75,13 +75,13 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/93: Version ' 2.1' c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sgets.F SID: 2.4 DATE OF SID: 4/19/96 RELEASE: 2 c c\Remarks @@ -142,7 +142,7 @@ subroutine ssgets ( ishift, which, kev, np, ritz, bounds, shifts ) c %-----------------------% c | Executable Statements | c %-----------------------% -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -150,7 +150,7 @@ subroutine ssgets ( ishift, which, kev, np, ritz, bounds, shifts ) c call arscnd (t0) msglvl = msgets -c +c if (which .eq. 'BE') then c c %-----------------------------------------------------% @@ -163,11 +163,11 @@ subroutine ssgets ( ishift, which, kev, np, ritz, bounds, shifts ) c %-----------------------------------------------------% c call ssortr ('LA', .true., kev+np, ritz, bounds) - kevd2 = kev / 2 + kevd2 = kev / 2 if ( kev .gt. 1 ) then - call sswap ( min(kevd2,np), ritz, 1, + call sswap ( min(kevd2,np), ritz, 1, & ritz( max(kevd2,np)+1 ), 1) - call sswap ( min(kevd2,np), bounds, 1, + call sswap ( min(kevd2,np), bounds, 1, & bounds( max(kevd2,np)+1 ), 1) end if c @@ -185,7 +185,7 @@ subroutine ssgets ( ishift, which, kev, np, ritz, bounds, shifts ) end if c if (ishift .eq. 1 .and. np .gt. 0) then -c +c c %-------------------------------------------------------% c | Sort the unwanted Ritz values used as shifts so that | c | the ones with largest Ritz estimates are first. | @@ -193,23 +193,23 @@ subroutine ssgets ( ishift, which, kev, np, ritz, bounds, shifts ) c | forward instability of the iteration when the shifts | c | are applied in subroutine ssapps. | c %-------------------------------------------------------% -c +c call ssortr ('SM', .true., np, bounds, ritz) call scopy (np, ritz, 1, shifts, 1) end if -c +c call arscnd (t1) tsgets = tsgets + (t1 - t0) c if (msglvl .gt. 0) then - call ivout (logfil, 1, kev, ndigit, '_sgets: KEV is') - call ivout (logfil, 1, np, ndigit, '_sgets: NP is') + call ivout (logfil, 1, [kev], ndigit, '_sgets: KEV is') + call ivout (logfil, 1, [np], ndigit, '_sgets: NP is') call svout (logfil, kev+np, ritz, ndigit, & '_sgets: Eigenvalues of current H matrix') - call svout (logfil, kev+np, bounds, ndigit, + call svout (logfil, kev+np, bounds, ndigit, & '_sgets: Associated Ritz estimates') end if -c +c return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssortc.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssortc.f index dba628ff926f..e322039cdd24 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssortc.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssortc.f @@ -4,7 +4,7 @@ c\Name: ssortc c c\Description: -c Sorts the complex array in XREAL and XIMAG into the order +c Sorts the complex array in XREAL and XIMAG into the order c specified by WHICH and optionally applies the permutation to the c real array Y. It is assumed that if an element of XIMAG is c nonzero, then its negative is also an element. In other words, @@ -49,14 +49,14 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c xx/xx/92: Version ' 2.1' c Adapted from the sort routine in LANSO. c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sortc.F SID: 2.3 DATE OF SID: 4/20/96 RELEASE: 2 c c\EndLib @@ -77,7 +77,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) c | Array Arguments | c %-----------------% c - Real + Real & xreal(0:n-1), ximag(0:n-1), y(0:n-1) c c %---------------% @@ -85,14 +85,14 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) c %---------------% c integer i, igap, j - Real + Real & temp, temp1, temp2 c c %--------------------% c | External Functions | c %--------------------% c - Real + Real & slapy2 external slapy2 c @@ -101,7 +101,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) c %-----------------------% c igap = n / 2 -c +c if (which .eq. 'LM') then c c %------------------------------------------------------% @@ -169,7 +169,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) temp = ximag(j) ximag(j) = ximag(j+igap) ximag(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -183,7 +183,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) 60 continue igap = igap / 2 go to 40 -c +c else if (which .eq. 'LR') then c c %------------------------------------------------% @@ -207,7 +207,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) temp = ximag(j) ximag(j) = ximag(j+igap) ximag(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -221,7 +221,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) 90 continue igap = igap / 2 go to 70 -c +c else if (which .eq. 'SR') then c c %------------------------------------------------% @@ -244,7 +244,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) temp = ximag(j) ximag(j) = ximag(j+igap) ximag(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -258,7 +258,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) 120 continue igap = igap / 2 go to 100 -c +c else if (which .eq. 'LI') then c c %------------------------------------------------% @@ -281,7 +281,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) temp = ximag(j) ximag(j) = ximag(j+igap) ximag(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -295,7 +295,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) 150 continue igap = igap / 2 go to 130 -c +c else if (which .eq. 'SI') then c c %------------------------------------------------% @@ -318,7 +318,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) temp = ximag(j) ximag(j) = ximag(j+igap) ximag(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -333,7 +333,7 @@ subroutine ssortc (which, apply, n, xreal, ximag, y) igap = igap / 2 go to 160 end if -c +c 9000 continue return c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssortr.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssortr.f index 267b1251c1b8..25d324b657cd 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssortr.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/ssortr.f @@ -4,7 +4,7 @@ c\Name: ssortr c c\Description: -c Sort the array X1 in the order specified by WHICH and optionally +c Sort the array X1 in the order specified by WHICH and optionally c applies the permutation to the array X2. c c\Usage: @@ -39,17 +39,17 @@ c c\Author c Danny Sorensen Phuong Vu -c Richard Lehoucq CRPC / Rice University -c Dept. of Computational & Houston, Texas +c Richard Lehoucq CRPC / Rice University +c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c c\Revision history: c 12/16/93: Version ' 2.1'. c Adapted from the sort routine in LANSO. c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: sortr.F SID: 2.3 DATE OF SID: 4/19/96 RELEASE: 2 c c\EndLib @@ -86,7 +86,7 @@ subroutine ssortr (which, apply, n, x1, x2) c %-----------------------% c igap = n / 2 -c +c if (which .eq. 'SA') then c c X1 is sorted into decreasing order of algebraic. @@ -158,7 +158,7 @@ subroutine ssortr (which, apply, n, x1, x2) 80 continue c if (j.lt.0) go to 90 -c +c if (x1(j).gt.x1(j+igap)) then temp = x1(j) x1(j) = x1(j+igap) @@ -176,7 +176,7 @@ subroutine ssortr (which, apply, n, x1, x2) 90 continue igap = igap / 2 go to 70 -c +c else if (which .eq. 'LM') then c c X1 is sorted into increasing order of magnitude. diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstatn.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstatn.f index cdf28830733a..f3288c1aba91 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstatn.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstatn.f @@ -9,10 +9,10 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: statn.F SID: 2.4 DATE OF SID: 4/20/96 RELEASE: 2 c subroutine sstatn @@ -22,7 +22,7 @@ subroutine sstatn c %--------------------------------% c include 'stat.h' -c +c c %-----------------------% c | Executable Statements | c %-----------------------% @@ -32,7 +32,7 @@ subroutine sstatn nrorth = 0 nitref = 0 nrstrt = 0 -c +c tnaupd = 0.0E+0 tnaup2 = 0.0E+0 tnaitr = 0.0E+0 @@ -43,14 +43,14 @@ subroutine sstatn titref = 0.0E+0 tgetv0 = 0.0E+0 trvec = 0.0E+0 -c +c c %----------------------------------------------------% c | User time including reverse communication overhead | c %----------------------------------------------------% c tmvopx = 0.0E+0 tmvbx = 0.0E+0 -c +c return c c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstats.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstats.f index 86109dcb47bc..0822d3f3aa96 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstats.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstats.f @@ -1,18 +1,18 @@ c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: stats.F SID: 2.1 DATE OF SID: 4/19/96 RELEASE: 2 c %---------------------------------------------% c | Initialize statistic and timing information | c | for symmetric Arnoldi code. | c %---------------------------------------------% - + subroutine sstats c %--------------------------------% c | See stat.doc for documentation | c %--------------------------------% include 'stat.h' - + c %-----------------------% c | Executable Statements | c %-----------------------% @@ -22,7 +22,7 @@ subroutine sstats nrorth = 0 nitref = 0 nrstrt = 0 - + tsaupd = 0.0E+0 tsaup2 = 0.0E+0 tsaitr = 0.0E+0 @@ -33,13 +33,13 @@ subroutine sstats titref = 0.0E+0 tgetv0 = 0.0E+0 trvec = 0.0E+0 - + c %----------------------------------------------------% c | User time including reverse communication overhead | c %----------------------------------------------------% tmvopx = 0.0E+0 tmvbx = 0.0E+0 - + return c c End of sstats diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstqrb.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstqrb.f index 9fd1e192570d..9697c366022d 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstqrb.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/sstqrb.f @@ -32,13 +32,13 @@ c On exit, E has been destroyed. c c Z Real array, dimension (N). (OUTPUT) -c On exit, Z contains the last row of the orthonormal -c eigenvector matrix of the symmetric tridiagonal matrix. +c On exit, Z contains the last row of the orthonormal +c eigenvector matrix of the symmetric tridiagonal matrix. c If an error exit is made, Z contains the last row of the c eigenvector matrix associated with the stored eigenvalues. c c WORK Real array, dimension (max(1,2*N-2)). (WORKSPACE) -c Workspace used in accumulating the transformation for +c Workspace used in accumulating the transformation for c computing the last components of the eigenvectors. c c INFO Integer. (OUTPUT) @@ -62,9 +62,9 @@ c scopy Level 1 BLAS that copies one vector to another. c sswap Level 1 BLAS that swaps the contents of two vectors. c lsame LAPACK character comparison routine. -c slae2 LAPACK routine that computes the eigenvalues of a 2-by-2 +c slae2 LAPACK routine that computes the eigenvalues of a 2-by-2 c symmetric matrix. -c slaev2 LAPACK routine that eigendecomposition of a 2-by-2 symmetric +c slaev2 LAPACK routine that eigendecomposition of a 2-by-2 symmetric c matrix. c slamch LAPACK routine that determines machine constants. c slanst LAPACK routine that computes the norm of a matrix. @@ -72,7 +72,7 @@ c slartg LAPACK Givens rotation construction routine. c slascl LAPACK routine for careful scaling of a matrix. c slaset LAPACK matrix initialization routine. -c slasr LAPACK routine that applies an orthogonal transformation to +c slasr LAPACK routine that applies an orthogonal transformation to c a matrix. c slasrt LAPACK sorting routine. c ssteqr LAPACK routine that computes eigenvalues and eigenvectors @@ -84,19 +84,19 @@ c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas c Applied Mathematics -c Rice University -c Houston, Texas +c Rice University +c Houston, Texas c -c\SCCS Information: @(#) +c\SCCS Information: @(#) c FILE: stqrb.F SID: 2.5 DATE OF SID: 8/27/96 RELEASE: 2 c c\Remarks c 1. Starting with version 2.5, this routine is a modified version c of LAPACK version 2.0 subroutine SSTEQR. No lines are deleted, -c only commeted out and new lines inserted. +c only commented out and new lines inserted. c All lines commented out have "c$$$" at the beginning. c Note that the LAPACK version 1.0 subroutine SSTEQR contained -c bugs. +c bugs. c c\EndLib c @@ -118,9 +118,9 @@ subroutine sstqrb ( n, d, e, z, work, info ) & d( n ), e( n-1 ), z( n ), work( 2*n-2 ) c c .. parameters .. - Real + Real & zero, one, two, three - parameter ( zero = 0.0E+0, one = 1.0E+0, + parameter ( zero = 0.0E+0, one = 1.0E+0, & two = 2.0E+0, three = 3.0E+0 ) integer maxit parameter ( maxit = 30 ) @@ -129,7 +129,7 @@ subroutine sstqrb ( n, d, e, z, work, info ) integer i, icompz, ii, iscale, j, jtot, k, l, l1, lend, & lendm1, lendp1, lendsv, lm1, lsv, m, mm, mm1, & nm1, nmaxit - Real + Real & anorm, b, c, eps, eps2, f, g, p, r, rt1, rt2, & s, safmax, safmin, ssfmax, ssfmin, tst c .. @@ -380,9 +380,9 @@ subroutine sstqrb ( n, d, e, z, work, info ) c c *** New starting with version 2.5 *** c - call slasr( 'r', 'v', 'b', 1, mm, work( l ), + call slasr( 'r', 'v', 'b', 1, mm, work( l ), & work( n-1+l ), z( l ), 1 ) -c ************************************* +c ************************************* end if c d( l ) = d( l ) - p @@ -440,7 +440,7 @@ subroutine sstqrb ( n, d, e, z, work, info ) tst = z(l) z(l) = c*tst - s*z(l-1) z(l-1) = s*tst + c*z(l-1) -c ************************************* +c ************************************* else call slae2( d( l-1 ), e( l-1 ), d( l ), rt1, rt2 ) end if @@ -502,7 +502,7 @@ subroutine sstqrb ( n, d, e, z, work, info ) c call slasr( 'r', 'v', 'f', 1, mm, work( m ), work( n-1+m ), & z( m ), 1 ) -c ************************************* +c ************************************* end if c d( l ) = d( l ) - p diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zgetv0.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zgetv0.f index c36e7a9a262b..1fbd5085195b 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zgetv0.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zgetv0.f @@ -2,13 +2,13 @@ c c\Name: zgetv0 c -c\Description: +c\Description: c Generate a random initial residual vector for the Arnoldi process. -c Force the residual vector to be in the range of the operator OP. +c Force the residual vector to be in the range of the operator OP. c c\Usage: c call zgetv0 -c ( IDO, BMAT, ITRY, INITV, N, J, V, LDV, RESID, RNORM, +c ( IDO, BMAT, ITRY, INITV, N, J, V, LDV, RESID, RNORM, c IPNTR, WORKD, IERR ) c c\Arguments @@ -35,7 +35,7 @@ c B = 'G' -> generalized eigenvalue problem A*x = lambda*B*x c c ITRY Integer. (INPUT) -c ITRY counts the number of times that zgetv0 is called. +c ITRY counts the number of times that zgetv0 is called. c It should be set to 1 on the initial call to zgetv0. c c INITV Logical variable. (INPUT) @@ -54,11 +54,11 @@ c if this is a "restart". c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c RESID Complex*16 array of length N. (INPUT/OUTPUT) -c Initial residual vector to be generated. If RESID is +c Initial residual vector to be generated. If RESID is c provided, force RESID into the range of the operator OP. c c RNORM Double precision scalar. (OUTPUT) @@ -91,19 +91,19 @@ c\Routines called: c arscnd ARPACK utility routine for timing. c zvout ARPACK utility routine that prints vectors. -c zlarnv LAPACK routine for generating a random vector. +c zlarnv LAPACK routine for generating a random vector. c zgemv Level 2 BLAS routine for matrix vector multiplication. c zcopy Level 1 BLAS that copies one vector to another. -c wzdotc Level 1 BLAS that computes the scalar product of two vectors. -c dznrm2 Level 1 BLAS that computes the norm of a vector. +c zdotc Level 1 BLAS that computes the scalar product of two vectors. +c dznrm2 Level 1 BLAS that computes the norm of a vector. c c\Author c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c\SCCS Information: @(#) c FILE: getv0.F SID: 2.3 DATE OF SID: 08/27/96 RELEASE: 2 @@ -112,10 +112,10 @@ c c----------------------------------------------------------------------- c - subroutine zgetv0 - & ( ido, bmat, itry, initv, n, j, v, ldv, resid, rnorm, + subroutine zgetv0 + & ( ido, bmat, itry, initv, n, j, v, ldv, resid, rnorm, & ipntr, workd, ierr ) -c +c c %----------------------------------------------------% c | Include files for debugging and timing information | c %----------------------------------------------------% @@ -174,11 +174,11 @@ subroutine zgetv0 c | External Functions | c %--------------------% c - Double precision + Double precision & dznrm2, dlapy2 Complex*16 - & wzdotc - external wzdotc, dznrm2, dlapy2 + & zzdotc + external zzdotc, dznrm2, dlapy2 c c %-----------------% c | Data Statements | @@ -205,7 +205,7 @@ subroutine zgetv0 end if c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -213,7 +213,7 @@ subroutine zgetv0 c call arscnd (t0) msglvl = mgetv0 -c +c ierr = 0 iter = 0 first = .FALSE. @@ -232,38 +232,40 @@ subroutine zgetv0 idist = 2 call zlarnv (idist, iseed, n, resid) end if -c +c c %----------------------------------------------------------% c | Force the starting vector into the range of OP to handle | c | the generalized problem when B is possibly (singular). | c %----------------------------------------------------------% c call arscnd (t2) - if (bmat .eq. 'G') then + if (itry .eq. 1) then nopx = nopx + 1 ipntr(1) = 1 ipntr(2) = n + 1 call zcopy (n, resid, 1, workd, 1) ido = -1 go to 9000 + else if (itry .gt. 1 .and. bmat .eq. 'G') then + call zcopy (n, resid, 1, workd(n + 1), 1) end if end if -c +c c %----------------------------------------% -c | Back from computing B*(initial-vector) | +c | Back from computing OP*(initial-vector) | c %----------------------------------------% c if (first) go to 20 c c %-----------------------------------------------% -c | Back from computing B*(orthogonalized-vector) | +c | Back from computing OP*(orthogonalized-vector) | c %-----------------------------------------------% c if (orth) go to 40 -c +c call arscnd (t3) tmvopx = tmvopx + (t3 - t2) -c +c c %------------------------------------------------------% c | Starting vector is now in the range of OP; r = OP*r; | c | Compute B-norm of starting vector. | @@ -271,9 +273,9 @@ subroutine zgetv0 c call arscnd (t2) first = .TRUE. + if (itry .eq. 1) call zcopy (n, workd(n + 1), 1, resid, 1) if (bmat .eq. 'G') then nbx = nbx + 1 - call zcopy (n, workd(n+1), 1, resid, 1) ipntr(1) = n + 1 ipntr(2) = 1 ido = 2 @@ -281,18 +283,18 @@ subroutine zgetv0 else if (bmat .eq. 'I') then call zcopy (n, resid, 1, workd, 1) end if -c +c 20 continue c if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c first = .FALSE. if (bmat .eq. 'G') then - cnorm = wzdotc (n, resid, 1, workd, 1) - rnorm0 = sqrt(dlapy2(dble(cnorm),dimag(cnorm))) + cnorm = zzdotc (n, resid, 1, workd, 1) + rnorm0 = sqrt(dlapy2(dble(cnorm),aimag(cnorm))) else if (bmat .eq. 'I') then rnorm0 = dznrm2(n, resid, 1) end if @@ -303,7 +305,7 @@ subroutine zgetv0 c %---------------------------------------------% c if (j .eq. 1) go to 50 -c +c c %---------------------------------------------------------------- c | Otherwise need to B-orthogonalize the starting vector against | c | the current Arnoldi basis using Gram-Schmidt with iter. ref. | @@ -319,11 +321,11 @@ subroutine zgetv0 orth = .TRUE. 30 continue c - call zgemv ('C', n, j-1, one, v, ldv, workd, 1, + call zgemv ('C', n, j-1, one, v, ldv, workd, 1, & zero, workd(n+1), 1) - call zgemv ('N', n, j-1, -one, v, ldv, workd(n+1), 1, + call zgemv ('N', n, j-1, -one, v, ldv, workd(n+1), 1, & one, resid, 1) -c +c c %----------------------------------------------------------% c | Compute the B-norm of the orthogonalized starting vector | c %----------------------------------------------------------% @@ -339,17 +341,17 @@ subroutine zgetv0 else if (bmat .eq. 'I') then call zcopy (n, resid, 1, workd, 1) end if -c +c 40 continue c if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c if (bmat .eq. 'G') then - cnorm = wzdotc (n, resid, 1, workd, 1) - rnorm = sqrt(dlapy2(dble(cnorm),dimag(cnorm))) + cnorm = zzdotc (n, resid, 1, workd, 1) + rnorm = sqrt(dlapy2(dble(cnorm),aimag(cnorm))) else if (bmat .eq. 'I') then rnorm = dznrm2(n, resid, 1) end if @@ -359,14 +361,14 @@ subroutine zgetv0 c %--------------------------------------% c if (msglvl .gt. 2) then - call dvout (logfil, 1, rnorm0, ndigit, + call dvout (logfil, 1, [rnorm0], ndigit, & '_getv0: re-orthonalization ; rnorm0 is') - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_getv0: re-orthonalization ; rnorm is') end if c if (rnorm .gt. 0.717*rnorm0) go to 50 -c +c iter = iter + 1 if (iter .le. 1) then c @@ -388,11 +390,11 @@ subroutine zgetv0 rnorm = rzero ierr = -1 end if -c +c 50 continue c if (msglvl .gt. 0) then - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_getv0: B-norm of initial / restarted starting vector') end if if (msglvl .gt. 2) then @@ -400,10 +402,10 @@ subroutine zgetv0 & '_getv0: initial / restarted starting vector') end if ido = 99 -c +c call arscnd (t1) tgetv0 = tgetv0 + (t1 - t0) -c +c 9000 continue return c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaitr.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaitr.f index b3def53df04f..240412ca02be 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaitr.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaitr.f @@ -2,8 +2,8 @@ c c\Name: znaitr c -c\Description: -c Reverse communication interface for applying NP additional steps to +c\Description: +c Reverse communication interface for applying NP additional steps to c a K step nonsymmetric Arnoldi factorization. c c Input: OP*V_{k} - V_{k}*H = r_{k}*e_{k}^T @@ -19,7 +19,7 @@ c c\Usage: c call znaitr -c ( IDO, BMAT, N, K, NP, NB, RESID, RNORM, V, LDV, H, LDH, +c ( IDO, BMAT, N, K, NP, NB, RESID, RNORM, V, LDV, H, LDH, c IPNTR, WORKD, INFO ) c c\Arguments @@ -61,8 +61,8 @@ c Number of additional Arnoldi steps to take. c c NB Integer. (INPUT) -c Blocksize to be used in the recurrence. -c Only work for NB = 1 right now. The goal is to have a +c Blocksize to be used in the recurrence. +c Only work for NB = 1 right now. The goal is to have a c program that implement both the block and non-block method. c c RESID Complex*16 array of length N. (INPUT/OUTPUT) @@ -74,37 +74,37 @@ c B-norm of the updated residual r_{k+p} on output. c c V Complex*16 N by K+NP array. (INPUT/OUTPUT) -c On INPUT: V contains the Arnoldi vectors in the first K +c On INPUT: V contains the Arnoldi vectors in the first K c columns. c On OUTPUT: V contains the new NP Arnoldi vectors in the next c NP columns. The first K columns are unchanged. c c LDV Integer. (INPUT) -c Leading dimension of V exactly as declared in the calling +c Leading dimension of V exactly as declared in the calling c program. c c H Complex*16 (K+NP) by (K+NP) array. (INPUT/OUTPUT) c H is used to store the generated upper Hessenberg matrix. c c LDH Integer. (INPUT) -c Leading dimension of H exactly as declared in the calling +c Leading dimension of H exactly as declared in the calling c program. c c IPNTR Integer array of length 3. (OUTPUT) -c Pointer to mark the starting locations in the WORK for +c Pointer to mark the starting locations in the WORK for c vectors used by the Arnoldi iteration. c ------------------------------------------------------------- c IPNTR(1): pointer to the current operand vector X. c IPNTR(2): pointer to the current result vector Y. -c IPNTR(3): pointer to the vector B * X when used in the +c IPNTR(3): pointer to the vector B * X when used in the c shift-and-invert mode. X is the current operand. c ------------------------------------------------------------- -c +c c WORKD Complex*16 work array of length 3*N. (REVERSE COMMUNICATION) c Distributed array to be used in the basic Arnoldi iteration -c for reverse communication. The calling program should not +c for reverse communication. The calling program should not c use WORKD as temporary workspace during the iteration !!!!!! -c On input, WORKD(1:N) = B*RESID and is used to save some +c On input, WORKD(1:N) = B*RESID and is used to save some c computation at the first step. c c INFO Integer. (OUTPUT) @@ -124,7 +124,7 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c @@ -143,29 +143,29 @@ c zgemv Level 2 BLAS routine for matrix vector multiplication. c zaxpy Level 1 BLAS that computes a vector triad. c zcopy Level 1 BLAS that copies one vector to another . -c wzdotc Level 1 BLAS that computes the scalar product of two vectors. +c zdotc Level 1 BLAS that computes the scalar product of two vectors. c zscal Level 1 BLAS that scales a vector. -c zdscal Level 1 BLAS that scales a complex vector by a real number. +c zdscal Level 1 BLAS that scales a complex vector by a real number. c dznrm2 Level 1 BLAS that computes the norm of a vector. c c\Author c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University -c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas -c +c Dept. of Computational & Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas +c c\SCCS Information: @(#) c FILE: naitr.F SID: 2.3 DATE OF SID: 8/27/96 RELEASE: 2 c c\Remarks c The algorithm implemented is: -c +c c restart = .false. -c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; +c Given V_{k} = [v_{1}, ..., v_{k}], r_{k}; c r_{k} contains the initial residual vector even for k = 0; -c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already +c Also assume that rnorm = || B*r_{k} || and B*r_{k} are already c computed by the calling program. c c betaj = rnorm ; p_{k+1} = B*r_{k} ; @@ -173,7 +173,7 @@ c 1) if ( betaj < tol ) stop or restart depending on j. c ( At present tol is zero ) c if ( restart ) generate a new starting vector. -c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; +c 2) v_{j} = r(j-1)/betaj; V_{j} = [V_{j-1}, v_{j}]; c p_{j} = p_{j}/betaj c 3) r_{j} = OP*v_{j} where OP is defined as in znaupd c For shift-invert mode p_{j} = B*v_{j} is already available. @@ -188,7 +188,7 @@ c 5) Re-orthogonalization step: c s = V_{j}'*B*r_{j} c r_{j} = r_{j} - V_{j}*s; rnorm1 = || r_{j} || -c alphaj = alphaj + s_{j}; +c alphaj = alphaj + s_{j}; c 6) Iterative refinement step: c If (rnorm1 > 0.717*rnorm) then c rnorm = rnorm1 @@ -198,7 +198,7 @@ c If this is the first time in step 6), go to 5) c Else r_{j} lies in the span of V_{j} numerically. c Set r_{j} = 0 and rnorm = 0; go to 1) -c EndIf +c EndIf c End Do c c\EndLib @@ -206,7 +206,7 @@ c----------------------------------------------------------------------- c subroutine znaitr - & (ido, bmat, n, k, np, nb, resid, rnorm, v, ldv, h, ldh, + & (ido, bmat, n, k, np, nb, resid, rnorm, v, ldv, h, ldh, & ipntr, workd, info) c c %----------------------------------------------------% @@ -241,7 +241,7 @@ subroutine znaitr & one, zero Double precision & rone, rzero - parameter (one = (1.0D+0, 0.0D+0), zero = (0.0D+0, 0.0D+0), + parameter (one = (1.0D+0, 0.0D+0), zero = (0.0D+0, 0.0D+0), & rone = 1.0D+0, rzero = 0.0D+0) c c %--------------% @@ -258,7 +258,7 @@ subroutine znaitr logical first, orth1, orth2, rstart, step3, step4 integer ierr, i, infol, ipj, irj, ivj, iter, itry, j, msglvl, & jj - Double precision + Double precision & ovfl, smlnum, tst1, ulp, unfl, betaj, & temp1, rnorm1, wnorm Complex*16 @@ -272,7 +272,7 @@ subroutine znaitr c | External Subroutines | c %----------------------% c - external zaxpy, zcopy, zscal, zdscal, zgemv, zgetv0, + external zaxpy, zcopy, zscal, zdscal, zgemv, zgetv0, & dlabad, zvout, zmout, ivout, arscnd c c %--------------------% @@ -280,16 +280,16 @@ subroutine znaitr c %--------------------% c Complex*16 - & wzdotc - Double precision + & zzdotc + Double precision & dlamch, dznrm2, zlanhs, dlapy2 - external wzdotc, dznrm2, zlanhs, dlamch, dlapy2 + external zzdotc, dznrm2, zlanhs, dlamch, dlapy2 c c %---------------------% c | Intrinsic Functions | c %---------------------% c - intrinsic dimag, dble, max, sqrt + intrinsic aimag, dble, max, sqrt c c %-----------------% c | Data statements | @@ -320,7 +320,7 @@ subroutine znaitr end if c if (ido .eq. 0) then -c +c c %-------------------------------% c | Initialize timing statistics | c | & message level for debugging | @@ -328,7 +328,7 @@ subroutine znaitr c call arscnd (t0) msglvl = mcaitr -c +c c %------------------------------% c | Initial call to this routine | c %------------------------------% @@ -344,7 +344,7 @@ subroutine znaitr irj = ipj + n ivj = irj + n end if -c +c c %-------------------------------------------------% c | When in reverse communication mode one of: | c | STEP3, STEP4, ORTH1, ORTH2, RSTART | @@ -374,16 +374,16 @@ subroutine znaitr c | | c | Note: B*r_{j-1} is already in WORKD(1:N)=WORKD(IPJ:IPJ+N-1) | c %--------------------------------------------------------------% - + 1000 continue c if (msglvl .gt. 1) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: generating Arnoldi vector number') - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_naitr: B-norm of the current residual is') end if -c +c c %---------------------------------------------------% c | STEP 1: Check if the B norm of j-th residual | c | vector is zero. Equivalent to determine whether | @@ -400,16 +400,16 @@ subroutine znaitr c %---------------------------------------------------% c if (msglvl .gt. 0) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: ****** RESTART AT STEP ******') end if -c +c c %---------------------------------------------% c | ITRY is the loop variable that controls the | c | maximum amount of times that a restart is | c | attempted. NRSTRT is used by stat.h | c %---------------------------------------------% -c +c betaj = rzero nrstrt = nrstrt + 1 itry = 1 @@ -423,7 +423,7 @@ subroutine znaitr c | RSTART = .true. flow returns here. | c %--------------------------------------% c - call zgetv0 (ido, bmat, itry, .false., n, j, v, ldv, + call zgetv0 (ido, bmat, itry, .false., n, j, v, ldv, & resid, rnorm, ipntr, workd, ierr) if (ido .ne. 99) go to 9000 if (ierr .lt. 0) then @@ -442,7 +442,7 @@ subroutine znaitr ido = 99 go to 9000 end if -c +c 40 continue c c %---------------------------------------------------------% @@ -466,7 +466,7 @@ subroutine znaitr c call zlascl ('General', i, i, rnorm, rone, & n, 1, v(1,j), n, infol) - call zlascl ('General', i, i, rnorm, rone, + call zlascl ('General', i, i, rnorm, rone, & n, 1, workd(ipj), n, infol) end if c @@ -483,14 +483,14 @@ subroutine znaitr ipntr(2) = irj ipntr(3) = ipj ido = 1 -c +c c %-----------------------------------% c | Exit in order to compute OP*v_{j} | c %-----------------------------------% -c - go to 9000 +c + go to 9000 50 continue -c +c c %----------------------------------% c | Back from reverse communication; | c | WORKD(IRJ:IRJ+N-1) := OP*v_{j} | @@ -499,7 +499,7 @@ subroutine znaitr c call arscnd (t3) tmvopx = tmvopx + (t3 - t2) - + step3 = .false. c c %------------------------------------------% @@ -507,7 +507,7 @@ subroutine znaitr c %------------------------------------------% c call zcopy (n, workd(irj), 1, resid, 1) -c +c c %---------------------------------------% c | STEP 4: Finish extending the Arnoldi | c | factorization to length j. | @@ -520,17 +520,17 @@ subroutine znaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-------------------------------------% c | Exit in order to compute B*OP*v_{j} | c %-------------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call zcopy (n, resid, 1, workd(ipj), 1) end if 60 continue -c +c c %----------------------------------% c | Back from reverse communication; | c | WORKD(IPJ:IPJ+N-1) := B*OP*v_{j} | @@ -541,7 +541,7 @@ subroutine znaitr call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c step4 = .false. c c %-------------------------------------% @@ -549,9 +549,9 @@ subroutine znaitr c | Compute the B-norm of OP*v_{j}. | c %-------------------------------------% c - if (bmat .eq. 'G') then - cnorm = wzdotc (n, resid, 1, workd(ipj), 1) - wnorm = sqrt( dlapy2(dble(cnorm),dimag(cnorm)) ) + if (bmat .eq. 'G') then + cnorm = zzdotc (n, resid, 1, workd(ipj), 1) + wnorm = sqrt( dlapy2(dble(cnorm),aimag(cnorm)) ) else if (bmat .eq. 'I') then wnorm = dznrm2(n, resid, 1) end if @@ -569,24 +569,24 @@ subroutine znaitr c | Compute the j Fourier coefficients w_{j} | c | WORKD(IPJ:IPJ+N-1) contains B*OP*v_{j}. | c %------------------------------------------% -c +c call zgemv ('C', n, j, one, v, ldv, workd(ipj), 1, & zero, h(1,j), 1) c c %--------------------------------------% c | Orthogonalize r_{j} against V_{j}. | -c | RESID contains OP*v_{j}. See STEP 3. | +c | RESID contains OP*v_{j}. See STEP 3. | c %--------------------------------------% c call zgemv ('N', n, j, -one, v, ldv, h(1,j), 1, & one, resid, 1) c - if (j .gt. 1) h(j,j-1) = dcmplx(betaj, rzero) + if (j .gt. 1) h(j,j-1) = cmplx(betaj, rzero, Kind=Kind(0d0)) c call arscnd (t4) -c +c orth1 = .true. -c +c call arscnd (t2) if (bmat .eq. 'G') then nbx = nbx + 1 @@ -594,17 +594,17 @@ subroutine znaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %----------------------------------% c | Exit in order to compute B*r_{j} | c %----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call zcopy (n, resid, 1, workd(ipj), 1) - end if + end if 70 continue -c +c c %---------------------------------------------------% c | Back from reverse communication if ORTH1 = .true. | c | WORKD(IPJ:IPJ+N-1) := B*r_{j}. | @@ -614,20 +614,20 @@ subroutine znaitr call arscnd (t3) tmvbx = tmvbx + (t3 - t2) end if -c +c orth1 = .false. c c %------------------------------% c | Compute the B-norm of r_{j}. | c %------------------------------% c - if (bmat .eq. 'G') then - cnorm = wzdotc (n, resid, 1, workd(ipj), 1) - rnorm = sqrt( dlapy2(dble(cnorm),dimag(cnorm)) ) + if (bmat .eq. 'G') then + cnorm = zzdotc (n, resid, 1, workd(ipj), 1) + rnorm = sqrt( dlapy2(dble(cnorm),aimag(cnorm)) ) else if (bmat .eq. 'I') then rnorm = dznrm2(n, resid, 1) end if -c +c c %-----------------------------------------------------------% c | STEP 5: Re-orthogonalization / Iterative refinement phase | c | Maximum NITER_ITREF tries. | @@ -650,20 +650,20 @@ subroutine znaitr c iter = 0 nrorth = nrorth + 1 -c +c c %---------------------------------------------------% c | Enter the Iterative refinement phase. If further | c | refinement is necessary, loop back here. The loop | c | variable is ITER. Perform a step of Classical | c | Gram-Schmidt using all the Arnoldi vectors V_{j} | c %---------------------------------------------------% -c +c 80 continue c if (msglvl .gt. 2) then rtemp(1) = wnorm rtemp(2) = rnorm - call dvout (logfil, 2, rtemp, ndigit, + call dvout (logfil, 2, rtemp, ndigit, & '_naitr: re-orthogonalization; wnorm and rnorm are') call zvout (logfil, j, h(1,j), ndigit, & '_naitr: j-th column of H') @@ -674,7 +674,7 @@ subroutine znaitr c | WORKD(IRJ:IRJ+J-1) = v(:,1:J)'*WORKD(IPJ:IPJ+N-1). | c %----------------------------------------------------% c - call zgemv ('C', n, j, one, v, ldv, workd(ipj), 1, + call zgemv ('C', n, j, one, v, ldv, workd(ipj), 1, & zero, workd(irj), 1) c c %---------------------------------------------% @@ -684,10 +684,10 @@ subroutine znaitr c | + v(:,1:J)*WORKD(IRJ:IRJ+J-1)*e'_j. | c %---------------------------------------------% c - call zgemv ('N', n, j, -one, v, ldv, workd(irj), 1, + call zgemv ('N', n, j, -one, v, ldv, workd(irj), 1, & one, resid, 1) call zaxpy (j, one, workd(irj), 1, h(1,j), 1) -c +c orth2 = .true. call arscnd (t2) if (bmat .eq. 'G') then @@ -696,16 +696,16 @@ subroutine znaitr ipntr(1) = irj ipntr(2) = ipj ido = 2 -c +c c %-----------------------------------% c | Exit in order to compute B*r_{j}. | c | r_{j} is the corrected residual. | c %-----------------------------------% -c +c go to 9000 else if (bmat .eq. 'I') then call zcopy (n, resid, 1, workd(ipj), 1) - end if + end if 90 continue c c %---------------------------------------------------% @@ -715,21 +715,21 @@ subroutine znaitr if (bmat .eq. 'G') then call arscnd (t3) tmvbx = tmvbx + (t3 - t2) - end if + end if c c %-----------------------------------------------------% c | Compute the B-norm of the corrected residual r_{j}. | c %-----------------------------------------------------% -c - if (bmat .eq. 'G') then - cnorm = wzdotc (n, resid, 1, workd(ipj), 1) - rnorm1 = sqrt( dlapy2(dble(cnorm),dimag(cnorm)) ) +c + if (bmat .eq. 'G') then + cnorm = zzdotc (n, resid, 1, workd(ipj), 1) + rnorm1 = sqrt( dlapy2(dble(cnorm),aimag(cnorm)) ) else if (bmat .eq. 'I') then rnorm1 = dznrm2(n, resid, 1) end if -c +c if (msglvl .gt. 0 .and. iter .gt. 0 ) then - call ivout (logfil, 1, j, ndigit, + call ivout (logfil, 1, [j], ndigit, & '_naitr: Iterative refinement for Arnoldi residual') if (msglvl .gt. 2) then rtemp(1) = rnorm @@ -757,7 +757,7 @@ subroutine znaitr c %---------------------------------------% c rnorm = rnorm1 -c +c else c c %-------------------------------------------% @@ -776,24 +776,24 @@ subroutine znaitr c do 95 jj = 1, n resid(jj) = zero - 95 continue + 95 continue rnorm = rzero end if -c +c c %----------------------------------------------% c | Branch here directly if iterative refinement | c | wasn't necessary or after at most NITER_REF | c | steps of iterative refinement. | c %----------------------------------------------% -c +c 100 continue -c +c rstart = .false. orth2 = .false. -c +c call arscnd (t5) titref = titref + (t5 - t4) -c +c c %------------------------------------% c | STEP 6: Update j = j+1; Continue | c %------------------------------------% @@ -804,27 +804,27 @@ subroutine znaitr tcaitr = tcaitr + (t1 - t0) ido = 99 do 110 i = max(1,k), k+np-1 -c +c c %--------------------------------------------% c | Check for splitting and deflation. | c | Use a standard test as in the QR algorithm | c | REFERENCE: LAPACK subroutine zlahqr | c %--------------------------------------------% -c - tst1 = dlapy2(dble(h(i,i)),dimag(h(i,i))) - & + dlapy2(dble(h(i+1,i+1)), dimag(h(i+1,i+1))) +c + tst1 = dlapy2(dble(h(i,i)),aimag(h(i,i))) + & + dlapy2(dble(h(i+1,i+1)), aimag(h(i+1,i+1))) if( tst1.eq.dble(zero) ) & tst1 = zlanhs( '1', k+np, h, ldh, workd(n+1) ) - if( dlapy2(dble(h(i+1,i)),dimag(h(i+1,i))) .le. - & max( ulp*tst1, smlnum ) ) + if( dlapy2(dble(h(i+1,i)),aimag(h(i+1,i))) .le. + & max( ulp*tst1, smlnum ) ) & h(i+1,i) = zero 110 continue -c +c if (msglvl .gt. 2) then - call zmout (logfil, k+np, k+np, h, ldh, ndigit, + call zmout (logfil, k+np, k+np, h, ldh, ndigit, & '_naitr: Final upper Hessenberg matrix H of order K+NP') end if -c +c go to 9000 end if c @@ -833,7 +833,7 @@ subroutine znaitr c %--------------------------------------------------------% c go to 1000 -c +c c %---------------------------------------------------------------% c | | c | E N D O F M A I N I T E R A T I O N L O O P | diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znapps.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znapps.f index 30fc484b617c..792fe6168fd8 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znapps.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znapps.f @@ -19,7 +19,7 @@ c c\Usage: c call znapps -c ( N, KEV, NP, SHIFT, V, LDV, H, LDH, RESID, Q, LDQ, +c ( N, KEV, NP, SHIFT, V, LDV, H, LDH, RESID, Q, LDQ, c WORKL, WORKD ) c c\Arguments @@ -28,7 +28,7 @@ c c KEV Integer. (INPUT/OUTPUT) c KEV+NP is the size of the input matrix H. -c KEV is the size of the updated matrix HNEW. +c KEV is the size of the updated matrix HNEW. c c NP Integer. (INPUT) c Number of implicit shifts to be applied. @@ -46,7 +46,7 @@ c program. c c H Complex*16 (KEV+NP) by (KEV+NP) array. (INPUT/OUTPUT) -c On INPUT, H contains the current KEV+NP by KEV+NP upper +c On INPUT, H contains the current KEV+NP by KEV+NP upper c Hessenberg matrix of the Arnoldi factorization. c On OUTPUT, H contains the updated KEV by KEV upper Hessenberg c matrix in the KEV leading submatrix. @@ -57,7 +57,7 @@ c c RESID Complex*16 array of length N. (INPUT/OUTPUT) c On INPUT, RESID contains the the residual vector r_{k+p}. -c On OUTPUT, RESID is the update residual vector rnew_{k} +c On OUTPUT, RESID is the update residual vector rnew_{k} c in the first KEV locations. c c Q Complex*16 KEV+NP by KEV+NP work array. (WORKSPACE) @@ -112,9 +112,9 @@ c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c\SCCS Information: @(#) c FILE: napps.F SID: 2.3 DATE OF SID: 3/28/97 RELEASE: 2 @@ -132,7 +132,7 @@ c----------------------------------------------------------------------- c subroutine znapps - & ( n, kev, np, shift, v, ldv, h, ldh, resid, q, ldq, + & ( n, kev, np, shift, v, ldv, h, ldh, resid, q, ldq, & workl, workd ) c c %----------------------------------------------------% @@ -153,7 +153,7 @@ subroutine znapps c %-----------------% c Complex*16 - & h(ldh,kev+np), resid(n), shift(np), + & h(ldh,kev+np), resid(n), shift(np), & v(ldv,kev+np), q(ldq,kev+np), workd(2*n), workl(kev+np) c c %------------% @@ -175,22 +175,22 @@ subroutine znapps logical first Complex*16 & cdum, f, g, h11, h21, r, s, sigma, t - Double precision + Double precision & c, ovfl, smlnum, ulp, unfl, tst1 - save first, ovfl, smlnum, ulp, unfl + save first, ovfl, smlnum, ulp, unfl c c %----------------------% c | External Subroutines | c %----------------------% c - external zaxpy, zcopy, zgemv, zscal, zlacpy, zlartg, + external zaxpy, zcopy, zgemv, zscal, zlacpy, zlartg, & zvout, zlaset, dlabad, zmout, arscnd, ivout c c %--------------------% c | External Functions | c %--------------------% c - Double precision + Double precision & zlanhs, dlamch, dlapy2 external zlanhs, dlamch, dlapy2 c @@ -198,18 +198,18 @@ subroutine znapps c | Intrinsics Functions | c %----------------------% c - intrinsic abs, dimag, conjg, dcmplx, max, min, dble + intrinsic abs, aimag, conjg, cmplx, max, min, dble c c %---------------------% c | Statement Functions | c %---------------------% c - Double precision + Double precision & zabs1 - zabs1( cdum ) = abs( dble( cdum ) ) + abs( dimag( cdum ) ) + zabs1( cdum ) = abs( dble( cdum ) ) + abs( aimag( cdum ) ) c c %----------------% -c | Data statments | +c | Data statements | c %----------------% c data first / .true. / @@ -242,9 +242,9 @@ subroutine znapps c call arscnd (t0) msglvl = mcapps -c - kplusp = kev + np -c +c + kplusp = kev + np +c c %--------------------------------------------% c | Initialize Q to the identity to accumulate | c | the rotations and reflections | @@ -268,9 +268,9 @@ subroutine znapps sigma = shift(jj) c if (msglvl .gt. 2 ) then - call ivout (logfil, 1, jj, ndigit, + call ivout (logfil, 1, [jj], ndigit, & '_napps: shift number.') - call zvout (logfil, 1, sigma, ndigit, + call zvout (logfil, 1, [sigma], ndigit, & '_napps: Value of the shift ') end if c @@ -288,14 +288,14 @@ subroutine znapps tst1 = zabs1( h( i, i ) ) + zabs1( h( i+1, i+1 ) ) if( tst1.eq.rzero ) & tst1 = zlanhs( '1', kplusp-jj+1, h, ldh, workl ) - if ( abs(dble(h(i+1,i))) + if ( abs(dble(h(i+1,i))) & .le. max(ulp*tst1, smlnum) ) then if (msglvl .gt. 0) then - call ivout (logfil, 1, i, ndigit, + call ivout (logfil, 1, [i], ndigit, & '_napps: matrix splitting at row/column no.') - call ivout (logfil, 1, jj, ndigit, + call ivout (logfil, 1, [jj], ndigit, & '_napps: matrix splitting with shift number.') - call zvout (logfil, 1, h(i+1,i), ndigit, + call zvout (logfil, 1, h(i+1,i), ndigit, & '_napps: off diagonal element.') end if iend = i @@ -307,9 +307,9 @@ subroutine znapps 40 continue c if (msglvl .gt. 2) then - call ivout (logfil, 1, istart, ndigit, + call ivout (logfil, 1, [istart], ndigit, & '_napps: Start of current block ') - call ivout (logfil, 1, iend, ndigit, + call ivout (logfil, 1, [iend], ndigit, & '_napps: End of current block ') end if c @@ -325,7 +325,7 @@ subroutine znapps h21 = h(istart+1,istart) f = h11 - sigma g = h21 -c +c do 80 i = istart, iend-1 c c %------------------------------------------------------% @@ -345,7 +345,7 @@ subroutine znapps do 50 j = i, kplusp t = c*h(i,j) + s*h(i+1,j) h(i+1,j) = -conjg(s)*h(i,j) + c*h(i+1,j) - h(i,j) = t + h(i,j) = t 50 continue c c %---------------------------------------------% @@ -355,7 +355,7 @@ subroutine znapps do 60 j = 1, min(i+2,iend) t = c*h(j,i) + conjg(s)*h(j,i+1) h(j,i+1) = -s*h(j,i) + c*h(j,i+1) - h(j,i) = t + h(j,i) = t 60 continue c c %-----------------------------------------------------% @@ -365,7 +365,7 @@ subroutine znapps do 70 j = 1, min(i+jj, kplusp) t = c*q(j,i) + conjg(s)*q(j,i+1) q(j,i+1) = - s*q(j,i) + c*q(j,i+1) - q(j,i) = t + q(j,i) = t 70 continue c c %---------------------------% @@ -381,7 +381,7 @@ subroutine znapps c %-------------------------------% c | Finished applying the shift. | c %-------------------------------% -c +c 100 continue c c %---------------------------------------------------------% @@ -405,12 +405,12 @@ subroutine znapps c do 120 j=1,kev if ( dble( h(j+1,j) ) .lt. rzero .or. - & dimag( h(j+1,j) ) .ne. rzero ) then - t = h(j+1,j) / dlapy2(dble(h(j+1,j)),dimag(h(j+1,j))) + & aimag( h(j+1,j) ) .ne. rzero ) then + t = h(j+1,j) / dlapy2(dble(h(j+1,j)),aimag(h(j+1,j))) call zscal( kplusp-j+1, conjg(t), h(j+1,j), ldh ) call zscal( min(j+2, kplusp), t, h(1,j+1), 1 ) call zscal( min(j+np+1,kplusp), t, q(1,j+1), 1 ) - h(j+1,j) = dcmplx( dble( h(j+1,j) ), rzero ) + h(j+1,j) = cmplx( dble( h(j+1,j) ), rzero, Kind=Kind(0d0) ) end if 120 continue c @@ -428,7 +428,7 @@ subroutine znapps tst1 = zabs1( h( i, i ) ) + zabs1( h( i+1, i+1 ) ) if( tst1 .eq. rzero ) & tst1 = zlanhs( '1', kev, h, ldh, workl ) - if( dble( h( i+1,i ) ) .le. max( ulp*tst1, smlnum ) ) + if( dble( h( i+1,i ) ) .le. max( ulp*tst1, smlnum ) ) & h(i+1,i) = zero 130 continue c @@ -441,9 +441,9 @@ subroutine znapps c %-------------------------------------------------% c if ( dble( h(kev+1,kev) ) .gt. rzero ) - & call zgemv ('N', n, kplusp, one, v, ldv, q(1,kev+1), 1, zero, + & call zgemv ('N', n, kplusp, one, v, ldv, q(1,kev+1), 1, zero, & workd(n+1), 1) -c +c c %----------------------------------------------------------% c | Compute column 1 to kev of (V*Q) in backward order | c | taking advantage of the upper Hessenberg structure of Q. | @@ -460,14 +460,14 @@ subroutine znapps c %-------------------------------------------------% c call zlacpy ('A', n, kev, v(1,kplusp-kev+1), ldv, v, ldv) -c +c c %--------------------------------------------------------------% c | Copy the (kev+1)-st column of (V*Q) in the appropriate place | c %--------------------------------------------------------------% c if ( dble( h(kev+1,kev) ) .gt. rzero ) & call zcopy (n, workd(n+1), 1, v(1,kev+1), 1) -c +c c %-------------------------------------% c | Update the residual vector: | c | r <- sigmak*r + betak*v(:,kev+1) | @@ -485,7 +485,7 @@ subroutine znapps & '_napps: sigmak = (e_{kev+p}^T*Q)*e_{kev}') call zvout (logfil, 1, h(kev+1,kev), ndigit, & '_napps: betak = e_{kev+1}^T*H*e_{kev}') - call ivout (logfil, 1, kev, ndigit, + call ivout (logfil, 1, [kev], ndigit, & '_napps: Order of the final Hessenberg matrix ') if (msglvl .gt. 2) then call zmout (logfil, kev, kev, h, ldh, ndigit, @@ -497,7 +497,7 @@ subroutine znapps 9000 continue call arscnd (t1) tcapps = tcapps + (t1 - t0) -c +c return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaup2.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaup2.f index 680fde33901b..0ab01dd0eb25 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaup2.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaup2.f @@ -26,7 +26,7 @@ c The logic for adjusting is contained within the current c subroutine. c If ISHIFT=0, NP is the number of shifts the user needs -c to provide via reverse comunication. 0 < NP < NCV-NEV. +c to provide via reverse communication. 0 < NP < NCV-NEV. c NP may be less than NCV-NEV since a leading block of the current c upper Hessenberg matrix has split off and contains "unwanted" c Ritz values. @@ -142,7 +142,7 @@ c dlamch LAPACK routine that determines machine constants. c dlapy2 LAPACK routine to compute sqrt(x**2+y**2) carefully. c zcopy Level 1 BLAS that copies one vector to another . -c wzdotc Level 1 BLAS that computes the scalar product of two vectors. +c zdotc Level 1 BLAS that computes the scalar product of two vectors. c zswap Level 1 BLAS that swaps two vectors. c dznrm2 Level 1 BLAS that computes the norm of a vector. c @@ -247,16 +247,16 @@ subroutine znaup2 c %--------------------% c Complex*16 - & wzdotc + & zzdotc Double precision & dznrm2 , dlamch , dlapy2 - external wzdotc , dznrm2 , dlamch , dlapy2 + external zzdotc , dznrm2 , dlamch , dlapy2 c c %---------------------% c | Intrinsic Functions | c %---------------------% c - intrinsic dimag , dble , min, max + intrinsic aimag , dble , min, max c c %-----------------------% c | Executable Statements | @@ -389,7 +389,7 @@ subroutine znaup2 iter = iter + 1 c if (msglvl .gt. 0) then - call ivout (logfil, 1, iter, ndigit, + call ivout (logfil, 1, [iter], ndigit, & '_naup2: **** Start of major iteration number ****') end if c @@ -402,9 +402,9 @@ subroutine znaup2 np = kplusp - nev c if (msglvl .gt. 1) then - call ivout (logfil, 1, nev, ndigit, + call ivout (logfil, 1, [nev], ndigit, & '_naup2: The length of the current Arnoldi factorization') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_naup2: Extend the Arnoldi factorization by') end if c @@ -430,7 +430,7 @@ subroutine znaup2 update = .false. c if (msglvl .gt. 1) then - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_naup2: Corresponding B-norm of the residual') end if c @@ -489,8 +489,8 @@ subroutine znaup2 c do 25 i = 1, nev rtemp = max( eps23, dlapy2 ( dble (ritz(np+i)), - & dimag (ritz(np+i)) ) ) - if ( dlapy2 (dble (bounds(np+i)),dimag (bounds(np+i))) + & aimag (ritz(np+i)) ) ) + if ( dlapy2 (dble (bounds(np+i)),aimag (bounds(np+i))) & .le. tol*rtemp ) then nconv = nconv + 1 end if @@ -550,7 +550,7 @@ subroutine znaup2 c | rnorm to zneupd if needed | c %------------------------------------------% - h(3,1) = dcmplx (rnorm,rzero) + h(3,1) = cmplx (rnorm,rzero,Kind=Kind(0d0)) c c %----------------------------------------------% c | Sort Ritz values so that converged Ritz | @@ -575,7 +575,7 @@ subroutine znaup2 c do 35 j = 1, nev0 rtemp = max( eps23, dlapy2 ( dble (ritz(j)), - & dimag (ritz(j)) ) ) + & aimag (ritz(j)) ) ) bounds(j) = bounds(j)/rtemp 35 continue c @@ -596,7 +596,7 @@ subroutine znaup2 c do 40 j = 1, nev0 rtemp = max( eps23, dlapy2 ( dble (ritz(j)), - & dimag (ritz(j)) ) ) + & aimag (ritz(j)) ) ) bounds(j) = bounds(j)*rtemp 40 continue c @@ -658,7 +658,7 @@ subroutine znaup2 end if c if (msglvl .gt. 0) then - call ivout (logfil, 1, nconv, ndigit, + call ivout (logfil, 1, [nconv], ndigit, & '_naup2: no. of "converged" Ritz values at this iter.') if (msglvl .gt. 1) then kp(1) = nev @@ -698,7 +698,7 @@ subroutine znaup2 end if c if (msglvl .gt. 2) then - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_naup2: The number of shifts to apply ') call zvout (logfil, np, ritz, ndigit, & '_naup2: values of the shifts') @@ -754,15 +754,15 @@ subroutine znaup2 end if c if (bmat .eq. 'G') then - cmpnorm = wzdotc (n, resid, 1, workd, 1) - rnorm = sqrt(dlapy2 (dble (cmpnorm),dimag (cmpnorm))) + cmpnorm = zzdotc (n, resid, 1, workd, 1) + rnorm = sqrt(dlapy2 (dble (cmpnorm),aimag (cmpnorm))) else if (bmat .eq. 'I') then rnorm = dznrm2 (n, resid, 1) end if cnorm = .false. c if (msglvl .gt. 2) then - call dvout (logfil, 1, rnorm, ndigit, + call dvout (logfil, 1, [rnorm], ndigit, & '_naup2: B-norm of residual for compressed factorization') call zmout (logfil, nev, nev, h, ldh, ndigit, & '_naup2: Compressed upper Hessenberg matrix H') diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaupd.f index 779eb2bc4f97..c7d58aaab9ad 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/znaupd.f @@ -601,9 +601,9 @@ subroutine znaupd if (info .eq. 2) info = 3 c if (msglvl .gt. 0) then - call ivout (logfil, 1, mxiter, ndigit, + call ivout (logfil, 1, [mxiter], ndigit, & '_naupd: Number of update iterations taken') - call ivout (logfil, 1, np, ndigit, + call ivout (logfil, 1, [np], ndigit, & '_naupd: Number of wanted "converged" Ritz values') call zvout (logfil, np, workl(ritz), ndigit, & '_naupd: The final Ritz values') diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zneigh.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zneigh.f index df30acbb2b49..db1bc22985aa 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zneigh.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zneigh.f @@ -12,7 +12,7 @@ c c\Arguments c RNORM Double precision scalar. (INPUT) -c Residual norm corresponding to the current upper Hessenberg +c Residual norm corresponding to the current upper Hessenberg c matrix H. c c N Integer. (INPUT) @@ -30,8 +30,8 @@ c c BOUNDS Complex*16 array of length N. (OUTPUT) c On output, BOUNDS contains the Ritz estimates associated with -c the eigenvalues held in RITZ. This is equal to RNORM -c times the last components of the eigenvectors corresponding +c the eigenvalues held in RITZ. This is equal to RNORM +c times the last components of the eigenvectors corresponding c to the eigenvalues in RITZ. c c Q Complex*16 N by N array. (WORKSPACE) @@ -48,7 +48,7 @@ c c RWORK Double precision work array of length N (WORKSPACE) c Private (replicated) array on each PE or array allocated on -c the front end. +c the front end. c c IERR Integer. (OUTPUT) c Error exit flag from zlahqr or ztrevc. @@ -74,18 +74,18 @@ c zlaset LAPACK matrix initialization routine. c ztrevc LAPACK routine to compute the eigenvectors of a matrix c in upper triangular form -c zcopy Level 1 BLAS that copies one vector to another. +c zcopy Level 1 BLAS that copies one vector to another. c zdscal Level 1 BLAS that scales a complex vector by a real number. c dznrm2 Level 1 BLAS that computes the norm of a vector. -c +c c c\Author c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c\SCCS Information: @(#) c FILE: neigh.F SID: 2.2 DATE OF SID: 4/20/96 RELEASE: 2 @@ -97,7 +97,7 @@ c c----------------------------------------------------------------------- c - subroutine zneigh (rnorm, n, h, ldh, ritz, bounds, + subroutine zneigh (rnorm, n, h, ldh, ritz, bounds, & q, ldq, workl, rwork, ierr) c c %----------------------------------------------------% @@ -112,37 +112,37 @@ subroutine zneigh (rnorm, n, h, ldh, ritz, bounds, c %------------------% c integer ierr, n, ldh, ldq - Double precision + Double precision & rnorm c c %-----------------% c | Array Arguments | c %-----------------% c - Complex*16 + Complex*16 & bounds(n), h(ldh,n), q(ldq,n), ritz(n), - & workl(n*(n+3)) - Double precision + & workl(n*(n+3)) + Double precision & rwork(n) -c +c c %------------% c | Parameters | c %------------% c - Complex*16 + Complex*16 & one, zero Double precision & rone parameter (one = (1.0D+0, 0.0D+0), zero = (0.0D+0, 0.0D+0), & rone = 1.0D+0) -c +c c %------------------------% c | Local Scalars & Arrays | c %------------------------% c logical select(1) integer j, msglvl - Complex*16 + Complex*16 & vl(1) Double precision & temp @@ -151,14 +151,14 @@ subroutine zneigh (rnorm, n, h, ldh, ritz, bounds, c | External Subroutines | c %----------------------% c - external zlacpy, zlahqr, ztrevc, zcopy, + external zlacpy, zlahqr, ztrevc, zcopy, & zdscal, zmout, zvout, arscnd c c %--------------------% c | External Functions | c %--------------------% c - Double precision + Double precision & dznrm2 external dznrm2 c @@ -173,17 +173,17 @@ subroutine zneigh (rnorm, n, h, ldh, ritz, bounds, c call arscnd (t0) msglvl = mceigh -c +c if (msglvl .gt. 2) then - call zmout (logfil, n, n, h, ldh, ndigit, + call zmout (logfil, n, n, h, ldh, ndigit, & '_neigh: Entering upper Hessenberg matrix H ') end if -c +c c %----------------------------------------------------------% c | 1. Compute the eigenvalues, the last components of the | c | corresponding Schur vectors and the full Schur form T | c | of the current upper Hessenberg matrix H. | -c | zlahqr returns the full Schur form of H | +c | zlahqr returns the full Schur form of H | c | in WORKL(1:N**2), and the Schur vectors in q. | c %----------------------------------------------------------% c @@ -205,7 +205,7 @@ subroutine zneigh (rnorm, n, h, ldh, ritz, bounds, c | eigenvectors. | c %----------------------------------------------------------% c - call ztrevc ('Right', 'Back', select, n, workl, n, vl, n, q, + call ztrevc ('Right', 'Back', select, n, workl, n, vl, n, q, & ldq, n, n, workl(n*n+1), rwork, ierr) c if (ierr .ne. 0) go to 9000 diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zneupd.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zneupd.f index c6400f6b311a..92e7dc9980d8 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zneupd.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zneupd.f @@ -1,48 +1,48 @@ c\BeginDoc -c -c\Name: zneupd -c -c\Description: -c This subroutine returns the converged approximations to eigenvalues -c of A*z = lambda*B*z and (optionally): -c -c (1) The corresponding approximate eigenvectors; -c -c (2) An orthonormal basis for the associated approximate -c invariant subspace; -c -c (3) Both. -c -c There is negligible additional cost to obtain eigenvectors. An orthonormal +c +c\Name: zneupd +c +c\Description: +c This subroutine returns the converged approximations to eigenvalues +c of A*z = lambda*B*z and (optionally): +c +c (1) The corresponding approximate eigenvectors; +c +c (2) An orthonormal basis for the associated approximate +c invariant subspace; +c +c (3) Both. +c +c There is negligible additional cost to obtain eigenvectors. An orthonormal c basis is always computed. There is an additional storage cost of n*nev -c if both are requested (in this case a separate array Z must be supplied). +c if both are requested (in this case a separate array Z must be supplied). c c The approximate eigenvalues and eigenvectors of A*z = lambda*B*z c are derived from approximate eigenvalues and eigenvectors of c of the linear operator OP prescribed by the MODE selection in the c call to ZNAUPD. ZNAUPD must be called before this routine is called. c These approximate eigenvalues and vectors are commonly called Ritz -c values and Ritz vectors respectively. They are referred to as such -c in the comments that follow. The computed orthonormal basis for the -c invariant subspace corresponding to these Ritz values is referred to as a -c Schur basis. -c +c values and Ritz vectors respectively. They are referred to as such +c in the comments that follow. The computed orthonormal basis for the +c invariant subspace corresponding to these Ritz values is referred to as a +c Schur basis. +c c The definition of OP as well as other terms and the relation of computed c Ritz values and vectors of OP with respect to the given problem -c A*z = lambda*B*z may be found in the header of ZNAUPD. For a brief +c A*z = lambda*B*z may be found in the header of ZNAUPD. For a brief c description, see definitions of IPARAM(7), MODE and WHICH in the c documentation of ZNAUPD. c c\Usage: -c call zneupd -c ( RVEC, HOWMNY, SELECT, D, Z, LDZ, SIGMA, WORKEV, BMAT, -c N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, WORKD, +c call zneupd +c ( RVEC, HOWMNY, SELECT, D, Z, LDZ, SIGMA, WORKEV, BMAT, +c N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, WORKD, c WORKL, LWORKL, RWORK, INFO ) c c\Arguments: c RVEC LOGICAL (INPUT) c Specifies whether a basis for the invariant subspace corresponding -c to the converged Ritz value approximations for the eigenproblem +c to the converged Ritz value approximations for the eigenproblem c A*z = lambda*B*z is computed. c c RVEC = .FALSE. Compute Ritz values only. @@ -51,7 +51,7 @@ c See Remarks below. c c HOWMNY Character*1 (INPUT) -c Specifies the form of the basis for the invariant subspace +c Specifies the form of the basis for the invariant subspace c corresponding to the converged Ritz values that is to be computed. c c = 'A': Compute NEV Ritz vectors; @@ -62,34 +62,34 @@ c SELECT Logical array of dimension NCV. (INPUT) c If HOWMNY = 'S', SELECT specifies the Ritz vectors to be c computed. To select the Ritz vector corresponding to a -c Ritz value D(j), SELECT(j) must be set to .TRUE.. -c If HOWMNY = 'A' or 'P', SELECT need not be initialized +c Ritz value D(j), SELECT(j) must be set to .TRUE.. +c If HOWMNY = 'A' or 'P', SELECT need not be initialized c but it is used as internal workspace. c c D Complex*16 array of dimension NEV+1. (OUTPUT) -c On exit, D contains the Ritz approximations +c On exit, D contains the Ritz approximations c to the eigenvalues lambda for A*z = lambda*B*z. c c Z Complex*16 N by NEV array (OUTPUT) -c On exit, if RVEC = .TRUE. and HOWMNY = 'A', then the columns of -c Z represents approximate eigenvectors (Ritz vectors) corresponding +c On exit, if RVEC = .TRUE. and HOWMNY = 'A', then the columns of +c Z represents approximate eigenvectors (Ritz vectors) corresponding c to the NCONV=IPARAM(5) Ritz values for eigensystem c A*z = lambda*B*z. c c If RVEC = .FALSE. or HOWMNY = 'P', then Z is NOT REFERENCED. c -c NOTE: If if RVEC = .TRUE. and a Schur basis is not required, -c the array Z may be set equal to first NEV+1 columns of the Arnoldi -c basis array V computed by ZNAUPD. In this case the Arnoldi basis +c NOTE: If if RVEC = .TRUE. and a Schur basis is not required, +c the array Z may be set equal to first NEV+1 columns of the Arnoldi +c basis array V computed by ZNAUPD. In this case the Arnoldi basis c will be destroyed and overwritten with the eigenvector basis. c c LDZ Integer. (INPUT) c The leading dimension of the array Z. If Ritz vectors are -c desired, then LDZ .ge. max( 1, N ) is required. +c desired, then LDZ .ge. max( 1, N ) is required. c In any case, LDZ .ge. 1 is required. c c SIGMA Complex*16 (INPUT) -c If IPARAM(7) = 3 then SIGMA represents the shift. +c If IPARAM(7) = 3 then SIGMA represents the shift. c Not referenced if IPARAM(7) = 1 or 2. c c WORKEV Complex*16 work array of dimension 2*NCV. (WORKSPACE) @@ -97,12 +97,12 @@ c **** The remaining arguments MUST be the same as for the **** c **** call to ZNAUPD that was just completed. **** c -c NOTE: The remaining arguments +c NOTE: The remaining arguments c -c BMAT, N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, -c WORKD, WORKL, LWORKL, RWORK, INFO +c BMAT, N, WHICH, NEV, TOL, RESID, NCV, V, LDV, IPARAM, IPNTR, +c WORKD, WORKL, LWORKL, RWORK, INFO c -c must be passed directly to ZNEUPD following the last call +c must be passed directly to ZNEUPD following the last call c to ZNAUPD. These arguments MUST NOT BE MODIFIED between c the the last call to ZNAUPD and the call to ZNEUPD. c @@ -128,7 +128,7 @@ c WORKL(1:ncv*ncv+2*ncv) contains information obtained in c znaupd. They are not changed by zneupd. c WORKL(ncv*ncv+2*ncv+1:3*ncv*ncv+4*ncv) holds the -c untransformed Ritz values, the untransformed error estimates of +c untransformed Ritz values, the untransformed error estimates of c the Ritz values, the upper triangular matrix for H, and the c associated matrix representation of the invariant subspace for H. c @@ -187,18 +187,18 @@ c 1. D.C. Sorensen, "Implicit Application of Polynomial Filters in c a k-Step Arnoldi Method", SIAM J. Matr. Anal. Apps., 13 (1992), c pp 357-385. -c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly +c 2. R.B. Lehoucq, "Analysis and Implementation of an Implicitly c Restarted Arnoldi Iteration", Rice University Technical Report c TR95-13, Department of Computational and Applied Mathematics. c 3. B. Nour-Omid, B. N. Parlett, T. Ericsson and P. S. Jensen, c "How to Implement the Spectral Transformation", Math Comp., -c Vol. 48, No. 178, April, 1987 pp. 664-673. +c Vol. 48, No. 178, April, 1987 pp. 664-673. c c\Routines called: c ivout ARPACK utility routine that prints integers. c zmout ARPACK utility routine that prints matrices c zvout ARPACK utility routine that prints vectors. -c zgeqr2 LAPACK routine that computes the QR factorization of +c zgeqr2 LAPACK routine that computes the QR factorization of c a matrix. c zlacpy LAPACK matrix copy routine. c zlahqr LAPACK routine that computes the Schur form of a @@ -207,7 +207,7 @@ c ztrevc LAPACK routine to compute the eigenvectors of a matrix c in upper triangular form. c ztrsen LAPACK routine that re-orders the Schur form. -c zunm2r LAPACK routine that applies an orthogonal matrix in +c zunm2r LAPACK routine that applies an orthogonal matrix in c factored form. c dlamch LAPACK routine that determines machine constants. c ztrmm Level 3 BLAS matrix times an upper triangular matrix. @@ -219,7 +219,7 @@ c c\Remarks c -c 1. Currently only HOWMNY = 'A' and 'P' are implemented. +c 1. Currently only HOWMNY = 'A' and 'P' are implemented. c c 2. Schur vectors are an orthogonal representation for the basis of c Ritz vectors. Thus, their numerical properties are often superior. @@ -227,16 +227,16 @@ c A * V(:,1:IPARAM(5)) = V(:,1:IPARAM(5)) * T, and c transpose( V(:,1:IPARAM(5)) ) * V(:,1:IPARAM(5)) = I c are approximately satisfied. -c Here T is the leading submatrix of order IPARAM(5) of the -c upper triangular matrix stored workl(ipntr(12)). +c Here T is the leading submatrix of order IPARAM(5) of the +c upper triangular matrix stored workl(ipntr(12)). c c\Authors c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University -c Chao Yang Houston, Texas -c Dept. of Computational & -c Applied Mathematics -c Rice University +c Chao Yang Houston, Texas +c Dept. of Computational & +c Applied Mathematics +c Rice University c Houston, Texas c c\SCCS Information: @(#) @@ -266,9 +266,9 @@ subroutine zneupd(rvec , howmny, select, d , character bmat, howmny, which*2 logical rvec integer info, ldz, ldv, lworkl, n, ncv, nev - Complex*16 + Complex*16 & sigma - Double precision + Double precision & tol c c %-----------------% @@ -281,7 +281,7 @@ subroutine zneupd(rvec , howmny, select, d , & rwork(ncv) Complex*16 & d(nev) , resid(n) , v(ldv,ncv), - & z(ldz, nev), + & z(ldz, nev), & workd(3*n) , workl(lworkl), workev(2*ncv) c c %------------% @@ -302,7 +302,7 @@ subroutine zneupd(rvec , howmny, select, d , & mode , msglvl, ritz , wr , k , irz , & ibd , outncv, iq , np , numcnv, jj , & ishift, nconv2 - Complex*16 + Complex*16 & rnorm, temp, vl(1) Double precision & conds, sep, rtemp, eps23 @@ -315,7 +315,7 @@ subroutine zneupd(rvec , howmny, select, d , external zcopy , zgeru, zgeqr2, zlacpy, zmout, & zunm2r, ztrmm, zvout, ivout, & zlahqr -c +c c %--------------------% c | External Functions | c %--------------------% @@ -325,13 +325,13 @@ subroutine zneupd(rvec , howmny, select, d , external dznrm2, dlamch, dlapy2 c Complex*16 - & wzdotc - external wzdotc + & zzdotc + external zzdotc c c %-----------------------% c | Executable Statements | c %-----------------------% -c +c c %------------------------% c | Set default parameters | c %------------------------% @@ -382,12 +382,12 @@ subroutine zneupd(rvec , howmny, select, d , else if (howmny .eq. 'S' ) then ierr = -12 end if -c +c if (mode .eq. 1 .or. mode .eq. 2) then type = 'REGULR' else if (mode .eq. 3 ) then type = 'SHIFTI' - else + else ierr = -10 end if if (mode .eq. 1 .and. bmat .eq. 'G') ierr = -11 @@ -400,7 +400,7 @@ subroutine zneupd(rvec , howmny, select, d , info = ierr go to 9000 end if -c +c c %--------------------------------------------------------% c | Pointer into WORKL for address of H, RITZ, WORKEV, Q | c | etc... and the remaining workspace. | @@ -428,7 +428,7 @@ subroutine zneupd(rvec , howmny, select, d , c | subspace for H. | c | GRAND total of NCV * ( 3 * NCV + 4 ) locations. | c %-----------------------------------------------------------% -c +c ih = ipntr(5) ritz = ipntr(6) iq = ipntr(7) @@ -516,11 +516,11 @@ subroutine zneupd(rvec , howmny, select, d , do 11 j = 1,ncv rtemp = max(eps23, & dlapy2 ( dble(workl(irz+ncv-j)), - & dimag(workl(irz+ncv-j)) )) + & aimag(workl(irz+ncv-j)) )) jj = workl(bounds + ncv - j) if (numcnv .lt. nconv .and. & dlapy2( dble(workl(ibd+jj-1)), - & dimag(workl(ibd+jj-1)) ) + & aimag(workl(ibd+jj-1)) ) & .le. tol*rtemp) then select(jj) = .true. numcnv = numcnv + 1 @@ -536,9 +536,9 @@ subroutine zneupd(rvec , howmny, select, d , c %-----------------------------------------------------------% c if (msglvl .gt. 2) then - call ivout(logfil, 1, numcnv, ndigit, + call ivout(logfil, 1, [numcnv], ndigit, & '_neupd: Number of specified eigenvalues') - call ivout(logfil, 1, nconv, ndigit, + call ivout(logfil, 1, [nconv], ndigit, & '_neupd: Number of "converged" eigenvalues') end if c @@ -555,10 +555,10 @@ subroutine zneupd(rvec , howmny, select, d , c %-------------------------------------------------------% c call zcopy(ldh*ncv, workl(ih), 1, workl(iuptri), 1) - call zlaset('All', ncv, ncv , + call zlaset('All', ncv, ncv , & zero , one, workl(invsub), & ldq) - call zlahqr(.true., .true. , ncv , + call zlahqr(.true., .true. , ncv , & 1 , ncv , workl(iuptri), & ldh , workl(iheig) , 1 , & ncv , workl(invsub), ldq , @@ -577,7 +577,7 @@ subroutine zneupd(rvec , howmny, select, d , call zvout (logfil, ncv, workl(ihbds), ndigit, & '_neupd: Last row of the Schur vector matrix') if (msglvl .gt. 3) then - call zmout (logfil , ncv, ncv , + call zmout (logfil , ncv, ncv , & workl(iuptri), ldh, ndigit, & '_neupd: The upper triangular matrix ') end if @@ -592,7 +592,7 @@ subroutine zneupd(rvec , howmny, select, d , call ztrsen('None' , 'V' , select , & ncv , workl(iuptri), ldh , & workl(invsub), ldq , workl(iheig), - & nconv2 , conds , sep , + & nconv2 , conds , sep , & workev , ncv , ierr) c if (nconv2 .lt. nconv) then @@ -625,7 +625,7 @@ subroutine zneupd(rvec , howmny, select, d , c call zcopy(ncv , workl(invsub+ncv-1), ldq, & workl(ihbds), 1) -c +c c %--------------------------------------------% c | Place the computed eigenvalues of H into D | c | if a spectral transformation was not used. | @@ -651,7 +651,7 @@ subroutine zneupd(rvec , howmny, select, d , c | * Postmultiply Z by R. | c | The N by NCONV matrix Z is now a matrix representation | c | of the approximate invariant subspace associated with | -c | the Ritz values in workl(iheig). The first NCONV | +c | the Ritz values in workl(iheig). The first NCONV | c | columns of V are now approximate Schur vectors | c | associated with the upper triangular matrix of order | c | NCONV in workl(iuptri). | @@ -674,7 +674,7 @@ subroutine zneupd(rvec , howmny, select, d , c | matrix consisting of plus or minus ones. | c %---------------------------------------------------% c - if ( dble( workl(invsub+(j-1)*ldq+j-1) ) .lt. + if ( dble( workl(invsub+(j-1)*ldq+j-1) ) .lt. & dble(zero) ) then call zscal(nconv, -one, workl(iuptri+j-1), ldq) call zscal(nconv, -one, workl(iuptri+(j-1)*ldq), 1) @@ -730,8 +730,8 @@ subroutine zneupd(rvec , howmny, select, d , c | upper triangular, thus the length of the | c | inner product can be set to j. | c %------------------------------------------% -c - workev(j) = wzdotc(j, workl(ihbds), 1, +c + workev(j) = zzdotc(j, workl(ihbds), 1, & workl(invsub+(j-1)*ldq), 1) 40 continue c @@ -750,7 +750,7 @@ subroutine zneupd(rvec , howmny, select, d , c %---------------------------------------% c | Copy Ritz estimates into workl(ihbds) | c %---------------------------------------% -c +c call zcopy(nconv, workev, 1, workl(ihbds), 1) c c %----------------------------------------------% @@ -762,7 +762,7 @@ subroutine zneupd(rvec , howmny, select, d , & 'Non-unit', n , nconv , & one , workl(invsub), ldq , & z , ldz) - end if + end if c else c @@ -785,25 +785,25 @@ subroutine zneupd(rvec , howmny, select, d , c if (type .eq. 'REGULR') then c - if (rvec) + if (rvec) & call zscal(ncv, rnorm, workl(ihbds), 1) -c +c else -c +c c %---------------------------------------% c | A spectral transformation was used. | c | * Determine the Ritz estimates of the | c | Ritz values in the original system. | c %---------------------------------------% c - if (rvec) + if (rvec) & call zscal(ncv, rnorm, workl(ihbds), 1) -c +c do 50 k=1, ncv temp = workl(iheig+k-1) workl(ihbds+k-1) = workl(ihbds+k-1) / temp / temp 50 continue -c +c end if c c %-----------------------------------------------------------% @@ -813,7 +813,7 @@ subroutine zneupd(rvec , howmny, select, d , c | NOTES: | c | *The Ritz vectors are not affected by the transformation. | c %-----------------------------------------------------------% -c +c if (type .eq. 'SHIFTI') then do 60 k=1, nconv d(k) = one / workl(iheig+k-1) + sigma @@ -868,7 +868,7 @@ subroutine zneupd(rvec , howmny, select, d , 9000 continue c return -c +c c %---------------% c | End of zneupd| c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zngets.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zngets.f index 498848abb560..e7d2433492ad 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zngets.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zngets.f @@ -2,9 +2,9 @@ c c\Name: zngets c -c\Description: +c\Description: c Given the eigenvalues of the upper Hessenberg matrix H, -c computes the NP shifts AMU that are zeros of the polynomial of +c computes the NP shifts AMU that are zeros of the polynomial of c degree NP which filters out components of the unwanted eigenvectors c corresponding to the AMU's based on some given criteria. c @@ -40,8 +40,8 @@ c On INPUT, RITZ contains the the eigenvalues of H. c On OUTPUT, RITZ are sorted so that the unwanted c eigenvalues are in the first NP locations and the wanted -c portion is in the last KEV locations. When exact shifts are -c selected, the unwanted part corresponds to the shifts to +c portion is in the last KEV locations. When exact shifts are +c selected, the unwanted part corresponds to the shifts to c be applied. Also, if ISHIFT .eq. 1, the unwanted eigenvalues c are further sorted so that the ones with largest Ritz values c are first. @@ -49,7 +49,7 @@ c BOUNDS Complex*16 array of length KEV+NP. (INPUT/OUTPUT) c Error bounds corresponding to the ordering in RITZ. c -c +c c c\EndDoc c @@ -70,9 +70,9 @@ c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c\SCCS Information: @(#) c FILE: ngets.F SID: 2.2 DATE OF SID: 4/20/96 RELEASE: 2 @@ -136,14 +136,14 @@ subroutine zngets ( ishift, which, kev, np, ritz, bounds) c | Initialize timing statistics | c | & message level for debugging | c %-------------------------------% -c +c call arscnd (t0) msglvl = mcgets -c +c call zsortc (which, .true., kev+np, ritz, bounds) -c +c if ( ishift .eq. 1 ) then -c +c c %-------------------------------------------------------% c | Sort the unwanted Ritz values used as shifts so that | c | the ones with largest Ritz estimates are first | @@ -152,27 +152,27 @@ subroutine zngets ( ishift, which, kev, np, ritz, bounds) c | are applied in subroutine znapps. | c | Be careful and use 'SM' since we want to sort BOUNDS! | c %-------------------------------------------------------% -c +c call zsortc ( 'SM', .true., np, bounds, ritz ) c end if -c +c call arscnd (t1) tcgets = tcgets + (t1 - t0) c if (msglvl .gt. 0) then - call ivout (logfil, 1, kev, ndigit, '_ngets: KEV is') - call ivout (logfil, 1, np, ndigit, '_ngets: NP is') + call ivout (logfil, 1, [kev], ndigit, '_ngets: KEV is') + call ivout (logfil, 1, [np], ndigit, '_ngets: NP is') call zvout (logfil, kev+np, ritz, ndigit, & '_ngets: Eigenvalues of current H matrix ') - call zvout (logfil, kev+np, bounds, ndigit, + call zvout (logfil, kev+np, bounds, ndigit, & '_ngets: Ritz estimates of the current KEV+NP Ritz values') end if -c +c return -c +c c %---------------% c | End of zngets | c %---------------% -c +c end diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zsortc.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zsortc.f index 7dc688a06fc4..6ea37a42f768 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zsortc.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zsortc.f @@ -3,9 +3,9 @@ c\Name: zsortc c c\Description: -c Sorts the Complex*16 array in X into the order +c Sorts the Complex*16 array in X into the order c specified by WHICH and optionally applies the permutation to the -c Double precision array Y. +c Double precision array Y. c c\Usage: c call zsortc @@ -15,7 +15,7 @@ c WHICH Character*2. (Input) c 'LM' -> sort X into increasing order of magnitude. c 'SM' -> sort X into decreasing order of magnitude. -c 'LR' -> sort X with real(X) in increasing algebraic order +c 'LR' -> sort X with real(X) in increasing algebraic order c 'SR' -> sort X with real(X) in decreasing algebraic order c 'LI' -> sort X with imag(X) in increasing algebraic order c 'SI' -> sort X with imag(X) in decreasing algebraic order @@ -45,9 +45,9 @@ c Danny Sorensen Phuong Vu c Richard Lehoucq CRPC / Rice University c Dept. of Computational & Houston, Texas -c Applied Mathematics -c Rice University -c Houston, Texas +c Applied Mathematics +c Rice University +c Houston, Texas c c Adapted from the sort routine in LANSO. c @@ -72,7 +72,7 @@ subroutine zsortc (which, apply, n, x, y) c | Array Arguments | c %-----------------% c - Complex*16 + Complex*16 & x(0:n-1), y(0:n-1) c c %---------------% @@ -80,9 +80,9 @@ subroutine zsortc (which, apply, n, x, y) c %---------------% c integer i, igap, j - Complex*16 + Complex*16 & temp - Double precision + Double precision & temp1, temp2 c c %--------------------% @@ -96,14 +96,14 @@ subroutine zsortc (which, apply, n, x, y) c | Intrinsic Functions | c %--------------------% Intrinsic - & dble, dimag + & dble, aimag c c %-----------------------% c | Executable Statements | c %-----------------------% c igap = n / 2 -c +c if (which .eq. 'LM') then c c %--------------------------------------------% @@ -119,8 +119,8 @@ subroutine zsortc (which, apply, n, x, y) c if (j.lt.0) go to 30 c - temp1 = dlapy2(dble(x(j)),dimag(x(j))) - temp2 = dlapy2(dble(x(j+igap)),dimag(x(j+igap))) + temp1 = dlapy2(dble(x(j)),aimag(x(j))) + temp2 = dlapy2(dble(x(j+igap)),aimag(x(j+igap))) c if (temp1.gt.temp2) then temp = x(j) @@ -156,14 +156,14 @@ subroutine zsortc (which, apply, n, x, y) c if (j .lt. 0) go to 60 c - temp1 = dlapy2(dble(x(j)),dimag(x(j))) - temp2 = dlapy2(dble(x(j+igap)),dimag(x(j+igap))) + temp1 = dlapy2(dble(x(j)),aimag(x(j))) + temp2 = dlapy2(dble(x(j+igap)),aimag(x(j+igap))) c if (temp1.lt.temp2) then temp = x(j) x(j) = x(j+igap) x(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -177,7 +177,7 @@ subroutine zsortc (which, apply, n, x, y) 60 continue igap = igap / 2 go to 40 -c +c else if (which .eq. 'LR') then c c %------------------------------------------------% @@ -197,7 +197,7 @@ subroutine zsortc (which, apply, n, x, y) temp = x(j) x(j) = x(j+igap) x(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -211,7 +211,7 @@ subroutine zsortc (which, apply, n, x, y) 90 continue igap = igap / 2 go to 70 -c +c else if (which .eq. 'SR') then c c %------------------------------------------------% @@ -230,7 +230,7 @@ subroutine zsortc (which, apply, n, x, y) temp = x(j) x(j) = x(j+igap) x(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -244,7 +244,7 @@ subroutine zsortc (which, apply, n, x, y) 120 continue igap = igap / 2 go to 100 -c +c else if (which .eq. 'LI') then c c %--------------------------------------------% @@ -259,7 +259,7 @@ subroutine zsortc (which, apply, n, x, y) c if (j.lt.0) go to 150 c - if (dimag(x(j)).gt.dimag(x(j+igap))) then + if (aimag(x(j)).gt.aimag(x(j+igap))) then temp = x(j) x(j) = x(j+igap) x(j+igap) = temp @@ -277,7 +277,7 @@ subroutine zsortc (which, apply, n, x, y) 150 continue igap = igap / 2 go to 130 -c +c else if (which .eq. 'SI') then c c %---------------------------------------------% @@ -292,11 +292,11 @@ subroutine zsortc (which, apply, n, x, y) c if (j.lt.0) go to 180 c - if (dimag(x(j)).lt.dimag(x(j+igap))) then + if (aimag(x(j)).lt.aimag(x(j+igap))) then temp = x(j) x(j) = x(j+igap) x(j+igap) = temp -c +c if (apply) then temp = y(j) y(j) = y(j+igap) @@ -311,7 +311,7 @@ subroutine zsortc (which, apply, n, x, y) igap = igap / 2 go to 160 end if -c +c 9000 continue return c diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zstatn.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zstatn.f index 1cdf5b3dfa0a..ddc5240f3f5c 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zstatn.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zstatn.f @@ -14,7 +14,7 @@ subroutine zstatn c %--------------------------------% c include 'stat.h' - + c %-----------------------% c | Executable Statements | c %-----------------------% @@ -24,7 +24,7 @@ subroutine zstatn nrorth = 0 nitref = 0 nrstrt = 0 - + tcaupd = 0.0D+0 tcaup2 = 0.0D+0 tcaitr = 0.0D+0 @@ -35,13 +35,13 @@ subroutine zstatn titref = 0.0D+0 tgetv0 = 0.0D+0 trvec = 0.0D+0 - + c %----------------------------------------------------% c | User time including reverse communication overhead | c %----------------------------------------------------% tmvopx = 0.0D+0 tmvbx = 0.0D+0 - + return c c %---------------% diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zzdotc.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zzdotc.f new file mode 100644 index 000000000000..a98c34230c3a --- /dev/null +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/zzdotc.f @@ -0,0 +1,36 @@ + double complex function zzdotc(n,zx,incx,zy,incy) +c +c forms the dot product of a vector. +c jack dongarra, 3/11/78. +c modified 12/3/93, array(1) declarations changed to array(*) +c + double complex zx(*),zy(*),ztemp + integer i,incx,incy,ix,iy,n + ztemp = (0.0d0,0.0d0) + zzdotc = (0.0d0,0.0d0) + if(n.le.0)return + if(incx.eq.1.and.incy.eq.1)go to 20 +c +c code for unequal increments or equal increments +c not equal to 1 +c + ix = 1 + iy = 1 + if(incx.lt.0)ix = (-n+1)*incx + 1 + if(incy.lt.0)iy = (-n+1)*incy + 1 + do 10 i = 1,n + ztemp = ztemp + conjg(zx(ix))*zy(iy) + ix = ix + incx + iy = iy + incy + 10 continue + zzdotc = ztemp + return +c +c code for both increments equal to 1 +c + 20 do 30 i = 1,n + ztemp = ztemp + conjg(zx(i))*zy(i) + 30 continue + zzdotc = ztemp + return + end diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/cmout.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/cmout.f index 1cdaf33e9021..ff04783099c0 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/cmout.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/cmout.f @@ -74,34 +74,34 @@ SUBROUTINE CMOUT( LOUT, M, N, A, LDA, IDIGIT, IFMT ) IF (K1.NE.N) THEN WRITE( LOUT, 9994 )I, ( A( I, J ), J = K1, K2 ) ELSE - WRITE( LOUT, 9984 )I, ( A( I, J ), J = K1, K2 ) + WRITE( LOUT, 9984 )I, ( A( I, J ), J = K1, K2 ) END IF 30 CONTINUE 40 CONTINUE * ELSE IF( NDIGIT.LE.6 ) THEN - DO 60 K1 = 1, N, 2 + DO 60 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) WRITE( LOUT, 9997 )( ICOL, I, I = K1, K2 ) DO 50 I = 1, M IF (K1.NE.N) THEN WRITE( LOUT, 9993 )I, ( A( I, J ), J = K1, K2 ) - ELSE - WRITE( LOUT, 9983 )I, ( A( I, J ), J = K1, K2 ) + ELSE + WRITE( LOUT, 9983 )I, ( A( I, J ), J = K1, K2 ) END IF 50 CONTINUE 60 CONTINUE * ELSE IF( NDIGIT.LE.8 ) THEN - DO 80 K1 = 1, N, 2 + DO 80 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) WRITE( LOUT, 9996 )( ICOL, I, I = K1, K2 ) DO 70 I = 1, M IF (K1.NE.N) THEN WRITE( LOUT, 9992 )I, ( A( I, J ), J = K1, K2 ) ELSE - WRITE( LOUT, 9982 )I, ( A( I, J ), J = K1, K2 ) - END IF + WRITE( LOUT, 9982 )I, ( A( I, J ), J = K1, K2 ) + END IF 70 CONTINUE 80 CONTINUE * @@ -124,20 +124,20 @@ SUBROUTINE CMOUT( LOUT, M, N, A, LDA, IDIGIT, IFMT ) K2 = MIN0( N, K1+3 ) WRITE( LOUT, 9998 )( ICOL, I, I = K1, K2 ) DO 110 I = 1, M - IF ((K1+3).LE.N) THEN + IF ((K1+3).LE.N) THEN WRITE( LOUT, 9974 )I, ( A( I, J ), J = K1, K2 ) ELSE IF ((K1+3-N).EQ.1) THEN WRITE( LOUT, 9964 )I, ( A( I, J ), J = k1, K2 ) ELSE IF ((K1+3-N).EQ.2) THEN WRITE( LOUT, 9954 )I, ( A( I, J ), J = K1, K2 ) ELSE IF ((K1+3-N).EQ.3) THEN - WRITE( LOUT, 9944 )I, ( A( I, J ), J = K1, K2 ) + WRITE( LOUT, 9944 )I, ( A( I, J ), J = K1, K2 ) END IF 110 CONTINUE 120 CONTINUE * ELSE IF( NDIGIT.LE.6 ) THEN - DO 140 K1 = 1, N, 3 + DO 140 K1 = 1, N, 3 K2 = MIN0( N, K1+ 2) WRITE( LOUT, 9997 )( ICOL, I, I = K1, K2 ) DO 130 I = 1, M @@ -185,14 +185,14 @@ SUBROUTINE CMOUT( LOUT, M, N, A, LDA, IDIGIT, IFMT ) 9998 FORMAT( 11X, 4( 9X, 3A1, I4, 9X ) ) 9997 FORMAT( 10X, 4( 11X, 3A1, I4, 11X ) ) 9996 FORMAT( 10X, 3( 13X, 3A1, I4, 13X ) ) - 9995 FORMAT( 12X, 2( 18x, 3A1, I4, 18X ) ) + 9995 FORMAT( 12X, 2( 18x, 3A1, I4, 18X ) ) * *======================================================== * FORMAT FOR 72 COLUMN *======================================================== * * DISPLAY 4 SIGNIFICANT DIGITS -* +* 9994 FORMAT( 1X, ' Row', I4, ':', 1X, 1P,2('(',E10.3,',',E10.3,') ') ) 9984 FORMAT( 1X, ' Row', I4, ':', 1X, 1P,1('(',E10.3,',',E10.3,') ') ) * diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/cvout.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/cvout.f index 31c22fe0294b..1ee9afabf7ab 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/cvout.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/cvout.f @@ -63,21 +63,21 @@ SUBROUTINE CVOUT( LOUT, N, CX, IDIGIT, IFMT ) DO 30 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) IF (K1.NE.N) THEN - WRITE( LOUT, 9998 )K1, K2, ( CX( I ), + WRITE( LOUT, 9998 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE - WRITE( LOUT, 9997 )K1, K2, ( CX( I ), - $ I = K1, K2 ) + WRITE( LOUT, 9997 )K1, K2, ( CX( I ), + $ I = K1, K2 ) END IF 30 CONTINUE ELSE IF( NDIGIT.LE.6 ) THEN DO 40 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) IF (K1.NE.N) THEN - WRITE( LOUT, 9988 )K1, K2, ( CX( I ), + WRITE( LOUT, 9988 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE - WRITE( LOUT, 9987 )K1, K2, ( CX( I ), + WRITE( LOUT, 9987 )K1, K2, ( CX( I ), $ I = K1, K2 ) END IF 40 CONTINUE @@ -85,11 +85,11 @@ SUBROUTINE CVOUT( LOUT, N, CX, IDIGIT, IFMT ) DO 50 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) IF (K1.NE.N) THEN - WRITE( LOUT, 9978 )K1, K2, ( CX( I ), + WRITE( LOUT, 9978 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE - WRITE( LOUT, 9977 )K1, K2, ( CX( I ), - $ I = K1, K2 ) + WRITE( LOUT, 9977 )K1, K2, ( CX( I ), + $ I = K1, K2 ) END IF 50 CONTINUE ELSE @@ -104,47 +104,47 @@ SUBROUTINE CVOUT( LOUT, N, CX, IDIGIT, IFMT ) * ELSE IF( NDIGIT.LE.4 ) THEN - DO 70 K1 = 1, N, 4 + DO 70 K1 = 1, N, 4 K2 = MIN0( N, K1+3 ) IF ((K1+3).LE.N) THEN - WRITE( LOUT, 9958 )K1, K2, ( CX( I ), + WRITE( LOUT, 9958 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+3-N) .EQ. 1) THEN - WRITE( LOUT, 9957 )K1, K2, ( CX( I ), + WRITE( LOUT, 9957 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+3-N) .EQ. 2) THEN - WRITE( LOUT, 9956 )K1, K2, ( CX( I ), + WRITE( LOUT, 9956 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+3-N) .EQ. 1) THEN - WRITE( LOUT, 9955 )K1, K2, ( CX( I ), + WRITE( LOUT, 9955 )K1, K2, ( CX( I ), $ I = K1, K2 ) END IF 70 CONTINUE ELSE IF( NDIGIT.LE.6 ) THEN - DO 80 K1 = 1, N, 3 + DO 80 K1 = 1, N, 3 K2 = MIN0( N, K1+2 ) IF ((K1+2).LE.N) THEN - WRITE( LOUT, 9948 )K1, K2, ( CX( I ), + WRITE( LOUT, 9948 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+2-N) .EQ. 1) THEN - WRITE( LOUT, 9947 )K1, K2, ( CX( I ), + WRITE( LOUT, 9947 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+2-N) .EQ. 2) THEN - WRITE( LOUT, 9946 )K1, K2, ( CX( I ), + WRITE( LOUT, 9946 )K1, K2, ( CX( I ), $ I = K1, K2 ) END IF 80 CONTINUE ELSE IF( NDIGIT.LE.8 ) THEN - DO 90 K1 = 1, N, 3 + DO 90 K1 = 1, N, 3 K2 = MIN0( N, K1+2 ) IF ((K1+2).LE.N) THEN - WRITE( LOUT, 9938 )K1, K2, ( CX( I ), + WRITE( LOUT, 9938 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+2-N) .EQ. 1) THEN - WRITE( LOUT, 9937 )K1, K2, ( CX( I ), + WRITE( LOUT, 9937 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+2-N) .EQ. 2) THEN - WRITE( LOUT, 9936 )K1, K2, ( CX( I ), + WRITE( LOUT, 9936 )K1, K2, ( CX( I ), $ I = K1, K2 ) END IF 90 CONTINUE @@ -152,10 +152,10 @@ SUBROUTINE CVOUT( LOUT, N, CX, IDIGIT, IFMT ) DO 100 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) IF ((K1+2).LE.N) THEN - WRITE( LOUT, 9928 )K1, K2, ( CX( I ), + WRITE( LOUT, 9928 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+2-N) .EQ. 1) THEN - WRITE( LOUT, 9927 )K1, K2, ( CX( I ), + WRITE( LOUT, 9927 )K1, K2, ( CX( I ), $ I = K1, K2 ) END IF 100 CONTINUE @@ -171,12 +171,12 @@ SUBROUTINE CVOUT( LOUT, N, CX, IDIGIT, IFMT ) * DISPLAY 4 SIGNIFICANT DIGITS * 9998 FORMAT( 1X, I4, ' - ', I4, ':', 1X, - $ 1P,2('(',E10.3,',',E10.3,') ') ) + $ 1P,2('(',E10.3,',',E10.3,') ') ) 9997 FORMAT( 1X, I4, ' - ', I4, ':', 1X, $ 1P,1('(',E10.3,',',E10.3,') ') ) * * DISPLAY 6 SIGNIFICANT DIGITS -* +* 9988 FORMAT( 1X, I4, ' - ', I4, ':', 1X, $ 1P,2('(',E12.5,',',E12.5,') ') ) 9987 FORMAT( 1X, I4, ' - ', I4, ':', 1X, @@ -192,7 +192,7 @@ SUBROUTINE CVOUT( LOUT, N, CX, IDIGIT, IFMT ) * DISPLAY 13 SIGNIFICANT DIGITS * 9968 FORMAT( 1X, I4, ' - ', I4, ':', 1X, - $ 1P,1('(',E20.13,',',E20.13,') ') ) + $ 1P,1('(',E20.13,',',E20.13,') ') ) * *========================================================================= * FORMAT FOR 132 COLUMNS @@ -235,6 +235,6 @@ SUBROUTINE CVOUT( LOUT, N, CX, IDIGIT, IFMT ) $ 1P,1('(',E20.13,',',E20.13,') ') ) * * -* +* 9994 FORMAT( 1X, ' ' ) END diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/second_NONE.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/second_NONE.f index b5d798c268ab..01fcc9dcf812 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/second_NONE.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/second_NONE.f @@ -20,6 +20,8 @@ SUBROUTINE ARSCND( T ) REAL TARRAY( 2 ) * .. * .. External Functions .. + REAL ETIME + EXTERNAL ETIME * .. * .. Executable Statements .. * diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/zmout.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/zmout.f index 9877aa833631..c39f6defee1f 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/zmout.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/zmout.f @@ -74,34 +74,34 @@ SUBROUTINE ZMOUT( LOUT, M, N, A, LDA, IDIGIT, IFMT ) IF (K1.NE.N) THEN WRITE( LOUT, 9994 )I, ( A( I, J ), J = K1, K2 ) ELSE - WRITE( LOUT, 9984 )I, ( A( I, J ), J = K1, K2 ) + WRITE( LOUT, 9984 )I, ( A( I, J ), J = K1, K2 ) END IF 30 CONTINUE 40 CONTINUE * ELSE IF( NDIGIT.LE.6 ) THEN - DO 60 K1 = 1, N, 2 + DO 60 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) WRITE( LOUT, 9997 )( ICOL, I, I = K1, K2 ) DO 50 I = 1, M IF (K1.NE.N) THEN WRITE( LOUT, 9993 )I, ( A( I, J ), J = K1, K2 ) - ELSE - WRITE( LOUT, 9983 )I, ( A( I, J ), J = K1, K2 ) + ELSE + WRITE( LOUT, 9983 )I, ( A( I, J ), J = K1, K2 ) END IF 50 CONTINUE 60 CONTINUE * ELSE IF( NDIGIT.LE.8 ) THEN - DO 80 K1 = 1, N, 2 + DO 80 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) WRITE( LOUT, 9996 )( ICOL, I, I = K1, K2 ) DO 70 I = 1, M IF (K1.NE.N) THEN WRITE( LOUT, 9992 )I, ( A( I, J ), J = K1, K2 ) ELSE - WRITE( LOUT, 9982 )I, ( A( I, J ), J = K1, K2 ) - END IF + WRITE( LOUT, 9982 )I, ( A( I, J ), J = K1, K2 ) + END IF 70 CONTINUE 80 CONTINUE * @@ -124,20 +124,20 @@ SUBROUTINE ZMOUT( LOUT, M, N, A, LDA, IDIGIT, IFMT ) K2 = MIN0( N, K1+3 ) WRITE( LOUT, 9998 )( ICOL, I, I = K1, K2 ) DO 110 I = 1, M - IF ((K1+3).LE.N) THEN + IF ((K1+3).LE.N) THEN WRITE( LOUT, 9974 )I, ( A( I, J ), J = K1, K2 ) ELSE IF ((K1+3-N).EQ.1) THEN WRITE( LOUT, 9964 )I, ( A( I, J ), J = k1, K2 ) ELSE IF ((K1+3-N).EQ.2) THEN WRITE( LOUT, 9954 )I, ( A( I, J ), J = K1, K2 ) ELSE IF ((K1+3-N).EQ.3) THEN - WRITE( LOUT, 9944 )I, ( A( I, J ), J = K1, K2 ) + WRITE( LOUT, 9944 )I, ( A( I, J ), J = K1, K2 ) END IF 110 CONTINUE 120 CONTINUE * ELSE IF( NDIGIT.LE.6 ) THEN - DO 140 K1 = 1, N, 3 + DO 140 K1 = 1, N, 3 K2 = MIN0( N, K1+ 2) WRITE( LOUT, 9997 )( ICOL, I, I = K1, K2 ) DO 130 I = 1, M @@ -185,14 +185,14 @@ SUBROUTINE ZMOUT( LOUT, M, N, A, LDA, IDIGIT, IFMT ) 9998 FORMAT( 11X, 4( 9X, 3A1, I4, 9X ) ) 9997 FORMAT( 10X, 4( 11X, 3A1, I4, 11X ) ) 9996 FORMAT( 10X, 3( 13X, 3A1, I4, 13X ) ) - 9995 FORMAT( 12X, 2( 18x, 3A1, I4, 18X ) ) + 9995 FORMAT( 12X, 2( 18x, 3A1, I4, 18X ) ) * *======================================================== * FORMAT FOR 72 COLUMN *======================================================== * * DISPLAY 4 SIGNIFICANT DIGITS -* +* 9994 FORMAT( 1X, ' Row', I4, ':', 1X, 1P,2('(',D10.3,',',D10.3,') ') ) 9984 FORMAT( 1X, ' Row', I4, ':', 1X, 1P,1('(',D10.3,',',D10.3,') ') ) * diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/zvout.f b/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/zvout.f index ac7e6f9fcd0a..8c42eb890846 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/zvout.f +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/UTIL/zvout.f @@ -63,21 +63,21 @@ SUBROUTINE ZVOUT( LOUT, N, CX, IDIGIT, IFMT ) DO 30 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) IF (K1.NE.N) THEN - WRITE( LOUT, 9998 )K1, K2, ( CX( I ), + WRITE( LOUT, 9998 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE - WRITE( LOUT, 9997 )K1, K2, ( CX( I ), - $ I = K1, K2 ) + WRITE( LOUT, 9997 )K1, K2, ( CX( I ), + $ I = K1, K2 ) END IF 30 CONTINUE ELSE IF( NDIGIT.LE.6 ) THEN DO 40 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) IF (K1.NE.N) THEN - WRITE( LOUT, 9988 )K1, K2, ( CX( I ), + WRITE( LOUT, 9988 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE - WRITE( LOUT, 9987 )K1, K2, ( CX( I ), + WRITE( LOUT, 9987 )K1, K2, ( CX( I ), $ I = K1, K2 ) END IF 40 CONTINUE @@ -85,11 +85,11 @@ SUBROUTINE ZVOUT( LOUT, N, CX, IDIGIT, IFMT ) DO 50 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) IF (K1.NE.N) THEN - WRITE( LOUT, 9978 )K1, K2, ( CX( I ), + WRITE( LOUT, 9978 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE - WRITE( LOUT, 9977 )K1, K2, ( CX( I ), - $ I = K1, K2 ) + WRITE( LOUT, 9977 )K1, K2, ( CX( I ), + $ I = K1, K2 ) END IF 50 CONTINUE ELSE @@ -104,47 +104,47 @@ SUBROUTINE ZVOUT( LOUT, N, CX, IDIGIT, IFMT ) * ELSE IF( NDIGIT.LE.4 ) THEN - DO 70 K1 = 1, N, 4 + DO 70 K1 = 1, N, 4 K2 = MIN0( N, K1+3 ) IF ((K1+3).LE.N) THEN - WRITE( LOUT, 9958 )K1, K2, ( CX( I ), + WRITE( LOUT, 9958 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+3-N) .EQ. 1) THEN - WRITE( LOUT, 9957 )K1, K2, ( CX( I ), + WRITE( LOUT, 9957 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+3-N) .EQ. 2) THEN - WRITE( LOUT, 9956 )K1, K2, ( CX( I ), + WRITE( LOUT, 9956 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+3-N) .EQ. 1) THEN - WRITE( LOUT, 9955 )K1, K2, ( CX( I ), + WRITE( LOUT, 9955 )K1, K2, ( CX( I ), $ I = K1, K2 ) END IF 70 CONTINUE ELSE IF( NDIGIT.LE.6 ) THEN - DO 80 K1 = 1, N, 3 + DO 80 K1 = 1, N, 3 K2 = MIN0( N, K1+2 ) IF ((K1+2).LE.N) THEN - WRITE( LOUT, 9948 )K1, K2, ( CX( I ), + WRITE( LOUT, 9948 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+2-N) .EQ. 1) THEN - WRITE( LOUT, 9947 )K1, K2, ( CX( I ), + WRITE( LOUT, 9947 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+2-N) .EQ. 2) THEN - WRITE( LOUT, 9946 )K1, K2, ( CX( I ), + WRITE( LOUT, 9946 )K1, K2, ( CX( I ), $ I = K1, K2 ) END IF 80 CONTINUE ELSE IF( NDIGIT.LE.8 ) THEN - DO 90 K1 = 1, N, 3 + DO 90 K1 = 1, N, 3 K2 = MIN0( N, K1+2 ) IF ((K1+2).LE.N) THEN - WRITE( LOUT, 9938 )K1, K2, ( CX( I ), + WRITE( LOUT, 9938 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+2-N) .EQ. 1) THEN - WRITE( LOUT, 9937 )K1, K2, ( CX( I ), + WRITE( LOUT, 9937 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+2-N) .EQ. 2) THEN - WRITE( LOUT, 9936 )K1, K2, ( CX( I ), + WRITE( LOUT, 9936 )K1, K2, ( CX( I ), $ I = K1, K2 ) END IF 90 CONTINUE @@ -152,10 +152,10 @@ SUBROUTINE ZVOUT( LOUT, N, CX, IDIGIT, IFMT ) DO 100 K1 = 1, N, 2 K2 = MIN0( N, K1+1 ) IF ((K1+2).LE.N) THEN - WRITE( LOUT, 9928 )K1, K2, ( CX( I ), + WRITE( LOUT, 9928 )K1, K2, ( CX( I ), $ I = K1, K2 ) ELSE IF ((K1+2-N) .EQ. 1) THEN - WRITE( LOUT, 9927 )K1, K2, ( CX( I ), + WRITE( LOUT, 9927 )K1, K2, ( CX( I ), $ I = K1, K2 ) END IF 100 CONTINUE @@ -171,12 +171,12 @@ SUBROUTINE ZVOUT( LOUT, N, CX, IDIGIT, IFMT ) * DISPLAY 4 SIGNIFICANT DIGITS * 9998 FORMAT( 1X, I4, ' - ', I4, ':', 1X, - $ 1P,2('(',D10.3,',',D10.3,') ') ) + $ 1P,2('(',D10.3,',',D10.3,') ') ) 9997 FORMAT( 1X, I4, ' - ', I4, ':', 1X, $ 1P,1('(',D10.3,',',D10.3,') ') ) * * DISPLAY 6 SIGNIFICANT DIGITS -* +* 9988 FORMAT( 1X, I4, ' - ', I4, ':', 1X, $ 1P,2('(',D12.5,',',D12.5,') ') ) 9987 FORMAT( 1X, I4, ' - ', I4, ':', 1X, @@ -192,7 +192,7 @@ SUBROUTINE ZVOUT( LOUT, N, CX, IDIGIT, IFMT ) * DISPLAY 13 SIGNIFICANT DIGITS * 9968 FORMAT( 1X, I4, ' - ', I4, ':', 1X, - $ 1P,1('(',D20.13,',',D20.13,') ') ) + $ 1P,1('(',D20.13,',',D20.13,') ') ) * *========================================================================= * FORMAT FOR 132 COLUMNS @@ -235,6 +235,6 @@ SUBROUTINE ZVOUT( LOUT, N, CX, IDIGIT, IFMT ) $ 1P,1('(',D20.13,',',D20.13,') ') ) * * -* +* 9994 FORMAT( 1X, ' ' ) END From 6fc285dbcd508f66d0aa84e92ded26154121bd1e Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Thu, 4 Jul 2024 00:00:50 +0200 Subject: [PATCH 493/500] MAINT:sparse.linalg: Modify meson file for ARPACK 3.9.1 --- scipy/sparse/linalg/_eigen/arpack/meson.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scipy/sparse/linalg/_eigen/arpack/meson.build b/scipy/sparse/linalg/_eigen/arpack/meson.build index 08a7b83d14c3..ed0ba3151118 100644 --- a/scipy/sparse/linalg/_eigen/arpack/meson.build +++ b/scipy/sparse/linalg/_eigen/arpack/meson.build @@ -1,4 +1,5 @@ arpack_sources = [ + 'ARPACK/SRC/ccdotc.f', 'ARPACK/SRC/cgetv0.f', 'ARPACK/SRC/cnaitr.f', 'ARPACK/SRC/cnapps.f', @@ -65,6 +66,7 @@ arpack_sources = [ 'ARPACK/SRC/zngets.f', 'ARPACK/SRC/zsortc.f', 'ARPACK/SRC/zstatn.f', + 'ARPACK/SRC/zzdotc.f', 'ARPACK/UTIL/cmout.f', 'ARPACK/UTIL/cvout.f', 'ARPACK/UTIL/dmout.f', From 3260301a018f34e86922fc123845fdefa1eed392 Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Thu, 4 Jul 2024 07:28:03 +0200 Subject: [PATCH 494/500] TST:MAINT:sparse.linalg: Adjust tests for ARPACK 3.9.1 --- scipy/sparse/linalg/_eigen/tests/test_svds.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scipy/sparse/linalg/_eigen/tests/test_svds.py b/scipy/sparse/linalg/_eigen/tests/test_svds.py index 216cf05c79da..02e589abd3c7 100644 --- a/scipy/sparse/linalg/_eigen/tests/test_svds.py +++ b/scipy/sparse/linalg/_eigen/tests/test_svds.py @@ -579,7 +579,7 @@ def reorder(args): with pytest.warns(UserWarning, match="The problem size"): U1, s1, VH1 = reorder(svds(A, k, v0=v0, solver=solver, random_state=0)) - U2, s2, VH2 = reorder(svds(L, k, v0=v0, solver=solver, + U2, s2, VH2 = reorder(svds(L, k, v0=v0, solver=solver, random_state=0)) else: U1, s1, VH1 = reorder(svds(A, k, v0=v0, solver=solver, @@ -715,6 +715,9 @@ def test_zero_matrix(self, shape, dtype): 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") From 1b3d0f3f3258563c010b83557c78a4501e86b18d Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Thu, 4 Jul 2024 07:32:30 +0200 Subject: [PATCH 495/500] MAINT:sparse.linalg: Extend the ARPACK comment about OSX ABI mismatch --- scipy/sparse/linalg/_eigen/arpack/ARPACK/README.scipy | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/README.scipy b/scipy/sparse/linalg/_eigen/arpack/ARPACK/README.scipy index 5cdb5a96c86f..eea037c4f9ec 100644 --- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/README.scipy +++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/README.scipy @@ -5,7 +5,8 @@ NOTE FOR VENDORS: it is in general safe to use a system version of ARPACK instead. Note, however, that ARPACK and early versions of ARPACK-NG have certain bugs, so using those over the bundled version is not recommended. -The bundled version has the following patch applied: +For versions of ARPACK-NG prior to 3.9.0, the bundled version has the +following patch applied: Replace calls to certain Fortran functions with wrapper functions, to avoid various ABI mismatches on OSX. These changes are @@ -20,3 +21,8 @@ s@\bcladiv\b@wcladiv@g; s@\bzladiv\b@wzladiv@g; s@\bslamch\b@slamch@g;' \ SRC/*.f UTIL/*.f + +For versions 3.9.0+, this is not necessary anymore and the issue is resolved +via vendoring the copies of ccdotc and zzdotc. + +See https://github.com/opencollab/arpack-ng/pull/346 for more details. From d0e7d4646bc55474bf3271ff6f2322f6469cb69a Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 4 Jul 2024 16:02:01 -0600 Subject: [PATCH 496/500] MAINT, DOC: simplify docs warn filter * gh-19228 has been resolved for quite some time now so let's remove the `conf.py` warnings filter shim that was associated with it. Docs build worked locally. [docs only] --- doc/source/conf.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index e517a789d0a4..ec8cdd7efb22 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -184,12 +184,6 @@ message=r'There is no current event loop', category=DeprecationWarning, ) -# TODO: remove after gh-19228 resolved: -warnings.filterwarnings( - 'ignore', - message=r'.*path is deprecated.*', - category=DeprecationWarning, -) # ----------------------------------------------------------------------------- # HTML output From 2bf546a021968a439d8fa7b8ead7dc411a6db1c8 Mon Sep 17 00:00:00 2001 From: Mikhail Ryazanov Date: Fri, 5 Jul 2024 16:29:58 +0300 Subject: [PATCH 497/500] MAINT/DOC: corrections to benchmarks README and `sparse.Arithmetic` benchmark (#21115) * Update dev.py syntax * Preserve format when squaring Currently **2 implicitly converts COO and DIA to CSR --- benchmarks/README.rst | 4 ++-- benchmarks/benchmarks/sparse.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmarks/README.rst b/benchmarks/README.rst index 2d708dff8c9c..9f844e47cad2 100644 --- a/benchmarks/README.rst +++ b/benchmarks/README.rst @@ -19,11 +19,11 @@ development version of SciPy to your current Python environment. Run a benchmark against currently checked-out SciPy version (don't record the result):: - python dev.py bench sparse.Arithmetic + python dev.py bench --submodule sparse.Arithmetic Compare change in benchmark results with another branch:: - python dev.py bench-compare main sparse.Arithmetic + python dev.py bench --compare main --submodule sparse.Arithmetic Run ASV commands directly (note, this will not set env vars for ``ccache`` and disabling BLAS/LAPACK multi-threading, as ``dev.py`` does):: diff --git a/benchmarks/benchmarks/sparse.py b/benchmarks/benchmarks/sparse.py index b9453d2665ff..a690b63c111f 100644 --- a/benchmarks/benchmarks/sparse.py +++ b/benchmarks/benchmarks/sparse.py @@ -57,8 +57,8 @@ class Arithmetic(Benchmark): ] def setup(self, format, XY, op): - matrices = dict(A=poisson2d(250, format=format), - B=poisson2d(250, format=format)**2) + matrices = dict(A=poisson2d(250, format=format)) + matrices['B'] = (matrices['A']**2).asformat(format) x = matrices[XY[0]] self.y = matrices[XY[1]] From 65e1d1bde1eecb10c7cdc4e9fc79ae36b1826ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A8=97=E0=A8=97=E0=A8=A8=E0=A8=A6=E0=A9=80=E0=A8=AA=20?= =?UTF-8?q?=E0=A8=B8=E0=A8=BF=E0=A9=B0=E0=A8=98=20=28Gagandeep=20Singh=29?= Date: Fri, 5 Jul 2024 21:53:34 +0530 Subject: [PATCH 498/500] MAINT: Unskip `scipy.misc.test.test_decorator` for Python 3.13+ (#21104) Closes gh-19572 --- scipy/misc/tests/test_doccer.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/scipy/misc/tests/test_doccer.py b/scipy/misc/tests/test_doccer.py index 2ba37becccec..176a69698b10 100644 --- a/scipy/misc/tests/test_doccer.py +++ b/scipy/misc/tests/test_doccer.py @@ -72,7 +72,6 @@ def test_docformat(): @pytest.mark.skipif(DOCSTRINGS_STRIPPED, reason="docstrings stripped") -@pytest.mark.skipif(sys.version_info >= (3, 13), reason='it fails on Py3.13') def test_decorator(): with suppress_warnings() as sup: sup.filter(category=DeprecationWarning) @@ -84,23 +83,32 @@ def func(): """ Docstring %(strtest3)s """ - assert_equal(func.__doc__, """ Docstring + + def expected(): + """ Docstring Another test with some indent - """) + """ + assert_equal(func.__doc__, expected.__doc__) # without unindentation of parameters - decorator = doccer.filldoc(doc_dict, False) + + # The docstring should be unindented for Python 3.13+ + # because of https://github.com/python/cpython/issues/81283 + decorator = doccer.filldoc(doc_dict, False if \ + sys.version_info < (3, 13) else True) @decorator def func(): """ Docstring %(strtest3)s """ - assert_equal(func.__doc__, """ Docstring + def expected(): + """ Docstring Another test with some indent - """) + """ + assert_equal(func.__doc__, expected.__doc__) @pytest.mark.skipif(DOCSTRINGS_STRIPPED, reason="docstrings stripped") From 77854c7e9693138267d8d3a38c23096772050d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Mon, 1 Jul 2024 12:22:02 -0300 Subject: [PATCH 499/500] DOC: optimize: Add docstring to InverseJacobian Also include a .. legacy:: note. [docs only] --- scipy/optimize/_nonlin.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scipy/optimize/_nonlin.py b/scipy/optimize/_nonlin.py index 2b9da70d34e6..b6e07683500b 100644 --- a/scipy/optimize/_nonlin.py +++ b/scipy/optimize/_nonlin.py @@ -451,6 +451,26 @@ def setup(self, x, F, func): class InverseJacobian: + """ + A simple wrapper that inverts the Jacobian using the `solve` method. + + .. legacy:: class + + See the newer, more consistent interfaces in :mod:`scipy.optimize`. + + Parameters + ---------- + jacobian : Jacobian + The Jacobian to invert. + + Attributes + ---------- + shape + Matrix dimensions (M, N) + dtype + Data type of the matrix. + + """ def __init__(self, jacobian): self.jacobian = jacobian self.matvec = jacobian.solve From 6e19616f68fde9f5d8e5f62d88c532301ce925ff Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Fri, 5 Jul 2024 18:23:17 -0600 Subject: [PATCH 500/500] MAINT: remove another mpl test shim * Remove another case where a test was being skipped because matplotlib had not yet released a version that is compatible with NumPy 2, which is no longer the case. [skip cirrus] [skip circle] --- scipy/stats/tests/test_survival.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/scipy/stats/tests/test_survival.py b/scipy/stats/tests/test_survival.py index f8360b913928..60a5fef27ae2 100644 --- a/scipy/stats/tests/test_survival.py +++ b/scipy/stats/tests/test_survival.py @@ -382,13 +382,9 @@ def test_plot_iv(self): import matplotlib.pyplot as plt # noqa: F401 res.sf.plot() # no other errors occur except (ModuleNotFoundError, ImportError): - # Avoid trying to call MPL with numpy 2.0-dev, because that fails - # too often due to ABI mismatches and is hard to avoid. This test - # will work fine again once MPL has done a 2.0-compatible release. - if not np.__version__.startswith('2.0.0.dev0'): - message = r"matplotlib must be installed to use method `plot`." - with pytest.raises(ModuleNotFoundError, match=message): - res.sf.plot() + message = r"matplotlib must be installed to use method `plot`." + with pytest.raises(ModuleNotFoundError, match=message): + res.sf.plot() class TestLogRank: