2
0
mirror of https://github.com/boostorg/math.git synced 2026-02-24 16:12:15 +00:00

Merge branch 'non-central-f-inverse' of https://github.com/JacobHass8/math into pr1345

This commit is contained in:
John Maddock
2026-01-24 13:00:17 +00:00
6 changed files with 375 additions and 1 deletions

View File

@@ -26,6 +26,11 @@
// Accessor to non-centrality parameter lambda:
BOOST_MATH_GPU_ENABLED RealType non_centrality()const;
// Parameter finders:
BOOST_MATH_GPU_ENABLED static RealType find_non_centrality(const RealType x, const RealType v1, const RealType v2, const RealType p);
template <class A, class B, class C, class D>
BOOST_MATH_GPU_ENABLED static RealType find_non_centrality(const complemented3_type<A,B,C,D>& c);
};
}} // namespaces
@@ -53,6 +58,20 @@ for different values of [lambda]:
[graph nc_f_pdf]
BOOST_MATH_GPU_ENABLED static RealType find_non_centrality(const RealType x, const RealType v1, const RealType v2, const RealType p);
This function returns the non centrality parameter /lambda/ such that:
`cdf(non_central_chi_squared<RealType, Policy>(v1, v2, lambda), x) == p`
template <class A, class B, class C, class D>
BOOST_MATH_GPU_ENABLED static RealType find_non_centrality(const complemented4_type<A,B,C,D>& c);
When called with argument `boost::math::complement(x, v1, v2, q)`
this function returns the non centrality parameter /lambda/ such that:
`cdf(complement(non_central_chi_squared<RealType, Policy>(v1, v2, lambda), x)) == q`.
[h4 Member Functions]
BOOST_MATH_GPU_ENABLED non_central_f_distribution(RealType v1, RealType v2, RealType lambda);

View File

