Skip to content

Commit 483f36c

Browse files
authored
Merge pull request #1297 from boostorg/1296
Add `logit`, `logistic_sigmoid`, and use in `logistic` dist to support promotion policies
2 parents 2519259 + f87d826 commit 483f36c

File tree

12 files changed

+436
-33
lines changed

12 files changed

+436
-33
lines changed

doc/math.qbk

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,10 @@ and use the function's name as the link text.]
339339
[def __hermite [link math_toolkit.sf_poly.hermite hermite]]
340340
[def __cardinal_b_splines [link math_toolkit.sf_poly.cardinal_b_splines cardinal_b_splines]]
341341

342+
[/logistic functions]
343+
[def __logit [link math_toolkit.logistic.logit logit]]
344+
[def __logistic_sigmoid [link math_toolkit.logistic.logistic_sigmoid logistic_sigmoid]]
345+
342346
[/Misc]
343347
[def __expint [link math_toolkit.expint.expint_i expint]]
344348
[def __spherical_harmonic [link math_toolkit.sf_poly.sph_harm spherical_harmonic]]
@@ -666,6 +670,11 @@ and as a CD ISBN 0-9504833-2-X 978-0-9504833-2-0, Classification 519.2-dc22.
666670
[include sf/jacobi.qbk]
667671
[endsect] [/section:sf_poly Polynomials]
668672

673+
[section:logistic Logistic Functions]
674+
[include sf/logit.qbk]
675+
[include sf/logistic_sigmoid.qbk]
676+
[endsect] [/section:logistic Logistic Functions]
677+
669678
[section:bessel Bessel Functions]
670679
[include sf/bessel_introduction.qbk]
671680
[include sf/bessel_jy.qbk]

