diff --git a/core/log/convergence.cpp b/core/log/convergence.cpp index a59b2904ad5..4a15df32c0b 100644 --- a/core/log/convergence.cpp +++ b/core/log/convergence.cpp @@ -57,34 +57,55 @@ void Convergence::on_iteration_complete( const LinOp* residual_norm, const LinOp* implicit_resnorm_sq, const array* status, const bool stopped) const { + auto update_history = [&](auto& container, auto&& new_val, bool is_norm) { + if (history_ == convergence_history::none) { + if (container.empty()) { + container.emplace_back(nullptr); + } + container.back() = std::move(new_val); + return; + } + if (is_norm || history_ == convergence_history::full) { + container.emplace_back(std::move(new_val)); + } + }; + if (num_iterations == 0) { + residual_.clear(); + residual_norm_.clear(); + implicit_sq_resnorm_.clear(); + } if (stopped) { array tmp(status->get_executor()->get_master(), *status); - this->convergence_status_ = true; + convergence_status_ = true; for (int i = 0; i < status->get_size(); i++) { if (!tmp.get_data()[i].has_converged()) { - this->convergence_status_ = false; + convergence_status_ = false; break; } } - this->num_iterations_ = num_iterations; + num_iterations_ = num_iterations; + } + if (stopped || history_ != convergence_history::none) { if (residual != nullptr) { - this->residual_.reset(residual->clone().release()); + update_history(residual_, residual->clone(), false); } if (implicit_resnorm_sq != nullptr) { - this->implicit_sq_resnorm_.reset( - implicit_resnorm_sq->clone().release()); + update_history(implicit_sq_resnorm_, implicit_resnorm_sq->clone(), + true); } if (residual_norm != nullptr) { - this->residual_norm_.reset(residual_norm->clone().release()); + update_history(residual_norm_, residual_norm->clone(), true); } else if (residual != nullptr) { using NormVector = matrix::Dense>; detail::vector_dispatch( residual, [&](const auto* dense_r) { - this->residual_norm_ = + update_history( + residual_norm_, NormVector::create(residual->get_executor(), - dim<2>{1, residual->get_size()[1]}); - dense_r->compute_norm2(this->residual_norm_); + dim<2>{1, residual->get_size()[1]}), + true); + dense_r->compute_norm2(residual_norm_.back()); }); } else if (dynamic_cast( solver) && @@ -97,13 +118,21 @@ void Convergence::on_iteration_complete( detail::vector_dispatch(b, [&](const auto* dense_b) { detail::vector_dispatch(x, [&](const auto* dense_x) { auto exec = system_mtx->get_executor(); - auto residual = dense_b->clone(); - this->residual_norm_ = NormVector::create( - exec, dim<2>{1, residual->get_size()[1]}); + update_history(residual_, dense_b->clone(), false); system_mtx->apply(initialize({-1.0}, exec), dense_x, initialize({1.0}, exec), - residual); - residual->compute_norm2(this->residual_norm_); + residual_.back()); + update_history( + residual_norm_, + NormVector::create( + exec, dim<2>{1, residual_.back()->get_size()[1]}), + true); + detail::vector_dispatch( + residual_.back().get(), + [&](const auto* actual_residual) { + actual_residual->compute_norm2( + residual_norm_.back()); + }); }); }); } diff --git a/core/test/log/convergence.cpp b/core/test/log/convergence.cpp index 5231e7c97b2..170e2879206 100644 --- a/core/test/log/convergence.cpp +++ b/core/test/log/convergence.cpp @@ -61,6 +61,9 @@ TYPED_TEST(Convergence, CanGetEmptyData) ASSERT_EQ(logger->get_residual(), nullptr); ASSERT_EQ(logger->get_residual_norm(), nullptr); ASSERT_EQ(logger->get_implicit_sq_resnorm(), nullptr); + ASSERT_TRUE(logger->get_residual_history().empty()); + ASSERT_TRUE(logger->get_residual_norm_history().empty()); + ASSERT_TRUE(logger->get_implicit_sq_resnorm_history().empty()); } @@ -100,6 +103,10 @@ TYPED_TEST(Convergence, DoesNotLogIfNotStopped) ASSERT_EQ(logger->get_num_iterations(), 0); ASSERT_EQ(logger->get_residual(), nullptr); ASSERT_EQ(logger->get_residual_norm(), nullptr); + ASSERT_EQ(logger->get_implicit_sq_resnorm(), nullptr); + ASSERT_TRUE(logger->get_residual_history().empty()); + ASSERT_TRUE(logger->get_residual_norm_history().empty()); + ASSERT_TRUE(logger->get_implicit_sq_resnorm_history().empty()); } @@ -131,4 +138,89 @@ TYPED_TEST(Convergence, CanComputeResidualNormFromSolution) } +TYPED_TEST(Convergence, CanLogDataWithNormHistory) +{ + using AbsoluteDense = gko::matrix::Dense>; + auto logger = gko::log::Convergence::create( + gko::convergence_history::norm); + + logger->template on( + this->system.get(), nullptr, nullptr, 100, nullptr, + this->residual_norm.get(), this->implicit_sq_resnorm.get(), nullptr, + false); + logger->template on( + this->system.get(), nullptr, nullptr, 101, nullptr, + this->residual_norm.get(), this->implicit_sq_resnorm.get(), + &this->status, true); + + ASSERT_EQ(logger->get_residual_history().size(), 0); + ASSERT_EQ(logger->get_residual_norm_history().size(), 2); + ASSERT_EQ(logger->get_implicit_sq_resnorm_history().size(), 2); + for (int i : {0, 1}) { + GKO_ASSERT_MTX_NEAR( + gko::as(logger->get_residual_norm_history()[i]), + this->residual_norm, 0); + GKO_ASSERT_MTX_NEAR(gko::as( + logger->get_implicit_sq_resnorm_history()[i]), + this->implicit_sq_resnorm, 0); + } +} + + +TYPED_TEST(Convergence, CanLogDataWithFullHistory) +{ + using Dense = gko::matrix::Dense; + using AbsoluteDense = gko::matrix::Dense>; + auto logger = gko::log::Convergence::create( + gko::convergence_history::full); + + logger->template on( + this->system.get(), nullptr, nullptr, 100, this->residual.get(), + this->residual_norm.get(), this->implicit_sq_resnorm.get(), nullptr, + false); + logger->template on( + this->system.get(), nullptr, nullptr, 101, this->residual.get(), + this->residual_norm.get(), this->implicit_sq_resnorm.get(), + &this->status, true); + + ASSERT_EQ(logger->get_residual_history().size(), 2); + ASSERT_EQ(logger->get_residual_norm_history().size(), 2); + ASSERT_EQ(logger->get_implicit_sq_resnorm_history().size(), 2); + for (int i : {0, 1}) { + GKO_ASSERT_MTX_NEAR(gko::as(logger->get_residual_history()[i]), + this->residual, 0); + GKO_ASSERT_MTX_NEAR( + gko::as(logger->get_residual_norm_history()[i]), + this->residual_norm, 0); + GKO_ASSERT_MTX_NEAR(gko::as( + logger->get_implicit_sq_resnorm_history()[i]), + this->implicit_sq_resnorm, 0); + } +} + + +TYPED_TEST(Convergence, CanClearHistory) +{ + auto logger = gko::log::Convergence::create( + gko::convergence_history::full); + + logger->template on( + this->system.get(), nullptr, nullptr, 100, this->residual.get(), + this->residual_norm.get(), this->implicit_sq_resnorm.get(), nullptr, + false); + logger->template on( + this->system.get(), nullptr, nullptr, 101, this->residual.get(), + this->residual_norm.get(), this->implicit_sq_resnorm.get(), + &this->status, true); + logger->template on( + this->system.get(), nullptr, nullptr, 0, this->residual.get(), + this->residual_norm.get(), this->implicit_sq_resnorm.get(), nullptr, + false); + + ASSERT_EQ(logger->get_residual_history().size(), 1); + ASSERT_EQ(logger->get_residual_norm_history().size(), 1); + ASSERT_EQ(logger->get_implicit_sq_resnorm_history().size(), 1); +} + + } // namespace diff --git a/include/ginkgo/core/log/convergence.hpp b/include/ginkgo/core/log/convergence.hpp index e10d1e7861a..ce52a904fea 100644 --- a/include/ginkgo/core/log/convergence.hpp +++ b/include/ginkgo/core/log/convergence.hpp @@ -15,6 +15,15 @@ namespace gko { + + +enum class convergence_history { + none, //!< keep no history + norm, //!< keep history of vector norms + full //!< keep history of vector norms and vectors +}; + + /** * @brief The logger namespace . * @ref log @@ -64,7 +73,6 @@ class Convergence : public Logger { * Creates a convergence logger. This dynamically allocates the memory, * constructs the object and returns an std::unique_ptr to this object. * - * @param exec the executor * @param enabled_events the events enabled for this logger. By default all * events. * @@ -77,16 +85,17 @@ class Convergence : public Logger { GKO_DEPRECATED("use single-parameter create") static std::unique_ptr create( std::shared_ptr, - const mask_type& enabled_events = Logger::criterion_events_mask | - Logger::iteration_complete_mask) + const mask_type& enabled_events = criterion_events_mask | + iteration_complete_mask) { - return std::unique_ptr(new Convergence(enabled_events)); + return create(enabled_events); } /** * Creates a convergence logger. This dynamically allocates the memory, * constructs the object and returns an std::unique_ptr to this object. * + * @param history decide how the convergence history should be handled * @param enabled_events the events enabled for this logger. By default all * events. * @@ -97,10 +106,34 @@ class Convergence : public Logger { * shouldn't be a problem. */ static std::unique_ptr create( - const mask_type& enabled_events = Logger::criterion_events_mask | - Logger::iteration_complete_mask) + convergence_history history = convergence_history::none, + const mask_type& enabled_events = criterion_events_mask | + iteration_complete_mask) { - return std::unique_ptr(new Convergence(enabled_events)); + return std::unique_ptr( + new Convergence(history, enabled_events)); + } + + /** + * Creates a convergence logger. This dynamically allocates the memory, + * constructs the object and returns an std::unique_ptr to this object. + * + * @param enabled_events the events enabled for this logger. By default all + * events. + * + * @note No history will be kept. Only the vectors and norms of the final + * iteration are accessible. + * + * @return an std::unique_ptr to the the constructed object + * + * @internal here I cannot use EnableCreateMethod due to complex circular + * dependencies. At the same time, this method is short enough that it + * shouldn't be a problem. + */ + static std::unique_ptr create(const mask_type& enabled_events) + { + return std::unique_ptr( + new Convergence(convergence_history::none, enabled_events)); } /** @@ -113,7 +146,7 @@ class Convergence : public Logger { /** * Resets the convergence status to false. */ - void reset_convergence_status() { this->convergence_status_ = false; } + void reset_convergence_status() { convergence_status_ = false; } /** * Returns the number of iterations @@ -126,66 +159,100 @@ class Convergence : public Logger { } /** - * Returns the residual + * Returns the residual of the final iteration. * * @return the residual */ - const LinOp* get_residual() const noexcept { return residual_.get(); } + const LinOp* get_residual() const noexcept + { + return residual_.empty() ? nullptr : residual_.back().get(); + } /** - * Returns the residual norm + * Returns the full history of the residuals. + * + * This will only have a length > 1 if convergence_history::full was set. + * + * @return the residual history + */ + const std::vector>& get_residual_history() + const noexcept + { + return residual_; + } + + /** + * Returns the residual norm of the final iteration. * * @return the residual norm */ const LinOp* get_residual_norm() const noexcept { - return residual_norm_.get(); + return residual_norm_.empty() ? nullptr : residual_norm_.back().get(); + } + + /** + * + * Returns the full history of the residual norms. + * + * This will only have a length > 1 if convergence_history::norm or + * convergence_history::full was set. + * + * @return the residual norm history + */ + const std::vector>& get_residual_norm_history() + const noexcept + { + return residual_norm_; } /** - * Returns the implicit squared residual norm + * Returns the implicit squared residual norm of the final iteration. * * @return the implicit squared residual norm */ const LinOp* get_implicit_sq_resnorm() const noexcept { - return implicit_sq_resnorm_.get(); + return implicit_sq_resnorm_.empty() ? nullptr + : implicit_sq_resnorm_.back().get(); } -protected: /** - * Creates a Convergence logger. * - * @param exec the executor - * @param enabled_events the events enabled for this logger. By default all - * events. + * Returns the full history of the implicit squared residual norm. + * + * This will only have a length > 1 if convergence_history::norm or + * convergence_history::full was set. + * + * @return the implicit squared residual norm history */ - GKO_DEPRECATED("use single-parameter constructor") - explicit Convergence( - std::shared_ptr, - const mask_type& enabled_events = Logger::criterion_events_mask | - Logger::iteration_complete_mask) - : Logger(enabled_events) - {} + const std::vector>& get_implicit_sq_resnorm_history() + const noexcept + { + return implicit_sq_resnorm_; + } +protected: /** * Creates a Convergence logger. * + * @param history decide how the convergence history should be handled * @param enabled_events the events enabled for this logger. By default all * events. */ - explicit Convergence( - const mask_type& enabled_events = Logger::criterion_events_mask | - Logger::iteration_complete_mask) - : Logger(enabled_events) + explicit Convergence(convergence_history history, + const mask_type& enabled_events = + criterion_events_mask | iteration_complete_mask) + : Logger(enabled_events), history_(history) {} private: + convergence_history history_; mutable bool convergence_status_{false}; mutable size_type num_iterations_{}; - mutable std::unique_ptr residual_{}; - mutable std::unique_ptr residual_norm_{}; - mutable std::unique_ptr implicit_sq_resnorm_{}; + mutable std::vector> residual_; + mutable std::vector> residual_norm_; + mutable std::vector> implicit_sq_resnorm_; };