@@ -17,11 +17,71 @@
#include <boost/math/distributions/detail/generic_mode.hpp>
#include <boost/math/special_functions/pow.hpp>
#include <boost/math/policies/policy.hpp>
#include <boost/math/distributions/complement.hpp> // complements
#include <boost/math/distributions/fisher_f.hpp>
#include <boost/math/special_functions/relative_difference.hpp>
namespace boost
{
namespace math
{
namespace detail
{
template <class RealType, class Policy>
struct non_centrality_finder_f
{
BOOST_MATH_GPU_ENABLED non_centrality_finder_f(const RealType x_, const RealType v1_, const RealType v2_, const RealType p_, const bool c)
: x(x_), v1(v1_), v2(v2_), p(p_), comp(c) {}
BOOST_MATH_GPU_ENABLED RealType operator()(RealType nc) const
{
non_central_f_distribution<RealType, Policy> d(v1, v2, nc);
return comp ?
RealType(p - cdf(complement(d, x)))
: RealType(cdf(d, x) - p);
}
private:
RealType x, v1, v2, p;
bool comp;
};
template <class RealType, class Policy>
BOOST_MATH_GPU_ENABLED RealType find_non_centrality_f(const RealType x, const RealType v1, const RealType v2, const RealType p, const RealType q, const Policy& pol)
{
constexpr auto function = "non_central_f<%1%>::find_non_centrality";
if ( p == 0 || q == 0) {
return policies::raise_domain_error<RealType>(function, "Can't find non centrality parameter when the probability is <=0 or >=1, only possible answer is %1%", // LCOV_EXCL_LINE
RealType(boost::math::numeric_limits<RealType>::quiet_NaN()), Policy()); // LCOV_EXCL_LINE
}
// Check if nc = 0 (which is just the F-distribution)
// I first tried comparing to boost::math::tools::epsilon<RealType>(),
// but this was never satisfied for floats or doubles. Only
// long doubles would correctly return 0.
fisher_f_distribution<RealType, Policy> dist(v1, v2);
if (boost::math::relative_difference(pdf(dist, x), p) < 1e-7){
return 0;
}
non_centrality_finder_f<RealType, Policy> f(x, v1, v2, p < q ? p : q, p < q ? false : true);
RealType guess = RealType(10); // Starting guess.
RealType factor = RealType(2); // How big steps to take when searching.
boost::math::uintmax_t max_iter = policies::get_max_root_iterations<Policy>();
tools::eps_tolerance<RealType> tol(policies::digits<RealType, Policy>());
boost::math::pair<RealType, RealType> result_bracket = tools::bracket_and_solve_root(
f, guess, factor, false, tol, max_iter, pol);
RealType result = result_bracket.first + (result_bracket.second - result_bracket.first)/2;
if (max_iter >= policies::get_max_root_iterations<Policy>()) {
return policies::raise_evaluation_error<RealType>(function, "Unable to locate solution in a reasonable time:" // LCOV_EXCL_LINE
" or there is no answer to problem. Current best guess is %1%", result, Policy()); // LCOV_EXCL_LINE
}
return result;
}
} // namespace detail
template <class RealType = double, class Policy = policies::policy<> >
class non_central_f_distribution
{
@@ -58,6 +118,49 @@ namespace boost
{ // Private data getter function.
return ncp;
}
BOOST_MATH_GPU_ENABLED static RealType find_non_centrality(const RealType x, const RealType v1, const RealType v2, const RealType p)
{
constexpr auto function = "non_central_f_distribution<%1%>::find_non_centrality";
typedef typename policies::evaluation<RealType, Policy>::type eval_type;
typedef typename policies::normalise<
Policy,
policies::promote_float<false>,
policies::promote_double<false>,
policies::discrete_quantile<>,
policies::assert_undefined<> >::type forwarding_policy;
eval_type result = detail::find_non_centrality_f(
static_cast<eval_type>(x),
static_cast<eval_type>(v1),
static_cast<eval_type>(v2),
static_cast<eval_type>(p),
static_cast<eval_type>(1-p),
forwarding_policy());
return policies::checked_narrowing_cast<RealType, forwarding_policy>(
result,
function);
}
template <class A, class B, class C, class D>
BOOST_MATH_GPU_ENABLED static RealType find_non_centrality(const complemented4_type<A,B,C, D>& c)
{
constexpr auto function = "non_central_f_distribution<%1%>::find_non_centrality";
typedef typename policies::evaluation<RealType, Policy>::type eval_type;
typedef typename policies::normalise<
Policy,
policies::promote_float<false>,
policies::promote_double<false>,
policies::discrete_quantile<>,
policies::assert_undefined<> >::type forwarding_policy;
eval_type result = detail::find_non_centrality_f(
static_cast<eval_type>(c.dist),
static_cast<eval_type>(c.param1),
static_cast<eval_type>(c.param2),
static_cast<eval_type>(1-c.param3),
static_cast<eval_type>(c.param3),
forwarding_policy());
return policies::checked_narrowing_cast<RealType, forwarding_policy>(
result,
function);
}
private:
// Data member, initialized by constructor.
RealType v1; // alpha.
@@ -404,7 +507,6 @@ namespace boost
Policy());
return (x / (1 - x)) * (c.dist.degrees_of_freedom2() / c.dist.degrees_of_freedom1());
} // quantile complement.
} // namespace math
} // namespace boost

View File

@@ -165,6 +165,8 @@ run test_nc_f_pdf_double.cu ;
run test_nc_f_pdf_float.cu ;
run test_nc_f_quan_double.cu ;
run test_nc_f_quan_float.cu ;
run test_nc_f_finder_double.cu ;
run test_nc_f_finder_float.cu ;
run test_nc_chi_squared_cdf_double.cu ;
run test_nc_chi_squared_cdf_float.cu ;

View File

