Skip to content

Commit a2e9157

Browse files
mckib2mdhaber
andauthored
BLD: Replace Boost with Boost.Math standalone (scipy#17432)
* BLD: Replace Boost with Boost.Math standalone Co-authored-by: Matt Haberland <[email protected]>
1 parent edd8fd2 commit a2e9157

24 files changed

+101
-79
lines changed

.gitmodules

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
[submodule "doc/source/_static/scipy-mathjax"]
22
path = doc/source/_static/scipy-mathjax
33
url = https://github.com/scipy/scipy-mathjax.git
4-
[submodule "scipy/_lib/boost"]
5-
path = scipy/_lib/boost
6-
url = https://github.com/scipy/boost-headers-only
7-
shallow = true
84
[submodule "scipy/sparse/linalg/_propack/PROPACK"]
95
path = scipy/sparse/linalg/_propack/PROPACK
106
url = https://github.com/scipy/PROPACK
@@ -17,3 +13,7 @@
1713
path = scipy/_lib/highs
1814
url = https://github.com/scipy/highs
1915
shallow = true
16+
[submodule "scipy/_lib/boost_math"]
17+
path = scipy/_lib/boost_math
18+
url = https://github.com/boostorg/math.git
19+
shallow = true

LICENSES_bundled.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,9 @@ License: MIT
237237
For details, see scipy/optimize/_highs/LICENCE
238238

239239
Name: Boost
240-
Files: scipy/_lib/boost/*
240+
Files: scipy/_lib/boost_math/*
241241
License: Boost Software License - Version 1.0
242-
For details, see scipy/_lib/boost/LICENSE_1_0.txt
242+
For details, see scipy/_lib/boost_math/LICENSE.txt
243243

244244
Name: Biasedurn
245245
Files: scipy/stats/biasedurn/*

ci/cirrus_general_ci.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ modified_clone: &MODIFIED_CLONE
1212
git reset --hard $CIRRUS_CHANGE_IN_REPO
1313
else
1414
# it's a PR so clone the main branch then merge the changes from the PR
15-
git clone --recursive https://x-access-token:${CIRRUS_REPO_CLONE_TOKEN}@github.com/${CIRRUS_REPO_FULL_NAME}.git $CIRRUS_WORKING_DIR
15+
git clone https://x-access-token:${CIRRUS_REPO_CLONE_TOKEN}@github.com/${CIRRUS_REPO_FULL_NAME}.git $CIRRUS_WORKING_DIR
1616
git fetch origin pull/$CIRRUS_PR/head:pull/$CIRRUS_PR
1717
1818
# CIRRUS_BASE_BRANCH will probably be `main` for the majority of the time
@@ -22,6 +22,7 @@ modified_clone: &MODIFIED_CLONE
2222
2323
# alpine git package needs default user.name and user.email to be set before a merge
2424
git -c user.email="[email protected]" merge --no-commit pull/$CIRRUS_PR
25+
git submodule update --init --recursive
2526
fi
2627
2728
@@ -114,7 +115,7 @@ musllinux_amd64_test_task:
114115
115116
macos_arm64_test_task:
116117
macos_instance:
117-
image: ghcr.io/cirruslabs/macos-monterey-xcode:13.3.1
118+
image: ghcr.io/cirruslabs/macos-monterey-xcode:14
118119

119120
<<: *MODIFIED_CLONE
120121

ci/cirrus_wheels.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ cirrus_wheels_linux_aarch64_task:
4242

4343
cirrus_wheels_macos_arm64_task:
4444
macos_instance:
45-
image: ghcr.io/cirruslabs/macos-monterey-xcode:13.3.1
45+
image: ghcr.io/cirruslabs/macos-monterey-xcode:14
4646
matrix:
4747
- env:
4848
CIBW_BUILD: cp39-*

scipy/_lib/_boost_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66

77
def _boost_dir(ret_path: bool = False) -> Union[pathlib.Path, str]:
88
'''Directory where root Boost/ directory lives.'''
9-
p = pathlib.Path(__file__).parent / 'boost'
9+
p = pathlib.Path(__file__).parent / 'boost_math/include'
1010
return p if ret_path else str(p)

scipy/_lib/boost

-1
This file was deleted.

scipy/_lib/boost_math

Submodule boost_math added at 7203fa2

scipy/_lib/meson.build

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
fs = import('fs')
2-
if not fs.exists('boost/README.rst')
2+
if not fs.exists('boost_math/README.md')
33
error('Missing the `boost` submodule! Run `git submodule update --init` to fix this.')
44
endif
55
if not fs.exists('highs/README.md')

scipy/_lib/setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
def check_boost_submodule():
55
from scipy._lib._boost_utils import _boost_dir
66

7-
if not os.path.exists(_boost_dir(ret_path=True) / 'README.rst'):
7+
if not os.path.exists(_boost_dir(ret_path=True).parent / 'README.md'):
88
raise RuntimeError("Missing the `boost` submodule! Run `git submodule "
99
"update --init` to fix this.")
1010

scipy/_lib/tests/test_boost_build.cpp

-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
#include <iostream>
44
#include <boost/math/distributions.hpp>
5-
#include <boost/static_assert.hpp>
65

76
void test() {
8-
BOOST_STATIC_ASSERT(1 == 1);
97
boost::math::binomial_distribution<double> d(10, 0.5);
108
}

scipy/special/meson.build

+2-2
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,8 @@ py3.extension_module('_ufuncs_cxx',
370370
[ufuncs_cxx_sources,
371371
uf_cython_gen_cpp.process(cython_special[2]), # _ufuncs_cxx.pyx
372372
],
373-
cpp_args: cython_cpp_args,
374-
include_directories: ['../_lib/boost', '../_lib',
373+
cpp_args: [cython_cpp_args, '-DBOOST_MATH_STANDALONE=1'],
374+
include_directories: ['../_lib/boost_math/include', '../_lib',
375375
'../_build_utils/src'],
376376
link_args: version_link_args,
377377
dependencies: [np_dep, ellint_dep],

scipy/special/setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ def configuration(parent_package='',top_path=None):
3535
if python_inc_dirs != plat_specific_python_inc_dirs:
3636
inc_dirs.append(plat_specific_python_inc_dirs)
3737
inc_dirs.append(join(dirname(dirname(__file__)), '_lib'))
38-
inc_dirs.append(join(dirname(dirname(__file__)), '_lib', 'boost'))
38+
inc_dirs.append(join(dirname(dirname(__file__)), '_lib', 'boost_math',
39+
'include'))
3940
inc_dirs.append(join(dirname(dirname(__file__)), '_build_utils', 'src'))
4041

4142
# C libraries

scipy/stats/_boost/include/Templated_PyUFunc.hpp

+1-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
#include <cstddef>
55
#include <stdexcept>
6-
#include <boost/static_assert.hpp>
7-
#define UFUNC_STATIC_ASSERT(cond, msg) BOOST_STATIC_ASSERT_MSG(cond, msg)
86
#define UFUNC_CALLFUNC(RealType, NINPUTS, func, inputs) \
97
*output = callfunc<RealType, NINPUTS>(func, inputs);
108

@@ -38,7 +36,7 @@ static void
3836
PyUFunc_T(char **args, npy_intp const *dimensions, npy_intp const *steps,
3937
void *func)
4038
{
41-
UFUNC_STATIC_ASSERT(NINPUTS > 0, "numpy.ufunc demands NINPUT > 0!");
39+
static_assert(NINPUTS > 0, "numpy.ufunc demands NINPUT > 0!");
4240
RealType *inputs[NINPUTS];
4341
for (std::size_t ii = 0; ii < NINPUTS; ++ii) {
4442
inputs[ii] = (RealType*)args[ii];

scipy/stats/_boost/include/func_defs.hpp

+6-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#include <cmath>
66

77
#include "boost/math/distributions.hpp"
8-
#include "boost/format.hpp"
98

109
// Round up to achieve correct ppf(cdf) round-trips for discrete distributions
1110
typedef boost::math::policies::policy<
@@ -25,7 +24,9 @@ template <class RealType>
2524
RealType
2625
boost::math::policies::user_evaluation_error(const char* function, const char* message, const RealType& val) {
2726
std::string msg("Error in function ");
28-
msg += (boost::format(function) % typeid(RealType).name()).str() + ": ";
27+
std::string haystack {function};
28+
const std::string needle {"%1%"};
29+
msg += haystack.replace(haystack.find(needle), needle.length(), typeid(RealType).name()) + ": ";
2930
// "message" may have %1%, but arguments don't always contain all
3031
// required information, so don't call boost::format for now
3132
msg += message;
@@ -40,7 +41,9 @@ template <class RealType>
4041
RealType
4142
boost::math::policies::user_overflow_error(const char* function, const char* message, const RealType& val) {
4243
std::string msg("Error in function ");
43-
msg += (boost::format(function) % typeid(RealType).name()).str() + ": ";
44+
std::string haystack {function};
45+
const std::string needle {"%1%"};
46+
msg += haystack.replace(haystack.find(needle), needle.length(), typeid(RealType).name()) + ": ";
4447
// From Boost docs: "overflow and underflow messages do not contain this %1% specifier
4548
// (since the value of value is immaterial in these cases)."
4649
if (message) {

scipy/stats/_boost/meson.build

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
cpp_args = [
2+
'-DBOOST_MATH_STANDALONE=1',
23
'-DBOOST_MATH_DOMAIN_ERROR_POLICY=ignore_error',
34
'-DBOOST_MATH_EVALUATION_ERROR_POLICY=user_error',
45
'-DBOOST_MATH_OVERFLOW_ERROR_POLICY=user_error',
@@ -23,7 +24,7 @@ pyx_files = [
2324
foreach pyx_file: pyx_files
2425
py3.extension_module(pyx_file[0],
2526
pyx_file[1],
26-
include_directories: ['include', '../../_lib/boost'],
27+
include_directories: ['include', '../../_lib/boost_math/include'],
2728
dependencies: np_dep,
2829
link_args: version_link_args,
2930
cpp_args: [cpp_args, cython_cpp_args],

scipy/stats/_boost/setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def configuration(parent_package='', top_path=None):
1717

1818
DEFINES = [
1919
# return nan instead of throwing
20+
('DBOOST_MATH_STANDALONE', '1'),
2021
('BOOST_MATH_DOMAIN_ERROR_POLICY', 'ignore_error'),
2122
('BOOST_MATH_EVALUATION_ERROR_POLICY', 'user_error'),
2223
('BOOST_MATH_OVERFLOW_ERROR_POLICY', 'user_error'),

scipy/stats/_continuous_distns.py

+24-25
Original file line numberDiff line numberDiff line change
@@ -689,14 +689,11 @@ def _sf(self, x, a, b):
689689
return _boost._beta_sf(x, a, b)
690690

691691
def _isf(self, x, a, b):
692-
with warnings.catch_warnings():
693-
# See gh-14901
694-
message = "overflow encountered in _beta_isf"
695-
warnings.filterwarnings('ignore', message=message)
692+
with np.errstate(over='ignore'): # see gh-17432
696693
return _boost._beta_isf(x, a, b)
697694

698695
def _ppf(self, q, a, b):
699-
with warnings.catch_warnings():
696+
with np.errstate(over='ignore'): # see gh-17432
700697
message = "overflow encountered in _beta_ppf"
701698
warnings.filterwarnings('ignore', message=message)
702699
return _boost._beta_ppf(q, a, b)
@@ -6765,35 +6762,31 @@ def _logpdf(self, x, df, nc):
67656762

67666763
def _pdf(self, x, df, nc):
67676764
cond = np.ones_like(x, dtype=bool) & (nc != 0)
6768-
with warnings.catch_warnings():
6769-
message = "overflow encountered in _ncx2_pdf"
6770-
warnings.filterwarnings("ignore", message=message)
6765+
with np.errstate(over='ignore'): # see gh-17432
67716766
return _lazywhere(cond, (x, df, nc), f=_boost._ncx2_pdf,
67726767
f2=lambda x, df, _: chi2._pdf(x, df))
67736768

67746769
def _cdf(self, x, df, nc):
67756770
cond = np.ones_like(x, dtype=bool) & (nc != 0)
6776-
return _lazywhere(cond, (x, df, nc), f=_boost._ncx2_cdf,
6777-
f2=lambda x, df, _: chi2._cdf(x, df))
6771+
with np.errstate(over='ignore'): # see gh-17432
6772+
return _lazywhere(cond, (x, df, nc), f=_boost._ncx2_cdf,
6773+
f2=lambda x, df, _: chi2._cdf(x, df))
67786774

67796775
def _ppf(self, q, df, nc):
67806776
cond = np.ones_like(q, dtype=bool) & (nc != 0)
6781-
with warnings.catch_warnings():
6782-
message = "overflow encountered in _ncx2_ppf"
6783-
warnings.filterwarnings("ignore", message=message)
6777+
with np.errstate(over='ignore'): # see gh-17432
67846778
return _lazywhere(cond, (q, df, nc), f=_boost._ncx2_ppf,
67856779
f2=lambda x, df, _: chi2._ppf(x, df))
67866780

67876781
def _sf(self, x, df, nc):
67886782
cond = np.ones_like(x, dtype=bool) & (nc != 0)
6789-
return _lazywhere(cond, (x, df, nc), f=_boost._ncx2_sf,
6790-
f2=lambda x, df, _: chi2._sf(x, df))
6783+
with np.errstate(over='ignore'): # see gh-17432
6784+
return _lazywhere(cond, (x, df, nc), f=_boost._ncx2_sf,
6785+
f2=lambda x, df, _: chi2._sf(x, df))
67916786

67926787
def _isf(self, x, df, nc):
67936788
cond = np.ones_like(x, dtype=bool) & (nc != 0)
6794-
with warnings.catch_warnings():
6795-
message = "overflow encountered in _ncx2_isf"
6796-
warnings.filterwarnings("ignore", message=message)
6789+
with np.errstate(over='ignore'): # see gh-17432
67976790
return _lazywhere(cond, (x, df, nc), f=_boost._ncx2_isf,
67986791
f2=lambda x, df, _: chi2._isf(x, df))
67996792

@@ -6875,13 +6868,15 @@ def _cdf(self, x, dfn, dfd, nc):
68756868
return _boost._ncf_cdf(x, dfn, dfd, nc)
68766869

68776870
def _ppf(self, q, dfn, dfd, nc):
6878-
return _boost._ncf_ppf(q, dfn, dfd, nc)
6871+
with np.errstate(over='ignore'): # see gh-17432
6872+
return _boost._ncf_ppf(q, dfn, dfd, nc)
68796873

68806874
def _sf(self, x, dfn, dfd, nc):
68816875
return _boost._ncf_sf(x, dfn, dfd, nc)
68826876

68836877
def _isf(self, x, dfn, dfd, nc):
6884-
return _boost._ncf_isf(x, dfn, dfd, nc)
6878+
with np.errstate(over='ignore'): # see gh-17432
6879+
return _boost._ncf_isf(x, dfn, dfd, nc)
68856880

68866881
def _munp(self, n, dfn, dfd, nc):
68876882
val = (dfn * 1.0/dfd)**n
@@ -7068,22 +7063,26 @@ def _pdf(self, x, df, nc):
70687063
return np.clip(Px, 0, None)
70697064

70707065
def _cdf(self, x, df, nc):
7071-
return np.clip(_boost._nct_cdf(x, df, nc), 0, 1)
7066+
with np.errstate(over='ignore'): # see gh-17432
7067+
return np.clip(_boost._nct_cdf(x, df, nc), 0, 1)
70727068

70737069
def _ppf(self, q, df, nc):
7074-
return _boost._nct_ppf(q, df, nc)
7070+
with np.errstate(over='ignore'): # see gh-17432
7071+
return _boost._nct_ppf(q, df, nc)
70757072

70767073
def _sf(self, x, df, nc):
7077-
return np.clip(_boost._nct_sf(x, df, nc), 0, 1)
7074+
with np.errstate(over='ignore'): # see gh-17432
7075+
return np.clip(_boost._nct_sf(x, df, nc), 0, 1)
70787076

70797077
def _isf(self, x, df, nc):
7080-
return _boost._nct_isf(x, df, nc)
7078+
with np.errstate(over='ignore'): # see gh-17432
7079+
return _boost._nct_isf(x, df, nc)
70817080

70827081
def _stats(self, df, nc, moments='mv'):
70837082
mu = _boost._nct_mean(df, nc)
70847083
mu2 = _boost._nct_variance(df, nc)
70857084
g1 = _boost._nct_skewness(df, nc) if 's' in moments else None
7086-
g2 = _boost._nct_kurtosis_excess(df, nc)-3 if 'k' in moments else None
7085+
g2 = _boost._nct_kurtosis_excess(df, nc) if 'k' in moments else None
70877086
return mu, mu2, g1, g2
70887087

70897088

scipy/stats/_discrete_distns.py

+7-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# SciPy Developers 2004-2011
44
#
55
from functools import partial
6-
import warnings
76

87
from scipy import special
98
from scipy.special import entr, logsumexp, betaln, gammaln as gamln, zeta
@@ -355,16 +354,11 @@ def _sf(self, x, n, p):
355354
return _boost._nbinom_sf(k, n, p)
356355

357356
def _isf(self, x, n, p):
358-
with warnings.catch_warnings():
359-
# See gh-14901
360-
message = "overflow encountered in _nbinom_isf"
361-
warnings.filterwarnings('ignore', message=message)
357+
with np.errstate(over='ignore'): # see gh-17432
362358
return _boost._nbinom_isf(x, n, p)
363359

364360
def _ppf(self, q, n, p):
365-
with warnings.catch_warnings():
366-
message = "overflow encountered in _nbinom_ppf"
367-
warnings.filterwarnings('ignore', message=message)
361+
with np.errstate(over='ignore'): # see gh-17432
368362
return _boost._nbinom_ppf(q, n, p)
369363

370364
def _stats(self, n, p):
@@ -1427,9 +1421,7 @@ def _rvs(self, mu1, mu2, size=None, random_state=None):
14271421
random_state.poisson(mu2, n))
14281422

14291423
def _pmf(self, x, mu1, mu2):
1430-
with warnings.catch_warnings():
1431-
message = "overflow encountered in _ncx2_pdf"
1432-
warnings.filterwarnings("ignore", message=message)
1424+
with np.errstate(over='ignore'): # see gh-17432
14331425
px = np.where(x < 0,
14341426
_boost._ncx2_pdf(2*mu2, 2*(1-x), 2*mu1)*2,
14351427
_boost._ncx2_pdf(2*mu1, 2*(1+x), 2*mu2)*2)
@@ -1438,9 +1430,10 @@ def _pmf(self, x, mu1, mu2):
14381430

14391431
def _cdf(self, x, mu1, mu2):
14401432
x = floor(x)
1441-
px = np.where(x < 0,
1442-
_boost._ncx2_cdf(2*mu2, -2*x, 2*mu1),
1443-
1 - _boost._ncx2_cdf(2*mu1, 2*(x+1), 2*mu2))
1433+
with np.errstate(over='ignore'): # see gh-17432
1434+
px = np.where(x < 0,
1435+
_boost._ncx2_cdf(2*mu2, -2*x, 2*mu1),
1436+
1 - _boost._ncx2_cdf(2*mu1, 2*(x+1), 2*mu2))
14441437
return px
14451438

14461439
def _stats(self, mu1, mu2):

scipy/stats/tests/test_boost_ufuncs.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,17 @@
3030
]
3131

3232

33-
@pytest.mark.filterwarnings('ignore::RuntimeWarning')
3433
@pytest.mark.parametrize('func, args, expected', test_data)
3534
def test_stats_boost_ufunc(func, args, expected):
3635
type_sigs = func.types
3736
type_chars = [sig.split('->')[-1] for sig in type_sigs]
3837
for type_char in type_chars:
3938
typ, rtol = type_char_to_type_tol[type_char]
4039
args = [typ(arg) for arg in args]
41-
value = func(*args)
40+
# Harmless overflow warnings are a "feature" of some wrappers on some
41+
# plaforms. This test is about dtype and accuracy, so let's avoid false
42+
# test failures cause by these warnings. See gh-17432.
43+
with np.errstate(over='ignore'):
44+
value = func(*args)
4245
assert isinstance(value, typ)
4346
assert_allclose(value, expected, rtol=rtol)

scipy/stats/tests/test_continuous_basic.py

-2
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ def cases_test_cont_basic():
144144
yield distname, arg
145145

146146

147-
@pytest.mark.filterwarnings('ignore::RuntimeWarning')
148147
@pytest.mark.parametrize('distname,arg', cases_test_cont_basic())
149148
@pytest.mark.parametrize('sn, n_fit_samples', [(500, 200)])
150149
def test_cont_basic(distname, arg, sn, n_fit_samples):
@@ -737,7 +736,6 @@ def check_fit_args_fix(distfn, arg, rvs, method):
737736
npt.assert_(vals5[2] == arg[2])
738737

739738

740-
@pytest.mark.filterwarnings('ignore::RuntimeWarning')
741739
@pytest.mark.parametrize('method', ['pdf', 'logpdf', 'cdf', 'logcdf',
742740
'sf', 'logsf', 'ppf', 'isf'])
743741
@pytest.mark.parametrize('distname, args', distcont)

0 commit comments

Comments
 (0)