doc/sf/logistic_sigmoid.qbk

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[/
2+
Copyright Matt Borland 2025
3+
Distributed under the Boost Software License, Version 1.0.
4+
(See accompanying file LICENSE_1_0.txt or copy at
5+
http://www.boost.org/LICENSE_1_0.txt).
6+
]
7+
8+
[section:logistic_sigmoid logistic_sigmoid]
9+
10+
[h4 Synopsis]
11+
12+
#include <boost/math/special_functions/logistic_sigmoid.hpp>
13+
14+
namespace boost { namespace math {
15+
16+
template <typename RealType, typename Policy>
17+
RealType logistic_sigmoid(RealType x, const Policy&);
18+
19+
template <typename RealType>
20+
RealType logistic_sigmoid(RealType x)
21+
22+
}} // namespaces
23+
24+
[h4 Description]
25+
26+
Returns the [@https://en.wikipedia.org/wiki/Logistic_function logistic sigmoid function]
27+
This function is broadly useful, and is used to compute the CDF of the logistic distribution.
28+
It is also sometimes referred to as expit as it is the inverse of the logit function.
29+
30+
[endsect]

doc/sf/logit.qbk

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[/
2+
Copyright Matt Borland 2025
3+
Distributed under the Boost Software License, Version 1.0.
4+
(See accompanying file LICENSE_1_0.txt or copy at
5+
http://www.boost.org/LICENSE_1_0.txt).
6+
]
7+
8+
[section:logit logit]
9+
10+
[h4 Synopsis]
11+
12+
#include <boost/math/special_functions/logit.hpp>
13+
14+
namespace boost { namespace math {
15+
16+
template <typename RealType, typename Policy>
17+
RealType logit(RealType x, const Policy&);
18+
19+
template <typename RealType>
20+
RealType logit(RealType x)
21+
22+
}} // namespaces
23+
24+
[h4 Description]
25+
26+
Returns the [@https://en.wikipedia.org/wiki/Logit logit function]
27+
This function is broadly useful, and is used to compute the Quantile of the logistic distribution.
28+
The inverse of this function is the logistic_sigmoid.
29+
30+
[endsect]

include/boost/math/distributions/logistic.hpp

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include <boost/math/constants/constants.hpp>
2020
#include <boost/math/policies/policy.hpp>
2121
#include <boost/math/policies/error_handling.hpp>
22+
#include <boost/math/special_functions/logit.hpp>
23+
#include <boost/math/special_functions/logistic_sigmoid.hpp>
2224

2325
namespace boost { namespace math {
2426

@@ -144,13 +146,10 @@ namespace boost { namespace math {
144146
{
145147
return result;
146148
}
147-
BOOST_MATH_STD_USING
148-
RealType power = (location - x) / scale;
149-
if(power > tools::log_max_value<RealType>())
150-
return 0;
151-
if(power < -tools::log_max_value<RealType>())
152-
return 1;
153-
return 1 / (1 + exp(power));
149+
150+
using promoted_real_type = typename policies::evaluation<RealType, Policy>::type;
151+
promoted_real_type power = (static_cast<promoted_real_type>(x) - static_cast<promoted_real_type>(location)) / static_cast<promoted_real_type>(scale);
152+
return logistic_sigmoid(power, policies::make_forwarding_policy_t<Policy>());
154153
}
155154

156155
template <class RealType, class Policy>
@@ -199,7 +198,6 @@ namespace boost { namespace math {
199198
template <class RealType, class Policy>
200199
BOOST_MATH_GPU_ENABLED inline RealType quantile(const logistic_distribution<RealType, Policy>& dist, const RealType& p)
201200
{
202-
BOOST_MATH_STD_USING
203201
RealType location = dist.location();
204202
RealType scale = dist.scale();
205203

@@ -221,21 +219,13 @@ namespace boost { namespace math {
221219
{
222220
return policies::raise_overflow_error<RealType>(function,"probability argument is 1, must be >0 and <1",Policy());
223221
}
224-
//Expressions to try
225-
//return location+scale*log(p/(1-p));
226-
//return location+scale*log1p((2*p-1)/(1-p));
227-
228-
//return location - scale*log( (1-p)/p);
229-
//return location - scale*log1p((1-2*p)/p);
230222

231-
//return -scale*log(1/p-1) + location;
232-
return location - scale * log((1 - p) / p);
223+
return location + scale * logit(p, Policy());
233224
} // RealType quantile(const logistic_distribution<RealType, Policy>& dist, const RealType& p)
234225

235226
template <class RealType, class Policy>
236227
BOOST_MATH_GPU_ENABLED inline RealType cdf(const complemented2_type<logistic_distribution<RealType, Policy>, RealType>& c)
237228
{
238-
BOOST_MATH_STD_USING
239229
RealType location = c.dist.location();
240230
RealType scale = c.dist.scale();
241231
RealType x = c.param;
@@ -259,12 +249,10 @@ namespace boost { namespace math {
259249
{
260250
return result;
261251
}
262-
RealType power = (x - location) / scale;
263-
if(power > tools::log_max_value<RealType>())
264-
return 0;
265-
if(power < -tools::log_max_value<RealType>())
266-
return 1;
267-
return 1 / (1 + exp(power));
252+
253+
using promoted_real_type = typename policies::evaluation<RealType, Policy>::type;
254+
promoted_real_type power = (static_cast<promoted_real_type>(location) - static_cast<promoted_real_type>(x)) / static_cast<promoted_real_type>(scale);
255+
return logistic_sigmoid(power, policies::make_forwarding_policy_t<Policy>());
268256
}
269257

270258
template <class RealType, class Policy>
@@ -306,7 +294,6 @@ namespace boost { namespace math {
306294
template <class RealType, class Policy>
307295
BOOST_MATH_GPU_ENABLED inline RealType quantile(const complemented2_type<logistic_distribution<RealType, Policy>, RealType>& c)
308296
{
309-
BOOST_MATH_STD_USING
310297
RealType scale = c.dist.scale();
311298
RealType location = c.dist.location();
312299
constexpr auto function = "boost::math::quantile(const complement(logistic_distribution<%1%>&), %1%)";
@@ -328,15 +315,8 @@ namespace boost { namespace math {
328315
{
329316
return policies::raise_overflow_error<RealType>(function,"probability argument is 0, but must be >0 and <1",Policy());
330317
}
331-
//Expressions to try
332-
//return location+scale*log((1-q)/q);
333-
return location + scale * log((1 - q) / q);
334-
335-
//return location-scale*log(q/(1-q));
336-
//return location-scale*log1p((2*q-1)/(1-q));
337318

338-
//return location+scale*log(1/q-1);
339-
//return location+scale*log1p(1/q-2);
319+
return location - scale * logit(q, Policy());
340320
}
341321

342322
template <class RealType, class Policy>

include/boost/math/policies/policy.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,25 @@ struct is_noexcept_error_policy
10031003
&& (t8::value != throw_on_error) && (t8::value != user_error));
10041004
};
10051005

1006+
// Generate a forwarding policy to stop further promotion from occurring
1007+
// For example if a special function for float promotes to double, we don't want the next
1008+
// function in the call chain to then promote to long double
1009+
template <typename Policy>
1010+
struct make_forwarding_policy
1011+
{
1012+
using type =
1013+
typename policies::normalise<
1014+
Policy,
1015+
policies::promote_float<false>,
1016+
policies::promote_double<false>,
1017+
policies::discrete_quantile<>,
1018+
policies::assert_undefined<>
1019+
>::type;
1020+
};
1021+
1022+
template <typename Policy>
1023+
using make_forwarding_policy_t = typename make_forwarding_policy<Policy>::type;
1024+
10061025
}}} // namespaces
10071026

10081027
#endif // BOOST_MATH_POLICY_HPP
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright Matt Borland 2025.
2+
// Use, modification and distribution are subject to the
3+
// Boost Software License, Version 1.0.
4+
// (See accompanying file LICENSE_1_0.txt
5+
// or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
7+
#ifndef BOOST_MATH_SF_EXPIT_HPP
8+
#define BOOST_MATH_SF_EXPIT_HPP
9+
10+
#include <boost/math/policies/policy.hpp>
11+
#include <boost/math/tools/precision.hpp>
12+
#include <cmath>
13+
14+
namespace boost {
15+
namespace math {
16+
17+
template <typename RealType, typename Policy>
18+
RealType logistic_sigmoid(RealType x, const Policy&)
19+
{
20+
BOOST_MATH_STD_USING
21+
22+
using promoted_real_type = typename policies::evaluation<RealType, Policy>::type;
23+
24+
if(-x >= tools::log_max_value<RealType>())
25+
{
26+
return static_cast<RealType>(0);
27+
}
28+
if(-x <= -tools::log_max_value<RealType>())
29+
{
30+
return static_cast<RealType>(1);
31+
}
32+
33+
const auto res {static_cast<RealType>(1 / (1 + exp(static_cast<promoted_real_type>(-x))))};
34+
return res;
35+
}
36+
37+
template <typename RealType>
38+
RealType logistic_sigmoid(RealType x)
39+
{
40+
return logistic_sigmoid(x, policies::policy<>());
41+
}
42+
43+
} // namespace math
44+
} // namespace boost
45+
46+
#endif // BOOST_MATH_SF_EXPIT_HPP
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright Matt Borland 2025.
2+
// Use, modification and distribution are subject to the
3+
// Boost Software License, Version 1.0.
4+
// (See accompanying file LICENSE_1_0.txt
5+
// or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
7+
#ifndef BOOST_MATH_SF_LOGIT_HPP
8+
#define BOOST_MATH_SF_LOGIT_HPP
9+
10+
#include <boost/math/tools/config.hpp>
11+
#include <boost/math/policies/policy.hpp>
12+
#include <boost/math/policies/error_handling.hpp>
13+
#include <cmath>
14+
#include <cfenv>
15+
16+
namespace boost {
17+
namespace math {
18+
19+
template <typename RealType, typename Policy>
20+
RealType logit(RealType p, const Policy&)
21+
{
22+
BOOST_MATH_STD_USING
23+
using std::atanh;
24+
25+
using promoted_real_type = typename policies::evaluation<RealType, Policy>::type;
26+
27+
if (p < tools::min_value<RealType>())
28+
{
29+
return -policies::raise_overflow_error<RealType>("logit", "sub-normals will overflow ln(x/(1-x))", Policy());
30+
}
31+
32+
static const RealType crossover {RealType{1}/4};
33+
const auto promoted_p {static_cast<promoted_real_type>(p)};
34+
RealType result {};
35+
if (p > crossover)
36+
{
37+
result = static_cast<RealType>(2 * atanh(2 * promoted_p - 1));
38+
}
39+
else
40+
{
41+
result = static_cast<RealType>(log(promoted_p / (1 - promoted_p)));
42+
}
43+
44+
return result;
45+
}
46+
47+
template <typename RealType>
48+
RealType logit(RealType p)
49+
{
50+
return logit(p, policies::policy<>());
51+
}
52+
53+
} // namespace math
54+
} // namespace boost
55+
56+
#endif // BOOST_MATH_SF_LOGIT_HPP

test/Jamfile.v2

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,8 @@ test-suite special_fun :
590590
[ run test_sinc.cpp /boost/test//boost_unit_test_framework pch_light ]
591591
[ run test_fibonacci.cpp /boost/test//boost_unit_test_framework ]
592592
[ run test_prime.cpp /boost/test//boost_unit_test_framework ]
593+
[ run test_logistic_sigmoid.cpp ]
594+
[ run test_logit.cpp ]
593595
;
594596

595597
test-suite distribution_tests :

test/git_issue_1294.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2025 Matt Borland
2+
// Use, modification and distribution are subject to the
3+
// Boost Software License, Version 1.0.
4+
// (See accompanying file LICENSE_1_0.txt
5+
// or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// See: https://github.com/boostorg/math/issues/1294
8+
9+
#include <boost/math/distributions/logistic.hpp>
10+
#include "math_unit_test.hpp"
11+
12+
int main()
13+
{
14+
using namespace boost::math::policies;
15+
using boost::math::logistic_distribution;
16+
17+
typedef policy<
18+
promote_float<true>,
19+
promote_double<true>
20+
> with_promotion;
21+
22+
constexpr double p = 2049.0/4096;
23+
constexpr double ref = 9.76562577610225755e-04;
24+
25+
logistic_distribution<double, with_promotion> dist_promote;
26+
const double x = quantile(dist_promote, p);
27+
28+
// Previously we had: 9.76562577610170027e-04
29+
// Which is an ULP distance of 256
30+
CHECK_ULP_CLOSE(x, ref, 1);
31+
32+
return boost::math::test::report_errors();
33+
}

test/math_unit_test.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ bool check_mollified_close(Real expected, Real computed, Real tol, std::string c
5858
}
5959
using std::max;
6060
using std::abs;
61-
Real denom = (max)(abs(expected), Real(1));
61+
Real denom = (max)(Real(abs(expected)), Real(1));
6262
Real mollified_relative_error = abs(expected - computed)/denom;
6363
if (mollified_relative_error > tol)
6464
{

0 commit comments

Comments
 (0)