@@ -141,6 +141,10 @@ void test_spot(
quantile(dist, P), x, tol * 10);
BOOST_CHECK_CLOSE(
quantile(complement(dist, Q)), x, tol * 10);
BOOST_CHECK_CLOSE(
dist.find_non_centrality(x, a, b, P), ncp, tol * 10);
BOOST_CHECK_CLOSE(
dist.find_non_centrality(boost::math::complement(x, a, b, Q)), ncp, tol * 10);
}
if(boost::math::tools::digits<RealType>() > 50)
{
@@ -323,6 +327,25 @@ void test_spots(RealType)
{
BOOST_CHECK_CLOSE(cdf(boost::math::non_central_f_distribution<RealType>(static_cast<RealType>(1e-100L), 3.f, 1.5f), static_cast<RealType>(1e100L)), static_cast<RealType>(0.6118152873453990639132215575213809716459L), tolerance);
}
// Check find_non_centrality_f edge case handling
// Case when nc=0
RealType a = 5;
RealType b = 2;
RealType nc = 0;
RealType x_vals[] = { 0.25, 1.25, 10, 100};
boost::math::non_central_f_distribution<RealType> dist_no_centrality(a, b, nc);
for (RealType x : x_vals)
{
RealType P = pdf(dist_no_centrality, x);
BOOST_CHECK_CLOSE(dist.find_non_centrality(x, a, b, P), static_cast<RealType>(0), tolerance);
}
// Case when P=1 or P=0
BOOST_MATH_CHECK_THROW(dist.find_non_centrality(x, a, b, 1), std::domain_error);
BOOST_MATH_CHECK_THROW(dist.find_non_centrality(x, a, b, 0), std::domain_error);
// Case when Q=1 or Q=0
BOOST_MATH_CHECK_THROW(dist.find_non_centrality(boost::math::complement(x, a, b, 1)), std::domain_error);
BOOST_MATH_CHECK_THROW(dist.find_non_centrality(boost::math::complement(x, a, b, 0)), std::domain_error);
} // template <class RealType>void test_spots(RealType)
BOOST_AUTO_TEST_CASE( test_main )

View File

