Use Boost for inverse cumulative Student t distribution#2650
Open
mpwaser wants to merge 1 commit into
Open
Conversation
|
Thanks for opening this pull request! It might take a while before we look at it, so don't worry if there seems to be no feedback. We'll get to it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposal
This improves
InverseCumulativeStudentby replacing the current unbracketed Newton iteration with Boost.Math's Student t quantile implementation.The existing implementation already contains a TODO asking to replace the Newton iteration with a more efficient inverse Student t algorithm:
QuantLib/ql/math/distributions/studenttdistribution.hpp
Lines 75 to 78 in 4567efa
This PR proposes to replace the algorithm with a Boost.Math based implementation:
master: master...mpwaser:feature/student-t-inverse-cdf-boostThe proposed Boost.Math based implementation:
In case you prefer not to delegate this calculation to Boost.Math, I also prepared a native QuantLib-only Brent-based alternative. It is also a clear improvement over the current Newton implementation, though the Boost.Math implementation performs better in the benchmarks below.
master: master...mpwaser:feature/student-t-inverse-cdfThe following tables compare all three implementations in terms of accuracy, robustness, and efficiency.
Benchmark results
Accuracy and robustness
Absolute and relative errors are measured against Boost.Math quantile. Since this PR delegates to Boost.Math, its absolute and relative errors are zero by construction in this comparison. The CDF residual is included as an additional check using QuantLib's cumulative Student t distribution.
Timing (ns/call)
Validation
ql_test_suite --run_test=QuantLibTests/DistributionTests/testInverseCumulativeStudentql_test_suite --run_test=QuantLibTests/DistributionTestsgit diff --checkImplementation note: Endpoint handling
The previous Student t inverse accepted inputs in
[0, 1]. At the exact endpoints, the mathematical inverse is not finite. The old Newton iteration therefore returned finite, accuracy-dependent tail values as an artifact of its stopping criterion.This implementation handles exact and numerically near-endpoint inputs before calling Boost.Math, following the convention already used by QuantLib's
InverseCumulativeNormal::tail_value:0returnQL_MIN_REAL1returnQL_MAX_REAL[0, 1]still throwThis avoids exposing Boost.Math's endpoint overflow behavior while aligning the Student t inverse with an existing QuantLib inverse-distribution convention.
One detail worth noting is that the inherited normal-inverse convention is slightly asymmetric: the upper endpoint is detected with
close_enough(y, 1.0), while the lower endpoint is detected withstd::fabs(y) < QL_EPSILON. I kept that behavior here for consistency withInverseCumulativeNormal.A cleaner alternative would be a symmetric endpoint policy, for instance using the same style of tolerance check at both endpoints. I did not include that broader policy change in this PR in order to keep the change focused on replacing the Student t Newton iteration.
Appendix: Benchmark details
The benchmark was run outside the PR code, against a Release build with
-O3 -DNDEBUG.The implementations compared were:
InverseCumulativeStudentimplementation using Boost.MathFor the QuantLib implementations, the benchmark used
accuracy = 1e-12andmaxIterations = 100.The accuracy grid used degrees of freedom:
1, 2, 3, 4, 5, 10, 30, 100, 1000.The accuracy grid used probabilities:
1e-15, 1e-12, 1e-10, 1e-9, 1e-8, 1e-6, 1e-4, 0.001, 0.01, 0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.99, 0.999, 1-1e-4, 1-1e-6, 1-1e-8, 1-1e-9, 1-1e-10, 1-1e-12, 1-1e-15.In the accuracy table,
Failedmeans that the implementation either threw or returned a non-finite value for the tested input. In this benchmark, the 25 old-Newton failures were all exceptions forn = 1000, caused by non-finite intermediate values in the Newton iteration. This high number of degrees of freedom was included as a stress case to check robustness outside the common parameter range. The large tail errors shown separately are finite results, not counted as failures.Timing used
std::chrono::steady_clock; each row reports nanoseconds per call. A volatile checksum was accumulated to prevent calls from being optimized away. Direct Boost.Math with the distribution object cached per degree of freedom gave very similar timings to this PR implementation, so distribution construction was not a material cost in this benchmark.