Skip to content

Commit e78785c

Browse files
author
Hendrik Muhs
committed
[ML] Refactor code to use std::lgamma instead of boost::math::lgamma (#132)
Migrate remaining codebase to std::lgamma, std::{isfinite,isinf,isnan}andstd::erf` fixes #128
1 parent 3d394c6 commit e78785c

19 files changed

+260
-263
lines changed

include/core/CRapidJsonWriterBase.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020
#include <rapidjson/writer.h>
2121

2222
#include <boost/iterator/indirect_iterator.hpp>
23-
#include <boost/math/special_functions/fpclassify.hpp>
2423
#include <boost/unordered/unordered_map.hpp>
2524
#include <boost/unordered/unordered_set.hpp>
2625

26+
#include <cmath>
2727
#include <memory>
2828
#include <stack>
2929

@@ -166,7 +166,7 @@ class CRapidJsonWriterBase
166166

167167
bool Double(double d) {
168168
// rewrite NaN and Infinity to 0
169-
if (!(boost::math::isfinite)(d)) {
169+
if (std::isfinite(d) == false) {
170170
return TRapidJsonWriterBase::Int(0);
171171
}
172172

@@ -355,7 +355,7 @@ class CRapidJsonWriterBase
355355
//! Adds a double field with the name fieldname to an object.
356356
//! \p fieldName must outlive \p obj or memory corruption will occur.
357357
void addDoubleFieldToObj(const std::string& fieldName, double value, TValue& obj) const {
358-
if (!(boost::math::isfinite)(value)) {
358+
if (std::isfinite(value) == false) {
359359
LOG_ERROR(<< "Adding " << value << " to the \"" << fieldName
360360
<< "\" field of a JSON document");
361361
// Don't return - make a best effort to add the value
@@ -489,7 +489,7 @@ class CRapidJsonWriterBase
489489
//! Log a message if we're trying to add nan/infinity to a JSON array
490490
template<typename NUMBER>
491491
void checkArrayNumberFinite(NUMBER val, const std::string& fieldName, bool& considerLogging) const {
492-
if (considerLogging && !(boost::math::isfinite)(val)) {
492+
if (considerLogging && (std::isfinite(val) == false)) {
493493
LOG_ERROR(<< "Adding " << val << " to the \"" << fieldName
494494
<< "\" array in a JSON document");
495495
// Don't return - make a best effort to add the value

include/maths/CMathsFuncs.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ namespace maths {
3838
//!
3939
class MATHS_EXPORT CMathsFuncs : private core::CNonInstantiatable {
4040
public:
41-
//! Wrapper around boost::math::isnan() which avoids the need to add
42-
//! cryptic brackets everywhere to deal with macros.
41+
//! Check if value is NaN.
4342
static bool isNan(double val);
4443
//! Check if any of the components are NaN.
4544
template<std::size_t N>
@@ -54,8 +53,7 @@ class MATHS_EXPORT CMathsFuncs : private core::CNonInstantiatable {
5453
//! Check if an element is NaN.
5554
static bool isNan(const core::CSmallVectorBase<double>& val);
5655

57-
//! Wrapper around boost::math::isinf() which avoids the need to add
58-
//! cryptic brackets everywhere to deal with macros.
56+
//! Check if value is infinite.
5957
static bool isInf(double val);
6058
//! Check if any of the components are infinite.
6159
template<std::size_t N>

include/maths/CMultivariateNormalConjugate.h

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@
2828
#include <maths/CNormalMeanPrecConjugate.h>
2929
#include <maths/CRestoreParams.h>
3030
#include <maths/CSampling.h>
31+
#include <maths/CTools.h>
3132
#include <maths/ProbabilityAggregators.h>
3233

3334
#include <boost/make_unique.hpp>
3435
#include <boost/math/special_functions/beta.hpp>
35-
#include <boost/math/special_functions/gamma.hpp>
3636
#include <boost/numeric/conversion/bounds.hpp>
3737
#include <boost/optional.hpp>
3838

@@ -1079,41 +1079,42 @@ class CMultivariateNormalConjugate : public CMultivariatePrior {
10791079
LOG_ERROR(<< "Failed to calculate log det " << wishartScaleMatrixPost);
10801080
return maths_t::E_FpFailed;
10811081
}
1082+
double logGammaPost = 0.0;
1083+
double logGammaPrior = 0.0;
1084+
double logGammaPostMinusPrior = 0.0;
10821085

1083-
try {
1084-
double logGammaPostMinusPrior = 0.0;
1085-
for (std::size_t i = 0u; i < N; ++i) {
1086-
logGammaPostMinusPrior +=
1087-
boost::math::lgamma(
1088-
0.5 * (wishartDegreesFreedomPost - static_cast<double>(i))) -
1089-
boost::math::lgamma(0.5 * (wishartDegreesFreedomPrior -
1090-
static_cast<double>(i)));
1086+
for (std::size_t i = 0u; i < N; ++i) {
1087+
if (CTools::lgamma(0.5 * (wishartDegreesFreedomPost - static_cast<double>(i)),
1088+
logGammaPost, true) &&
1089+
CTools::lgamma(0.5 * (wishartDegreesFreedomPrior - static_cast<double>(i)),
1090+
logGammaPrior, true)) {
1091+
1092+
logGammaPostMinusPrior += logGammaPost - logGammaPrior;
1093+
} else {
1094+
return maths_t::E_FpOverflowed;
10911095
}
1092-
LOG_TRACE(<< "numberSamples = " << numberSamples);
1093-
LOG_TRACE(<< "logGaussianPrecisionPrior = " << logGaussianPrecisionPrior
1094-
<< ", logGaussianPrecisionPost = " << logGaussianPrecisionPost);
1095-
LOG_TRACE(<< "wishartDegreesFreedomPrior = " << wishartDegreesFreedomPrior
1096-
<< ", wishartDegreesFreedomPost = " << wishartDegreesFreedomPost);
1097-
LOG_TRACE(<< "wishartScaleMatrixPrior = " << m_WishartScaleMatrix);
1098-
LOG_TRACE(<< "wishartScaleMatrixPost = " << wishartScaleMatrixPost);
1099-
LOG_TRACE(<< "logDeterminantPrior = " << logDeterminantPrior
1100-
<< ", logDeterminantPost = " << logDeterminantPost);
1101-
LOG_TRACE(<< "logGammaPostMinusPrior = " << logGammaPostMinusPrior);
1102-
LOG_TRACE(<< "logCountVarianceScales = " << logCountVarianceScales);
1103-
1104-
double d = static_cast<double>(N);
1105-
result = 0.5 * (wishartDegreesFreedomPrior * logDeterminantPrior -
1106-
wishartDegreesFreedomPost * logDeterminantPost -
1107-
d * (logGaussianPrecisionPost - logGaussianPrecisionPrior) +
1108-
(wishartDegreesFreedomPost - wishartDegreesFreedomPrior) *
1109-
d * core::constants::LOG_TWO +
1110-
2.0 * logGammaPostMinusPrior -
1111-
numberSamples * d * core::constants::LOG_TWO_PI -
1112-
logCountVarianceScales);
1113-
} catch (const std::exception& e) {
1114-
LOG_ERROR(<< "Failed to calculate marginal likelihood: " << e.what());
1115-
return maths_t::E_FpFailed;
11161096
}
1097+
LOG_TRACE(<< "numberSamples = " << numberSamples);
1098+
LOG_TRACE(<< "logGaussianPrecisionPrior = " << logGaussianPrecisionPrior
1099+
<< ", logGaussianPrecisionPost = " << logGaussianPrecisionPost);
1100+
LOG_TRACE(<< "wishartDegreesFreedomPrior = " << wishartDegreesFreedomPrior
1101+
<< ", wishartDegreesFreedomPost = " << wishartDegreesFreedomPost);
1102+
LOG_TRACE(<< "wishartScaleMatrixPrior = " << m_WishartScaleMatrix);
1103+
LOG_TRACE(<< "wishartScaleMatrixPost = " << wishartScaleMatrixPost);
1104+
LOG_TRACE(<< "logDeterminantPrior = " << logDeterminantPrior
1105+
<< ", logDeterminantPost = " << logDeterminantPost);
1106+
LOG_TRACE(<< "logGammaPostMinusPrior = " << logGammaPostMinusPrior);
1107+
LOG_TRACE(<< "logCountVarianceScales = " << logCountVarianceScales);
1108+
1109+
double d = static_cast<double>(N);
1110+
result = 0.5 * (wishartDegreesFreedomPrior * logDeterminantPrior -
1111+
wishartDegreesFreedomPost * logDeterminantPost -
1112+
d * (logGaussianPrecisionPost - logGaussianPrecisionPrior) +
1113+
(wishartDegreesFreedomPost - wishartDegreesFreedomPrior) *
1114+
d * core::constants::LOG_TWO +
1115+
2.0 * logGammaPostMinusPrior -
1116+
numberSamples * d * core::constants::LOG_TWO_PI - logCountVarianceScales);
1117+
11171118
return static_cast<maths_t::EFloatingPointErrorStatus>(CMathsFuncs::fpStatus(result));
11181119
}
11191120

include/maths/CTools.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ class MATHS_EXPORT CTools : private core::CNonInstantiatable {
695695
static double logOneMinusX(double x);
696696

697697
//! A wrapper around lgamma which handles corner cases if requested
698-
static bool lgamma(double value, double& result, bool checkForFinite = false);
698+
static bool lgamma(double value, double& result, bool checkForFinite = true);
699699
};
700700
}
701701
}

lib/maths/CCategoricalTools.cc

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
#include <maths/MathsTypes.h>
1515

1616
#include <boost/math/distributions/binomial.hpp>
17-
#include <boost/math/special_functions/gamma.hpp>
1817
#include <boost/numeric/conversion/bounds.hpp>
1918

2019
#include <cmath>
@@ -408,8 +407,7 @@ double CCategoricalTools::logBinomialCoefficient(std::size_t n, std::size_t m) {
408407
}
409408
double n_ = static_cast<double>(n);
410409
double m_ = static_cast<double>(m);
411-
return boost::math::lgamma(n_ + 1.0) - boost::math::lgamma(m_ + 1.0) -
412-
boost::math::lgamma(n_ - m_ + 1.0);
410+
return std::lgamma(n_ + 1.0) - std::lgamma(m_ + 1.0) - std::lgamma(n_ - m_ + 1.0);
413411
}
414412

415413
double CCategoricalTools::binomialCoefficient(std::size_t n, std::size_t m) {
@@ -544,8 +542,19 @@ CCategoricalTools::logBinomialProbability(std::size_t n, double p, std::size_t m
544542

545543
double n_ = static_cast<double>(n);
546544
double m_ = static_cast<double>(m);
547-
result = std::min(boost::math::lgamma(n_ + 1.0) - boost::math::lgamma(m_ + 1.0) -
548-
boost::math::lgamma(n_ - m_ + 1.0) +
545+
546+
double logGammaNPlusOne = 0.0;
547+
double logGammaMPlusOne = 0.0;
548+
double logGammaNMinusMPlusOne = 0.0;
549+
550+
if ((CTools::lgamma(n_ + 1.0, logGammaNPlusOne, true) &&
551+
CTools::lgamma(m_ + 1.0, logGammaMPlusOne, true) &&
552+
CTools::lgamma(n_ - m_ + 1.0, logGammaNMinusMPlusOne, true)) == false) {
553+
554+
return maths_t::E_FpOverflowed;
555+
}
556+
557+
result = std::min(logGammaNPlusOne - logGammaMPlusOne - logGammaNMinusMPlusOne +
549558
m_ * std::log(p) + (n_ - m_) * std::log(1.0 - p),
550559
0.0);
551560
return maths_t::E_FpNoErrors;
@@ -570,7 +579,11 @@ CCategoricalTools::logMultinomialProbability(const TDoubleVec& probabilities,
570579
}
571580

572581
double n_ = static_cast<double>(n);
573-
double logP = boost::math::lgamma(n_ + 1.0);
582+
double logP = 0.0;
583+
584+
if (CTools::lgamma(n_ + 1.0, logP, true) == false) {
585+
return maths_t::E_FpOverflowed;
586+
}
574587

575588
for (std::size_t i = 0u; i < ni.size(); ++i) {
576589
double ni_ = static_cast<double>(ni[i]);
@@ -584,7 +597,13 @@ CCategoricalTools::logMultinomialProbability(const TDoubleVec& probabilities,
584597
result = boost::numeric::bounds<double>::lowest();
585598
return maths_t::E_FpOverflowed;
586599
}
587-
logP += ni_ * std::log(pi_) - boost::math::lgamma(ni_ + 1.0);
600+
601+
double logGammaNiPlusOne = 0.0;
602+
if (CTools::lgamma(ni_ + 1.0, logGammaNiPlusOne, true) == false) {
603+
return maths_t::E_FpOverflowed;
604+
}
605+
606+
logP += ni_ * std::log(pi_) - logGammaNiPlusOne;
588607
}
589608
}
590609

lib/maths/CGammaRateConjugate.cc

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
#include <boost/math/distributions/beta.hpp>
2828
#include <boost/math/distributions/gamma.hpp>
2929
#include <boost/math/special_functions/digamma.hpp>
30-
#include <boost/math/special_functions/gamma.hpp>
3130
#include <boost/math/tools/roots.hpp>
3231
#include <boost/numeric/conversion/bounds.hpp>
3332

@@ -637,35 +636,37 @@ class CLogMarginalLikelihood : core::CNonCopyable {
637636
double logGammaScaledLikelihoodShape = 0.0;
638637
double scaledImpliedShape = 0.0;
639638

640-
try {
641-
for (std::size_t i = 0u; i < m_Weights.size(); ++i) {
642-
double n = maths_t::countForUpdate(m_Weights[i]);
643-
double varianceScale = maths_t::seasonalVarianceScale(m_Weights[i]) *
644-
maths_t::countVarianceScale(m_Weights[i]);
645-
m_NumberSamples += n;
646-
if (varianceScale != 1.0) {
647-
logVarianceScaleSum -= m_LikelihoodShape / varianceScale *
648-
std::log(varianceScale);
649-
logGammaScaledLikelihoodShape +=
650-
n * boost::math::lgamma(m_LikelihoodShape / varianceScale);
651-
scaledImpliedShape += n * m_LikelihoodShape / varianceScale;
652-
} else {
653-
nResidual += n;
654-
}
639+
for (std::size_t i = 0u; i < m_Weights.size(); ++i) {
640+
double n = maths_t::countForUpdate(m_Weights[i]);
641+
double varianceScale = maths_t::seasonalVarianceScale(m_Weights[i]) *
642+
maths_t::countVarianceScale(m_Weights[i]);
643+
m_NumberSamples += n;
644+
if (varianceScale != 1.0) {
645+
logVarianceScaleSum -= m_LikelihoodShape / varianceScale *
646+
std::log(varianceScale);
647+
logGammaScaledLikelihoodShape +=
648+
n * std::lgamma(m_LikelihoodShape / varianceScale);
649+
scaledImpliedShape += n * m_LikelihoodShape / varianceScale;
650+
} else {
651+
nResidual += n;
655652
}
653+
}
656654

657-
m_ImpliedShape = scaledImpliedShape + nResidual * m_LikelihoodShape + m_PriorShape;
655+
m_ImpliedShape = scaledImpliedShape + nResidual * m_LikelihoodShape + m_PriorShape;
658656

659-
LOG_TRACE(<< "numberSamples = " << m_NumberSamples);
657+
LOG_TRACE(<< "numberSamples = " << m_NumberSamples);
660658

661-
m_Constant = m_PriorShape * std::log(m_PriorRate) -
662-
boost::math::lgamma(m_PriorShape) +
663-
logVarianceScaleSum - logGammaScaledLikelihoodShape -
664-
nResidual * boost::math::lgamma(m_LikelihoodShape) +
665-
boost::math::lgamma(m_ImpliedShape);
666-
} catch (const std::exception& e) {
667-
LOG_ERROR(<< "Error calculating marginal likelihood: " << e.what());
659+
m_Constant = m_PriorShape * std::log(m_PriorRate) - std::lgamma(m_PriorShape) +
660+
logVarianceScaleSum - logGammaScaledLikelihoodShape -
661+
nResidual * std::lgamma(m_LikelihoodShape) +
662+
std::lgamma(m_ImpliedShape);
663+
664+
if (std::isnan(m_ImpliedShape) || std::isnan(m_Constant)) {
665+
LOG_ERROR(<< "Error calculating marginal likelihood: floating point nan");
668666
this->addErrorStatus(maths_t::E_FpFailed);
667+
} else if (std::isinf(m_ImpliedShape) || std::isinf(m_Constant)) {
668+
LOG_ERROR(<< "Error calculating marginal likelihood: floating point overflow");
669+
this->addErrorStatus(maths_t::E_FpOverflowed);
669670
}
670671
}
671672

lib/maths/CLogNormalMeanPrecConjugate.cc

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
#include <boost/math/distributions/lognormal.hpp>
3131
#include <boost/math/distributions/normal.hpp>
3232
#include <boost/math/distributions/students_t.hpp>
33-
#include <boost/math/special_functions/gamma.hpp>
3433
#include <boost/numeric/conversion/bounds.hpp>
3534

3635
#include <algorithm>
@@ -487,49 +486,51 @@ class CLogMarginalLikelihood : core::CNonCopyable {
487486
private:
488487
//! Compute all the constants in the integrand.
489488
void precompute() {
490-
try {
491-
double logVarianceScaleSum = 0.0;
492-
493-
if (maths_t::hasSeasonalVarianceScale(m_Weights) ||
494-
maths_t::hasCountVarianceScale(m_Weights)) {
495-
m_Scales.reserve(m_Weights.size());
496-
double r = m_Rate / m_Shape;
497-
double s = std::exp(-r);
498-
for (std::size_t i = 0u; i < m_Weights.size(); ++i) {
499-
double varianceScale = maths_t::seasonalVarianceScale(m_Weights[i]) *
500-
maths_t::countVarianceScale(m_Weights[i]);
501-
502-
// Get the scale and shift of the exponentiated Gaussian.
503-
if (varianceScale == 1.0) {
504-
m_Scales.emplace_back(1.0, 0.0);
505-
} else {
506-
double t = r + std::log(s + varianceScale * (1.0 - s));
507-
m_Scales.emplace_back(t / r, 0.5 * (r - t));
508-
logVarianceScaleSum += std::log(t / r);
509-
}
489+
double logVarianceScaleSum = 0.0;
490+
491+
if (maths_t::hasSeasonalVarianceScale(m_Weights) ||
492+
maths_t::hasCountVarianceScale(m_Weights)) {
493+
m_Scales.reserve(m_Weights.size());
494+
double r = m_Rate / m_Shape;
495+
double s = std::exp(-r);
496+
for (std::size_t i = 0u; i < m_Weights.size(); ++i) {
497+
double varianceScale = maths_t::seasonalVarianceScale(m_Weights[i]) *
498+
maths_t::countVarianceScale(m_Weights[i]);
499+
500+
// Get the scale and shift of the exponentiated Gaussian.
501+
if (varianceScale == 1.0) {
502+
m_Scales.emplace_back(1.0, 0.0);
503+
} else {
504+
double t = r + std::log(s + varianceScale * (1.0 - s));
505+
m_Scales.emplace_back(t / r, 0.5 * (r - t));
506+
logVarianceScaleSum += std::log(t / r);
510507
}
511508
}
509+
}
512510

513-
m_NumberSamples = 0.0;
514-
double weightedNumberSamples = 0.0;
511+
m_NumberSamples = 0.0;
512+
double weightedNumberSamples = 0.0;
515513

516-
for (std::size_t i = 0u; i < m_Weights.size(); ++i) {
517-
double n = maths_t::countForUpdate(m_Weights[i]);
518-
m_NumberSamples += n;
519-
weightedNumberSamples +=
520-
n / (m_Scales.empty() ? 1.0 : m_Scales[i].first);
521-
}
514+
for (std::size_t i = 0u; i < m_Weights.size(); ++i) {
515+
double n = maths_t::countForUpdate(m_Weights[i]);
516+
m_NumberSamples += n;
517+
weightedNumberSamples += n / (m_Scales.empty() ? 1.0 : m_Scales[i].first);
518+
}
522519

523-
double impliedShape = m_Shape + 0.5 * m_NumberSamples;
524-
double impliedPrecision = m_Precision + weightedNumberSamples;
520+
double impliedShape = m_Shape + 0.5 * m_NumberSamples;
521+
double impliedPrecision = m_Precision + weightedNumberSamples;
525522

526-
m_Constant = 0.5 * (std::log(m_Precision) - std::log(impliedPrecision)) -
527-
0.5 * m_NumberSamples * LOG_2_PI - 0.5 * logVarianceScaleSum +
528-
boost::math::lgamma(impliedShape) -
529-
boost::math::lgamma(m_Shape) + m_Shape * std::log(m_Rate);
530-
} catch (const std::exception& e) {
531-
LOG_ERROR(<< "Error calculating marginal likelihood: " << e.what());
523+
m_Constant = 0.5 * (std::log(m_Precision) - std::log(impliedPrecision)) -
524+
0.5 * m_NumberSamples * LOG_2_PI -
525+
0.5 * logVarianceScaleSum + std::lgamma(impliedShape) -
526+
std::lgamma(m_Shape) + m_Shape * std::log(m_Rate);
527+
528+
if (std::isnan(m_Constant)) {
529+
LOG_ERROR(<< "Error calculating marginal likelihood, floating point nan");
532530
this->addErrorStatus(maths_t::E_FpFailed);
531+
} else if (std::isinf(m_Constant)) {
532+
LOG_ERROR(<< "Error calculating marginal likelihood, floating point overflow");
533+
this->addErrorStatus(maths_t::E_FpOverflowed);
533534
}
534535
}
535536

@@ -1219,7 +1220,7 @@ void CLogNormalMeanPrecConjugate::sampleMarginalLikelihood(std::size_t numberSam
12191220
double z = (xq - m_GaussianMean - scale * scale) / scale /
12201221
boost::math::double_constants::root_two;
12211222

1222-
double partialExpectation = mean * (1.0 + boost::math::erf(z)) / 2.0;
1223+
double partialExpectation = mean * (1.0 + std::erf(z)) / 2.0;
12231224

12241225
double sample = static_cast<double>(numberSamples) *
12251226
(partialExpectation - lastPartialExpectation) -

0 commit comments

Comments
 (0)