@@ -0,0 +1,114 @@
// Copyright John Maddock 2016.
// Copyright Matt Borland 2024.
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#define BOOST_MATH_OVERFLOW_ERROR_POLICY ignore_error
#include <iostream>
#include <iomanip>
#include <vector>
#include <boost/math/distributions/non_central_f.hpp>
#include <boost/math/special_functions/relative_difference.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include "cuda_managed_ptr.hpp"
#include "stopwatch.hpp"
// For the CUDA runtime routines (prefixed with "cuda_")
#include <cuda_runtime.h>
typedef double float_type;
/**
* CUDA Kernel Device code
*
*/
__global__ void cuda_test(const float_type *in1, float_type *out, int numElements)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < numElements)
{
boost::math::non_central_f_distribution<float_type> dist(5, 5, 5);
out[i] = dist.find_non_centrality(5, 5, 5, in1[i]);
}
}
/**
* Host main routine
*/
int main(void)
{
try{
// Error code to check return values for CUDA calls
cudaError_t err = cudaSuccess;
// Print the vector length to be used, and compute its size
int numElements = 50000;
std::cout << "[Vector operation on " << numElements << " elements]" << std::endl;
// Allocate the managed input vector A
cuda_managed_ptr<float_type> input_vector1(numElements);
// Allocate the managed output vector C
cuda_managed_ptr<float_type> output_vector(numElements);
boost::random::mt19937 gen;
boost::random::uniform_real_distribution<float_type> dist(0.1, 0.9);
// Initialize the input vectors
for (int i = 0; i < numElements; ++i)
{
input_vector1[i] = dist(gen);
}
// Launch the Vector Add CUDA Kernel
int threadsPerBlock = 256;
int blocksPerGrid =(numElements + threadsPerBlock - 1) / threadsPerBlock;
std::cout << "CUDA kernel launch with " << blocksPerGrid << " blocks of " << threadsPerBlock << " threads" << std::endl;
watch w;
cuda_test<<<blocksPerGrid, threadsPerBlock>>>(input_vector1.get(), output_vector.get(), numElements);
cudaDeviceSynchronize();
std::cout << "CUDA kernal done in " << w.elapsed() << "s" << std::endl;
err = cudaGetLastError();
if (err != cudaSuccess)
{
std::cerr << "Failed to launch non_central_f distribution kernel (error code " << cudaGetErrorString(err) << ")!" << std::endl;
return EXIT_FAILURE;
}
// Verify that the result vector is correct
std::vector<float_type> results;
results.reserve(numElements);
w.reset();
boost::math::non_central_f_distribution<float_type> non_central_dist(5, 5, 5);
for(int i = 0; i < numElements; ++i)
{
results.push_back(non_central_dist.find_non_centrality(5, 5, 5, input_vector1[i]));
}
double t = w.elapsed();
// check the results
for(int i = 0; i < numElements; ++i)
{
if (boost::math::epsilon_difference(output_vector[i], results[i]) > 100.0)
{
std::cerr << "Result verification failed at element " << i << "!" << std::endl;
std::cerr << "Error rate was: " << boost::math::epsilon_difference(output_vector[i], results[i]) << "eps" << std::endl;
return EXIT_FAILURE;
}
}
std::cout << "Test PASSED with calculation time: " << t << "s" << std::endl;
std::cout << "Done\n";
}
catch(const std::exception& e)
{
std::cerr << "Stopped with exception: " << e.what() << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,114 @@
// Copyright John Maddock 2016.
// Copyright Matt Borland 2024.
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#define BOOST_MATH_OVERFLOW_ERROR_POLICY ignore_error
#include <iostream>
#include <iomanip>
#include <vector>
#include <boost/math/distributions/non_central_f.hpp>
#include <boost/math/special_functions/relative_difference.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include "cuda_managed_ptr.hpp"
#include "stopwatch.hpp"
// For the CUDA runtime routines (prefixed with "cuda_")
#include <cuda_runtime.h>
typedef float float_type;
/**
* CUDA Kernel Device code
*
*/
__global__ void cuda_test(const float_type *in1, float_type *out, int numElements)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < numElements)
{
boost::math::non_central_f_distribution<float_type> dist(5, 5, 5);
out[i] = dist.find_non_centrality(5, 5, 5, in1[i]);
}
}
/**
* Host main routine
*/
int main(void)
{
try{
// Error code to check return values for CUDA calls
cudaError_t err = cudaSuccess;
// Print the vector length to be used, and compute its size
int numElements = 50000;
std::cout << "[Vector operation on " << numElements << " elements]" << std::endl;
// Allocate the managed input vector A
cuda_managed_ptr<float_type> input_vector1(numElements);
// Allocate the managed output vector C
cuda_managed_ptr<float_type> output_vector(numElements);
boost::random::mt19937 gen;
boost::random::uniform_real_distribution<float_type> dist(0.1, 0.9);
// Initialize the input vectors
for (int i = 0; i < numElements; ++i)
{
input_vector1[i] = dist(gen);
}
// Launch the Vector Add CUDA Kernel
int threadsPerBlock = 256;
int blocksPerGrid =(numElements + threadsPerBlock - 1) / threadsPerBlock;
std::cout << "CUDA kernel launch with " << blocksPerGrid << " blocks of " << threadsPerBlock << " threads" << std::endl;
watch w;
cuda_test<<<blocksPerGrid, threadsPerBlock>>>(input_vector1.get(), output_vector.get(), numElements);
cudaDeviceSynchronize();
std::cout << "CUDA kernal done in " << w.elapsed() << "s" << std::endl;
err = cudaGetLastError();
if (err != cudaSuccess)
{
std::cerr << "Failed to launch non_central_f distribution kernel (error code " << cudaGetErrorString(err) << ")!" << std::endl;
return EXIT_FAILURE;
}
// Verify that the result vector is correct
std::vector<float_type> results;
results.reserve(numElements);
w.reset();
boost::math::non_central_f_distribution<float_type> non_central_dist(5, 5, 5);
for(int i = 0; i < numElements; ++i)
{
results.push_back(non_central_dist.find_non_centrality(5, 5, 5, input_vector1[i]));
}
double t = w.elapsed();
// check the results
for(int i = 0; i < numElements; ++i)
{
if (boost::math::epsilon_difference(output_vector[i], results[i]) > 100.0)
{
std::cerr << "Result verification failed at element " << i << "!" << std::endl;
std::cerr << "Error rate was: " << boost::math::epsilon_difference(output_vector[i], results[i]) << "eps" << std::endl;
return EXIT_FAILURE;
}
}
std::cout << "Test PASSED with calculation time: " << t << "s" << std::endl;
std::cout << "Done\n";
}
catch(const std::exception& e)
{
std::cerr << "Stopped with exception: " << e.what() << std::endl;
}
return 0;
}