diff --git a/ql/termstructures/globalbootstrap.hpp b/ql/termstructures/globalbootstrap.hpp index 92e150f0ab2..ee838942688 100644 --- a/ql/termstructures/globalbootstrap.hpp +++ b/ql/termstructures/globalbootstrap.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include namespace QuantLib { @@ -98,9 +99,8 @@ class AdditionalBootstrapVariables { for a concrete implementation of this interface. WARNING: This class is known to work with Traits Discount, ZeroYield, Forward, - i.e. the usual IR curves traits in QL. It requires Traits::transformDirect() - and Traits::transformInverse() to be implemented. Also, check the usage of - Traits::updateGuess(), Traits::guess() in this class. + i.e. the usual IR curves traits in QL. For new Traits you may want to implement + Traits::transformDirect()/transformInverse()/globalGuess(). */ template class GlobalBootstrap final : public MultiCurveBootstrapContributor { typedef typename Curve::traits_type Traits; // ZeroYield, Discount, ForwardRate @@ -133,6 +133,12 @@ template class GlobalBootstrap final : public MultiCurveBootstrapC void calculate() const; private: + template + static constexpr bool hasTransform = false; + + template + static constexpr bool hasGlobalGuess = false; + void initialize() const; void setParentBootstrapper(const ext::shared_ptr& b) const override; @@ -158,6 +164,18 @@ template class GlobalBootstrap final : public MultiCurveBootstrapC // template definitions +template +template +constexpr bool GlobalBootstrap::hasTransform< + T, + std::void_t()))>> = true; + +template +template +constexpr bool GlobalBootstrap::hasGlobalGuess< + T, + std::void_t(), true))>> = true; + template GlobalBootstrap::GlobalBootstrap(Real accuracy, ext::shared_ptr optimizer, @@ -353,8 +371,13 @@ template Array GlobalBootstrap::setupCostFunction() const { // setup interpolation if (!validCurve_) { - ts_->interpolation_ = ts_->interpolator_.interpolate(ts_->times_.begin(), ts_->times_.end(), - ts_->data_.begin()); + ts_->interpolation_ = detail::interpolateWithoutUpdate( + ts_->interpolator_, ts_->times_.begin(), ts_->times_.end(), ts_->data_.begin()); + if constexpr (!hasGlobalGuess) { + // Update interpolation because Traits::guess() might rely on it. Implementing + // Traits::globalGuess() is a more efficient option. + ts_->interpolation_.update(); + } } // Initial guess. We have guesses for the curve values first (numberPillars), @@ -365,10 +388,19 @@ template Array GlobalBootstrap::setupCostFunction() const { } Array guess(ts_->times_.size() - 1 + additionalGuesses.size()); for (Size i = 0; i < ts_->times_.size() - 1; ++i) { - // just pass zero as the first alive helper, it's not used in the standard QL traits anyway - // update ts_->data_ since Traits::guess() usually depends on previous values - Traits::updateGuess(ts_->data_, Traits::guess(i + 1, ts_, validCurve_, 0), i + 1); - guess[i] = Traits::transformInverse(ts_->data_[i + 1], i + 1, ts_); + Real value; + if constexpr (hasGlobalGuess) { + value = Traits::globalGuess(i + 1, ts_, validCurve_); + } else { + // Just pass zero as the first alive helper, it's not used in the standard QL traits + // anyway. Update ts_->data_ since Traits::guess() usually depends on previous values. + Traits::updateGuess(ts_->data_, Traits::guess(i + 1, ts_, validCurve_, 0), i + 1); + value = ts_->data_[i + 1]; + } + if constexpr (hasTransform) { + value = Traits::transformInverse(value, i + 1, ts_); + } + guess[i] = value; } std::copy(additionalGuesses.begin(), additionalGuesses.end(), guess.begin() + ts_->times_.size() - 1); @@ -380,7 +412,11 @@ void GlobalBootstrap::setCostFunctionArgument(const Array& x) const { // x has the same layout as guess above: the first numberPillars values go into // the curve, while the rest are new values for the additional variables. for (Size i = 0; i < ts_->times_.size() - 1; ++i) { - Traits::updateGuess(ts_->data_, Traits::transformDirect(x[i], i + 1, ts_), i + 1); + Real value = x[i]; + if constexpr (hasTransform) { + value = Traits::transformDirect(value, i + 1, ts_); + } + Traits::updateGuess(ts_->data_, value, i + 1); } ts_->interpolation_.update(); if (additionalVariables_) { diff --git a/ql/termstructures/inflation/inflationtraits.hpp b/ql/termstructures/inflation/inflationtraits.hpp index 1f3897853fd..4ece2739f19 100644 --- a/ql/termstructures/inflation/inflationtraits.hpp +++ b/ql/termstructures/inflation/inflationtraits.hpp @@ -64,6 +64,13 @@ namespace QuantLib { return detail::avgInflation; } + template + static Real globalGuess(Size i, + const C* c, + bool validData) + { + return guess(i, c, validData, 0); + } // constraints template @@ -101,15 +108,6 @@ namespace QuantLib { if (i==1) data[0] = level; // the first point is updated as well } - // transformation to add constraints to an unconstrained optimization - template - static Real transformDirect(Real x, Size, const C*) { - return x; - } - template - static Real transformInverse(Real x, Size, const C*) { - return x; - } // upper bound for convergence loop static Size maxIterations() { return 40; } }; @@ -142,6 +140,13 @@ namespace QuantLib { return detail::avgInflation; } + template + static Real globalGuess(Size i, + const C* c, + bool validData) + { + return guess(i, c, validData, 0); + } // constraints template @@ -177,15 +182,6 @@ namespace QuantLib { Size i) { data[i] = level; } - // transformation to add constraints to an unconstrained optimization - template - static Real transformDirect(Real x, Size, const C*) { - return x; - } - template - static Real transformInverse(Real x, Size, const C*) { - return x; - } // upper bound for convergence loop static Size maxIterations() { return 40; } }; diff --git a/ql/termstructures/yield/bootstraptraits.hpp b/ql/termstructures/yield/bootstraptraits.hpp index 6df12138b37..e08cc25c4db 100644 --- a/ql/termstructures/yield/bootstraptraits.hpp +++ b/ql/termstructures/yield/bootstraptraits.hpp @@ -76,6 +76,15 @@ namespace QuantLib { Real r = -std::log(c->data()[i-1])/c->times()[i-1]; return std::exp(-r * c->times()[i]); } + template + static Real globalGuess(Size i, + const C* c, + bool validData) + { + if (validData) // previous iteration value + return c->data()[i]; + return std::exp(-detail::avgRate * c->times()[i]); + } // possible constraints based on previous values template @@ -161,6 +170,15 @@ namespace QuantLib { return c->zeroRate(d, c->dayCounter(), Continuous, Annual, true); } + template + static Real globalGuess(Size i, + const C* c, + bool validData) + { + if (validData) // previous iteration value + return c->data()[i]; + return detail::avgRate; + } // possible constraints based on previous values template @@ -192,18 +210,6 @@ namespace QuantLib { return detail::maxRate; } - // transformation to add constraints to an unconstrained optimization - template - static Real transformDirect(Real x, Size i, const C* c) - { - return x; - } - template - static Real transformInverse(Real x, Size i, const C* c) - { - return x; - } - // root-finding update static void updateGuess(std::vector& data, Real rate, @@ -254,6 +260,15 @@ namespace QuantLib { return c->forwardRate(d, d, c->dayCounter(), Continuous, Annual, true); } + template + static Real globalGuess(Size i, + const C* c, + bool validData) + { + if (validData) // previous iteration value + return c->data()[i]; + return detail::avgRate; + } // possible constraints based on previous values template @@ -285,18 +300,6 @@ namespace QuantLib { return detail::maxRate; } - // transformation to add constraints to an unconstrained optimization - template - static Real transformDirect(Real x, Size i, const C* c) - { - return x; - } - template - static Real transformInverse(Real x, Size i, const C* c) - { - return x; - } - // root-finding update static void updateGuess(std::vector& data, Real forward, @@ -346,6 +349,15 @@ namespace QuantLib { return c->zeroRate(d, c->dayCounter(), Simple, Annual, true); } + template + static Real globalGuess(Size i, + const C* c, + bool validData) + { + if (validData) // previous iteration value + return c->data()[i]; + return detail::avgRate; + } // possible constraints based on previous values template