Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
89f8dd3
Initial implementation of [skip ci]
JacobHass8 Dec 28, 2025
ed3289d
Added tests and changed namescapes
JacobHass8 Dec 31, 2025
55e2b17
Added boost tuple support
JacobHass8 Jan 2, 2026
b330dd7
Updated code to match non_central_chi_squared syntax
JacobHass8 Jan 2, 2026
2464bcc
Added documentation; changed parameter naming
JacobHass8 Jan 2, 2026
a487eaa
Merge remote-tracking branch 'origin/develop' into non-central-f-inverse
mborland Jan 21, 2026
b794ab7
Add GPU markers and change std::pair to boost::math::pair
mborland Jan 21, 2026
b223789
Add CUDA tests
mborland Jan 21, 2026
e51d71a
Consistency of GPU markers in the docs
mborland Jan 21, 2026
59a4e7a
Added catch for edge case nc=0 and testing for this
JacobHass8 Jan 21, 2026
2d6c1f6
Merge branch 'non-central-f-inverse' of https://github.com/JacobHass8…
jzmaddock Jan 24, 2026
03bfa33
Accepted modifications for nc close to 0
JacobHass8 Jan 27, 2026
d9e3def
Merge branch 'non-central-f-inverse' of https://github.com/JacobHass8…
jzmaddock Jan 30, 2026
1dacef3
Try and tighten up test case.
jzmaddock Jan 30, 2026
a011dc7
Update tolerance for zero non-centrality.
jzmaddock Feb 1, 2026
0e48617
Reverted to checking the relative error for small nc
JacobHass8 Feb 10, 2026
794ce3b
Merge branch 'non-central-f-inverse' of github.com:JacobHass8/math in…
JacobHass8 Feb 10, 2026
02189e7
Changed nc=0 condition
JacobHass8 Feb 10, 2026
e765047
Revert "Changed nc=0 condition"
JacobHass8 Feb 11, 2026
07bf86f
Relaxed constraints on returning nc=0
JacobHass8 Feb 11, 2026
49bd134
Merge branch 'boostorg:develop' into non-central-f-inverse
JacobHass8 Feb 12, 2026
2a9394a
Reverted nc=0 condition and changed relaxed test conditions
JacobHass8 Feb 12, 2026
4b6975b
Removed unnecessary includes
JacobHass8 Feb 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions doc/distributions/nc_f.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
106 changes: 105 additions & 1 deletion include/boost/math/distributions/non_central_f.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

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 RealType p_q_precision, 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)
non_centrality_finder_f<RealType, Policy> f(x, v1, v2, p < q ? p : q, p < q ? false : true);
// This occurs when the root finder would need to find a result smaller than
// tools::min_value (which it cannot do). Note that we have to add in a small
// amount of "tolerance" since the subtraction in our termination condition
// implies a small amount of wobble in the result which should be of the
// order p * eps (note not q * eps, since q is calculated as 1-p).
// Also note that p_q_precision is passed down from our caller as the
// epsilon of the original called values, and not after possible promotion.
if (f(tools::min_value<RealType>()) <= 20 * p_q_precision * p){
return 0;
}

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
{
Expand Down Expand Up @@ -58,6 +118,51 @@ 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),
static_cast<eval_type>(tools::epsilon<RealType>()),
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),
static_cast<eval_type>(tools::epsilon<RealType>()),
forwarding_policy());
return policies::checked_narrowing_cast<RealType, forwarding_policy>(
result,
function);
}
private:
// Data member, initialized by constructor.
RealType v1; // alpha.
Expand Down Expand Up @@ -404,7 +509,6 @@ namespace boost
Policy());
return (x / (1 - x)) * (c.dist.degrees_of_freedom2() / c.dist.degrees_of_freedom1());
} // quantile complement.

} // namespace math
} // namespace boost

Expand Down
2 changes: 2 additions & 0 deletions test/cuda_jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 ;
Expand Down
62 changes: 54 additions & 8 deletions test/test_nc_f.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -155,12 +159,10 @@ void test_spot(
}

template <class RealType> // Any floating-point type RealType.
void test_spots(RealType)
void test_spots(RealType, const char* name = nullptr)
{
RealType tolerance = boost::math::tools::epsilon<RealType>() * 10000;

cout << "Tolerance = " << (tolerance / 100) << "%." << endl;

std::cout << "Testing spot values with type " << name << " (Tolerance = " << (tolerance / 100) << "%)." << std::endl;
//
// Spot tests from Mathematica computed values:
//
Expand Down Expand Up @@ -323,6 +325,50 @@ 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 = cdf(dist_no_centrality, x);
BOOST_CHECK_LE(dist.find_non_centrality(x, a, b, P), 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);
//
// Test non centrality finder over a grid of values:
//
RealType values[] = { 1.25, 3.5, 6.75, 8.25 };
for (RealType v1 : values)
{
for (RealType v2 : values)
{
for (RealType nc : values)
{
for (RealType x : values)
{
boost::math::non_central_f_distribution<RealType> ref(v1, v2, nc);
RealType P = cdf(ref, x);
RealType Q = cdf(complement(ref, x));

RealType nc1 = ref.find_non_centrality(x, v1, v2, P);
RealType nc2 = ref.find_non_centrality(boost::math::complement(x, v1, v2, Q));

BOOST_CHECK_CLOSE(nc1, nc, 2 * tolerance);
BOOST_CHECK_CLOSE(nc2, nc, tolerance);
}
}
}
}
} // template <class RealType>void test_spots(RealType)

BOOST_AUTO_TEST_CASE( test_main )
Expand All @@ -331,12 +377,12 @@ BOOST_AUTO_TEST_CASE( test_main )
// Basic sanity-check spot values.
expected_results();
// (Parameter value, arbitrarily zero, only communicates the floating point type).
test_spots(0.0F); // Test float.
test_spots(0.0); // Test double.
test_spots(0.0F, "float"); // Test float.
test_spots(0.0, "double"); // Test double.
#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
test_spots(0.0L); // Test long double.
test_spots(0.0L, "long double"); // Test long double.
#if !BOOST_WORKAROUND(BOOST_BORLANDC, BOOST_TESTED_AT(0x582)) && !defined(BOOST_MATH_NO_REAL_CONCEPT_TESTS)
test_spots(boost::math::concepts::real_concept(0.)); // Test real concept.
test_spots(boost::math::concepts::real_concept(0.), "real_concept"); // Test real concept.
#endif
#endif

Expand Down
114 changes: 114 additions & 0 deletions test/test_nc_f_finder_double.cu
Original file line number Diff line number Diff line change
@@ -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;
}
Loading