67 switch (preconditioner) {
75 return "SymmetricGaussSeidel";
79 return "ChebyshevJacobi";
90 if (name ==
"Identity" || name ==
"identity") {
93 if (name ==
"Jacobi" || name ==
"jacobi") {
96 if (name ==
"SpdIsai" || name ==
"spd-isai" || name ==
"spd_isai") {
99 if (name ==
"SymmetricGaussSeidel" || name ==
"symmetric-gauss-seidel"
100 || name ==
"symmetric_gauss_seidel") {
103 if (name ==
"Ssor" || name ==
"SSOR" || name ==
"ssor") {
106 if (name ==
"ChebyshevJacobi" || name ==
"chebyshev-jacobi" || name ==
"chebyshev_jacobi") {
109 if (name ==
"IrJacobi" || name ==
"ir-jacobi" || name ==
"ir_jacobi") {
112 if (name ==
"GeneralIsai" || name ==
"general-isai" || name ==
"general_isai"
116 throw std::runtime_error(
"unknown strong-formulation preconditioner: " + std::string(name));
148inline unsigned int solver_progress_stride()
150 char const*
const value = std::getenv(
"SIMILIE_SOLVER_PROGRESS_STRIDE");
151 if (value ==
nullptr || value[0] ==
'\0') {
155 char* parse_end =
nullptr;
156 unsigned long const parsed = std::strtoul(value, &parse_end, 10);
157 if (parse_end == value) {
163 if (parsed > std::numeric_limits<unsigned int>::max()) {
164 return std::numeric_limits<unsigned int>::max();
166 return static_cast<unsigned int>(parsed);
169inline bool solver_progress_enabled()
171 return solver_progress_stride() != 0U;
174inline bool env_flag_enabled(
char const* name)
176 char const*
const value = std::getenv(name);
177 return value !=
nullptr && value[0] !=
'\0' && value[0] !=
'0';
180inline bool env_value_equals(
char const* name,
char const* expected)
182 char const*
const value = std::getenv(name);
183 return value !=
nullptr && std::string_view(value) == expected;
186inline double env_double_or(
char const* name,
double default_value)
188 char const*
const value = std::getenv(name);
189 if (value ==
nullptr || value[0] ==
'\0') {
190 return default_value;
192 char* parse_end =
nullptr;
193 double const parsed = std::strtod(value, &parse_end);
194 if (parse_end == value) {
195 return default_value;
200inline int env_int_or(
char const* name,
int default_value)
202 char const*
const value = std::getenv(name);
203 if (value ==
nullptr || value[0] ==
'\0') {
204 return default_value;
206 char* parse_end =
nullptr;
207 long const parsed = std::strtol(value, &parse_end, 10);
208 if (parse_end == value) {
209 return default_value;
211 if (parsed > std::numeric_limits<int>::max()) {
212 return std::numeric_limits<int>::max();
214 if (parsed < std::numeric_limits<int>::min()) {
215 return std::numeric_limits<int>::min();
217 return static_cast<int>(parsed);
220template <
class MatrixData>
221void log_matrix_diagnostics(MatrixData
const& matrix_data)
223 std::size_t
const size = matrix_data.size[0];
224 std::vector<double> diagonal(size, 0.0);
225 std::size_t negative_diagonal_count = 0;
226 std::size_t zero_diagonal_count = 0;
227 double min_diagonal = std::numeric_limits<double>::infinity();
228 double max_diagonal = -std::numeric_limits<double>::infinity();
229 std::vector<double> off_diagonal_abs_row_sum(size, 0.0);
230 for (
auto const& entry : matrix_data.nonzeros) {
231 if (entry.row == entry.column) {
232 diagonal[
static_cast<std::size_t
>(entry.row)] += entry.value;
234 off_diagonal_abs_row_sum[
static_cast<std::size_t
>(entry.row)] += std::abs(entry.value);
237 double jacobi_gershgorin_lower = std::numeric_limits<double>::infinity();
238 double jacobi_gershgorin_upper = -std::numeric_limits<double>::infinity();
239 double jacobi_max_abs_off_diagonal_row_sum = 0.0;
240 for (
double const value : diagonal) {
242 ++negative_diagonal_count;
245 ++zero_diagonal_count;
247 min_diagonal = std::min(min_diagonal, value);
248 max_diagonal = std::max(max_diagonal, value);
250 for (std::size_t row = 0; row < size; ++row) {
251 if (diagonal[row] == 0.0) {
254 double const scaled_radius = off_diagonal_abs_row_sum[row] / std::abs(diagonal[row]);
255 jacobi_max_abs_off_diagonal_row_sum
256 = std::max(jacobi_max_abs_off_diagonal_row_sum, scaled_radius);
257 jacobi_gershgorin_lower = std::min(jacobi_gershgorin_lower, 1.0 - scaled_radius);
258 jacobi_gershgorin_upper = std::max(jacobi_gershgorin_upper, 1.0 + scaled_radius);
261 double max_abs_asymmetry = 0.0;
262 std::size_t asymmetric_entry_count = 0;
263 bool const symmetry_diagnostics_skipped
264 = env_flag_enabled(
"SIMILIE_SKIP_MATRIX_SYMMETRY_DIAGNOSTICS");
265 if (!symmetry_diagnostics_skipped) {
266 auto matrix_entries = std::unordered_map<std::uint64_t, double>();
267 matrix_entries.reserve(matrix_data.nonzeros.size());
268 auto const entry_key = [size](std::size_t row, std::size_t column) {
269 return static_cast<std::uint64_t
>(row) *
static_cast<std::uint64_t
>(size)
270 +
static_cast<std::uint64_t
>(column);
272 for (
auto const& entry : matrix_data.nonzeros) {
273 matrix_entries[entry_key(
274 static_cast<std::size_t
>(entry.row),
275 static_cast<std::size_t
>(entry.column))]
278 for (
auto const& [key, value] : matrix_entries) {
279 std::size_t
const row
280 =
static_cast<std::size_t
>(key /
static_cast<std::uint64_t
>(size));
281 std::size_t
const column
282 =
static_cast<std::size_t
>(key %
static_cast<std::uint64_t
>(size));
283 double transposed_value = 0.0;
284 if (
auto const transposed = matrix_entries.find(entry_key(column, row));
285 transposed != matrix_entries.end()) {
286 transposed_value = transposed->second;
288 double const asymmetry = std::abs(value - transposed_value);
290 > 1.0e-10 * std::max({1.0, std::abs(value), std::abs(transposed_value)})) {
291 ++asymmetric_entry_count;
293 max_abs_asymmetry = std::max(max_abs_asymmetry, asymmetry);
297 std::cout <<
"SimiLie matrix diagnostics: size=" << size
298 <<
" nonzeros=" << matrix_data.nonzeros.size() <<
" diagonal_min=" << min_diagonal
299 <<
" diagonal_max=" << max_diagonal
300 <<
" diagonal_negative_count=" << negative_diagonal_count
301 <<
" diagonal_zero_count=" << zero_diagonal_count
302 <<
" jacobi_gershgorin_lower=" << jacobi_gershgorin_lower
303 <<
" jacobi_gershgorin_upper=" << jacobi_gershgorin_upper
304 <<
" jacobi_max_abs_off_diagonal_row_sum=" << jacobi_max_abs_off_diagonal_row_sum
305 <<
" symmetry_diagnostics_skipped=" << symmetry_diagnostics_skipped
306 <<
" asymmetric_entry_count=" << asymmetric_entry_count
307 <<
" max_abs_asymmetry=" << max_abs_asymmetry <<
'\n';
310class SolverProgressLogger final :
public gko::log::Logger
313 SolverProgressLogger(
314 std::shared_ptr<gko::Executor const> master_executor,
315 double initial_residual_l2,
317 : Logger(gko::log::Logger::iteration_complete_mask)
318 , m_master_executor(std::move(master_executor))
319 , m_initial_residual_l2(initial_residual_l2)
325 using gko::log::Logger::on_iteration_complete;
327 void on_iteration_complete(
331 gko::size_type
const& num_iterations,
332 gko::LinOp
const* residual,
333 gko::LinOp
const* residual_norm,
335 gko::array<gko::stopping_status>
const*,
338 if (m_stride == 0U || num_iterations % m_stride != 0U) {
341 std::optional<double> residual_l2 = residual_norm_value(residual_norm);
342 if (!residual_l2.has_value()) {
343 residual_l2 = residual_vector_l2(residual);
345 if (!residual_l2.has_value()) {
348 double const relative_residual
349 = m_initial_residual_l2 == 0.0 ? 0.0 : *residual_l2 / m_initial_residual_l2;
350 std::cout <<
"SimiLie solver progress: iteration=" << num_iterations
351 <<
" residual_l2=" << *residual_l2 <<
" relative_residual=" << relative_residual
356 std::optional<double> residual_norm_value(gko::LinOp
const* residual_norm)
const
358 auto const* residual_norm_dense
359 =
dynamic_cast<gko::matrix::Dense<double> const*
>(residual_norm);
360 if (residual_norm_dense ==
nullptr) {
363 auto host_dense = gko::matrix::Dense<
364 double>::create(m_master_executor, residual_norm_dense->get_size());
365 residual_norm_dense->convert_to(host_dense.get());
366 return host_dense->at(0, 0);
369 std::optional<double> residual_vector_l2(gko::LinOp
const* residual)
const
371 auto const* residual_dense =
dynamic_cast<gko::matrix::Dense<double> const*
>(residual);
372 if (residual_dense ==
nullptr) {
376 = gko::matrix::Dense<double>::create(m_master_executor, residual_dense->get_size());
377 residual_dense->convert_to(host_dense.get());
378 double squared_norm = 0.0;
379 for (gko::size_type row = 0; row < host_dense->get_size()[0]; ++row) {
380 for (gko::size_type column = 0; column < host_dense->get_size()[1]; ++column) {
381 double const value = host_dense->at(row, column);
382 squared_norm += value * value;
385 return std::sqrt(squared_norm);
388 std::shared_ptr<gko::Executor const> m_master_executor;
389 double m_initial_residual_l2;
390 unsigned int m_stride;
393inline void log_nonlinear_progress(
394 unsigned int iteration,
395 StrongFormulationSolverDiagnostics
const& diagnostics)
397 if (!solver_progress_enabled()) {
400 std::cout <<
"SimiLie nonlinear progress: iteration=" << iteration
401 <<
" residual_l2=" << diagnostics.final_residual_l2
402 <<
" relative_residual=" << diagnostics.final_relative_residual << std::endl;
405template <
class ExecSpace,
class OperatorModel,
class =
void>
406struct MatrixFreeWorkspaceTraits
408 static constexpr bool enabled =
false;
414template <
class ExecSpace,
class OperatorModel>
415struct MatrixFreeWorkspaceTraits<
418 std::void_t<decltype(std::declval<OperatorModel const&>().create_matrix_free_workspace(
419 std::declval<ExecSpace>()))>>
421 static constexpr bool enabled =
true;
422 using type =
decltype(std::declval<OperatorModel const&>().create_matrix_free_workspace(
423 std::declval<ExecSpace>()));
426template <
class OperatorModel>
427bool uses_precomputed_matrix_free_stencils(OperatorModel
const& operator_model)
429 if constexpr (
requires(OperatorModel
const& model) { model.has_precomputed_stencils(); }) {
430 return operator_model.has_precomputed_stencils();
435template <
class ExecSpace,
class ViewType1,
class ViewType2>
436double dot(ExecSpace exec_space, ViewType1 lhs, ViewType2 rhs)
439 Kokkos::parallel_reduce(
441 Kokkos::MDRangePolicy<
443 Kokkos::Rank<2>>(exec_space, {0, 0}, {lhs.extent(0), lhs.extent(1)}),
444 KOKKOS_LAMBDA(std::size_t row, std::size_t column,
double& local_sum) {
445 local_sum += lhs(row, column) * rhs(row, column);
452template <
class ExecSpace,
class ViewType>
453double residual_norm_l2(ExecSpace exec_space, ViewType residual)
455 return std::sqrt(dot(exec_space, residual, residual));
458template <
class ExecSpace,
class ViewType>
459void fill(ExecSpace exec_space, ViewType view,
double value)
461 Kokkos::parallel_for(
463 Kokkos::MDRangePolicy<
465 Kokkos::Rank<2>>(exec_space, {0, 0}, {view.extent(0), view.extent(1)}),
466 KOKKOS_LAMBDA(std::size_t row, std::size_t column) { view(row, column) = value; });
470template <
class ExecSpace,
class DestinationView,
class SourceView>
471void copy(ExecSpace exec_space, DestinationView destination, SourceView source)
473 Kokkos::parallel_for(
475 Kokkos::MDRangePolicy<
478 2>>(exec_space, {0, 0}, {destination.extent(0), destination.extent(1)}),
479 KOKKOS_LAMBDA(std::size_t row, std::size_t column) {
480 destination(row, column) = source(row, column);
485template <
class ExecSpace,
class ViewType1,
class ViewType2,
class ViewType3>
487 ExecSpace exec_space,
488 ViewType1 destination,
494 Kokkos::parallel_for(
496 Kokkos::MDRangePolicy<
499 2>>(exec_space, {0, 0}, {destination.extent(0), destination.extent(1)}),
500 KOKKOS_LAMBDA(std::size_t row, std::size_t column) {
501 destination(row, column) = alpha * x(row, column) + beta * y(row, column);
506template <
class ExecSpace,
class ViewType1,
class ViewType2>
507void axpy_inplace(ExecSpace exec_space, ViewType1 destination,
double alpha, ViewType2 source)
509 Kokkos::parallel_for(
510 "similie_axpy_inplace",
511 Kokkos::MDRangePolicy<
514 2>>(exec_space, {0, 0}, {destination.extent(0), destination.extent(1)}),
515 KOKKOS_LAMBDA(std::size_t row, std::size_t column) {
516 destination(row, column) += alpha * source(row, column);
521template <
class KokkosViewType>
524 std::shared_ptr<gko::matrix::Dense<typename KokkosViewType::non_const_value_type>> dense;
525 std::optional<Kokkos::View<
526 typename KokkosViewType::non_const_value_type**,
528 typename KokkosViewType::memory_space>>
532template <
class KokkosViewType>
533auto to_gko_dense(std::shared_ptr<gko::Executor const>
const& gko_exec, KokkosViewType
const& view)
535 static_assert(Kokkos::is_view_v<KokkosViewType> && KokkosViewType::rank == 2);
536 using value_type =
typename KokkosViewType::traits::value_type;
537 using non_const_value_type =
typename KokkosViewType::non_const_value_type;
538 using owning_view_type = Kokkos::View<
539 non_const_value_type**,
541 typename KokkosViewType::memory_space>;
543 GkoDenseHandle<KokkosViewType> handle;
544 if (view.stride(1) == 1) {
545 handle.dense = gko::matrix::Dense<value_type>::
547 gko::dim<2>(view.extent(0), view.extent(1)),
548 gko::array<value_type>::view(gko_exec, view.span(), view.data()),
553 handle.owned_view.emplace(
"similie_gko_dense_bridge", view.extent(0), view.extent(1));
554 Kokkos::deep_copy(*handle.owned_view, view);
555 handle.dense = gko::matrix::Dense<value_type>::
557 gko::dim<2>(handle.owned_view->extent(0), handle.owned_view->extent(1)),
558 gko::array<value_type>::
559 view(gko_exec, handle.owned_view->span(), handle.owned_view->data()),
560 handle.owned_view->stride(0));
564template <
class DestinationView,
class KokkosViewType>
565void copy_back_from_gko_dense_bridge(
566 DestinationView destination,
567 GkoDenseHandle<KokkosViewType>
const& handle)
569 if (handle.owned_view.has_value()) {
570 Kokkos::deep_copy(destination, *handle.owned_view);
574inline std::shared_ptr<gko::matrix::Csr<double, gko::int32>> csr_from_matrix_data(
575 std::shared_ptr<gko::Executor const>
const& gko_exec,
576 gko::matrix_data<double, gko::int32>
const& matrix_data)
578 using matrix_type = gko::matrix::Csr<double, gko::int32>;
579 std::vector<double> values(matrix_data.nonzeros.size());
580 std::vector<gko::int32> columns(matrix_data.nonzeros.size());
581 std::vector<gko::int32> row_ptrs(matrix_data.size[0] + 1, 0);
582 for (
auto const& nonzero : matrix_data.nonzeros) {
583 ++row_ptrs[
static_cast<std::size_t
>(nonzero.row) + 1];
585 for (std::size_t row = 0; row < matrix_data.size[0]; ++row) {
586 row_ptrs[row + 1] += row_ptrs[row];
589 auto next_offsets = row_ptrs;
590 auto const next_offsets_view = Kokkos::
591 View<gko::int32*, Kokkos::HostSpace>(next_offsets.data(), next_offsets.size());
592 auto const nonzeros_view = Kokkos::View<
593 gko::matrix_data<double, gko::int32>::nonzero_type
const*,
594 Kokkos::HostSpace>(matrix_data.nonzeros.data(), matrix_data.nonzeros.size());
595 auto const columns_view
596 = Kokkos::View<gko::int32*, Kokkos::HostSpace>(columns.data(), columns.size());
597 auto const values_view = Kokkos::View<double*, Kokkos::HostSpace>(values.data(), values.size());
598 Kokkos::parallel_for(
599 "similie_pack_ginkgo_csr_arrays",
600 Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>(
601 Kokkos::DefaultHostExecutionSpace(),
603 static_cast<Kokkos::DefaultHostExecutionSpace::size_type
>(
604 matrix_data.nonzeros.size())),
605 KOKKOS_LAMBDA(std::size_t nonzero_index) {
606 auto const nonzero = nonzeros_view(nonzero_index);
607 gko::int32
const output_index
608 = Kokkos::atomic_fetch_add(&next_offsets_view(nonzero.row), 1);
609 columns_view(
static_cast<std::size_t
>(output_index)) = nonzero.column;
610 values_view(
static_cast<std::size_t
>(output_index)) = nonzero.value;
612 Kokkos::DefaultHostExecutionSpace().fence();
614 return std::shared_ptr<matrix_type>(
618 gko::array<double>(gko_exec, values.begin(), values.end()),
619 gko::array<gko::int32>(gko_exec, columns.begin(), columns.end()),
620 gko::array<gko::int32>(gko_exec, row_ptrs.begin(), row_ptrs.end()))
624template <
class OperatorModel>
625std::shared_ptr<gko::matrix::Csr<double, gko::int32>> build_matrix(
626 std::shared_ptr<gko::Executor const>
const& gko_exec,
627 OperatorModel
const& operator_model)
629 auto matrix_data = assemble_matrix_data(operator_model);
630 if (env_flag_enabled(
"SIMILIE_MATRIX_DIAGNOSTICS")) {
631 log_matrix_diagnostics(matrix_data);
633 return csr_from_matrix_data(gko_exec, matrix_data);
636template <
class OperatorModel,
class StateView>
637std::shared_ptr<gko::matrix::Csr<double, gko::int32>> build_matrix(
638 std::shared_ptr<gko::Executor const>
const& gko_exec,
639 OperatorModel
const& operator_model,
642 auto matrix_data = assemble_matrix_data(operator_model, state);
643 if (env_flag_enabled(
"SIMILIE_MATRIX_DIAGNOSTICS")) {
644 log_matrix_diagnostics(matrix_data);
646 return csr_from_matrix_data(gko_exec, matrix_data);
649template <
class ExecSpace,
class OperatorModel>
650class MatrixFreeLinOp :
public gko::EnableLinOp<MatrixFreeLinOp<ExecSpace, OperatorModel>>
652 using value_type = double;
653 using dense_type = gko::matrix::Dense<value_type>;
654 using memory_space =
typename ExecSpace::memory_space;
655 using base_type = gko::EnableLinOp<MatrixFreeLinOp<ExecSpace, OperatorModel>>;
656 using workspace_traits = MatrixFreeWorkspaceTraits<ExecSpace, OperatorModel>;
657 using workspace_type =
typename workspace_traits::type;
659 ExecSpace m_exec_space;
660 std::shared_ptr<OperatorModel const> m_operator_model;
661 mutable std::shared_ptr<workspace_type> m_workspace;
662 mutable std::size_t m_apply_count = 0;
663 mutable std::size_t m_advanced_apply_count = 0;
664 mutable double m_apply_duration = 0.0;
665 mutable double m_advanced_apply_duration = 0.0;
668 explicit MatrixFreeLinOp(std::shared_ptr<gko::Executor const> exec)
669 : base_type(std::move(exec))
676 std::shared_ptr<gko::Executor const> exec,
677 ExecSpace exec_space,
678 std::shared_ptr<OperatorModel const> operator_model)
679 : base_type(exec, gko::dim<2>(operator_model->size(), operator_model->size()))
680 , m_exec_space(exec_space)
681 , m_operator_model(std::move(operator_model))
683 if constexpr (workspace_traits::enabled) {
684 if (!uses_precomputed_matrix_free_stencils(*m_operator_model)) {
685 m_workspace = std::make_shared<workspace_type>(
686 m_operator_model->create_matrix_free_workspace(m_exec_space));
692 void apply_impl(gko::LinOp
const* b, gko::LinOp* x)
const override
694 log_apply_start(
"simple", m_apply_count + 1);
695 auto const apply_start = std::chrono::steady_clock::now();
696 auto const* b_dense =
dynamic_cast<dense_type const*
>(b);
697 auto* x_dense =
dynamic_cast<dense_type*
>(x);
698 if (b_dense ==
nullptr || x_dense ==
nullptr) {
699 throw std::invalid_argument(
"MatrixFreeLinOp expects dense inputs and outputs");
701 auto b_view = gko::ext::kokkos::map_data<memory_space>(*b_dense);
702 auto x_view = gko::ext::kokkos::map_data<memory_space>(*x_dense);
703 if constexpr (workspace_traits::enabled) {
704 if constexpr (
requires(
705 OperatorModel
const& model,
707 decltype(b_view) input,
708 decltype(x_view) output,
709 workspace_type& workspace) {
710 model.apply(ex, input, output, workspace);
712 if (uses_precomputed_matrix_free_stencils(*m_operator_model)) {
713 m_operator_model->apply(m_exec_space, b_view, x_view);
715 if (m_workspace ==
nullptr) {
716 m_workspace = std::make_shared<workspace_type>(
717 m_operator_model->create_matrix_free_workspace(m_exec_space));
719 m_operator_model->apply(m_exec_space, b_view, x_view, *m_workspace);
722 m_operator_model->apply(m_exec_space, b_view, x_view);
725 m_operator_model->apply(m_exec_space, b_view, x_view);
727 m_exec_space.fence();
728 auto const apply_end = std::chrono::steady_clock::now();
730 m_apply_duration += std::chrono::duration<double>(apply_end - apply_start).count();
731 log_apply_progress(
"simple", m_apply_count, m_apply_duration);
735 gko::LinOp
const* alpha,
737 gko::LinOp
const* beta,
738 gko::LinOp* x)
const override
740 log_apply_start(
"advanced", m_advanced_apply_count + 1);
741 auto const apply_start = std::chrono::steady_clock::now();
742 auto const* alpha_dense =
dynamic_cast<dense_type const*
>(alpha);
743 auto const* b_dense =
dynamic_cast<dense_type const*
>(b);
744 auto const* beta_dense =
dynamic_cast<dense_type const*
>(beta);
745 auto* x_dense =
dynamic_cast<dense_type*
>(x);
746 if (alpha_dense ==
nullptr || b_dense ==
nullptr || beta_dense ==
nullptr
747 || x_dense ==
nullptr) {
748 throw std::invalid_argument(
749 "MatrixFreeLinOp expects dense alpha, beta, input, and output");
752 auto alpha_view = gko::ext::kokkos::map_data<memory_space>(*alpha_dense);
753 auto b_view = gko::ext::kokkos::map_data<memory_space>(*b_dense);
754 auto beta_view = gko::ext::kokkos::map_data<memory_space>(*beta_dense);
755 auto x_view = gko::ext::kokkos::map_data<memory_space>(*x_dense);
756 Kokkos::View<double**, Kokkos::LayoutRight, memory_space>
757 applied(
"similie_matrix_free_linop_apply", x_view.extent(0), x_view.extent(1));
758 if constexpr (workspace_traits::enabled) {
759 if constexpr (
requires(
760 OperatorModel
const& model,
762 decltype(b_view) input,
763 decltype(applied) output,
764 workspace_type& workspace) {
765 model.apply(ex, input, output, workspace);
767 if (uses_precomputed_matrix_free_stencils(*m_operator_model)) {
768 m_operator_model->apply(m_exec_space, b_view, applied);
770 if (m_workspace ==
nullptr) {
771 m_workspace = std::make_shared<workspace_type>(
772 m_operator_model->create_matrix_free_workspace(m_exec_space));
774 m_operator_model->apply(m_exec_space, b_view, applied, *m_workspace);
777 m_operator_model->apply(m_exec_space, b_view, applied);
780 m_operator_model->apply(m_exec_space, b_view, applied);
782 Kokkos::parallel_for(
783 "similie_matrix_free_linop_advanced_apply",
784 Kokkos::MDRangePolicy<
787 2>>(m_exec_space, {0, 0}, {x_view.extent(0), x_view.extent(1)}),
788 KOKKOS_LAMBDA(std::size_t row, std::size_t column) {
789 x_view(row, column) = alpha_view(0, 0) * applied(row, column)
790 + beta_view(0, 0) * x_view(row, column);
792 m_exec_space.fence();
793 auto const apply_end = std::chrono::steady_clock::now();
794 ++m_advanced_apply_count;
795 m_advanced_apply_duration += std::chrono::duration<double>(apply_end - apply_start).count();
796 log_apply_progress(
"advanced", m_advanced_apply_count, m_advanced_apply_duration);
799 void log_apply_start(
char const* apply_kind, std::size_t apply_count)
const
801 if (!env_flag_enabled(
"SIMILIE_MATRIX_FREE_APPLY_PROGRESS")) {
804 std::size_t
const stride =
static_cast<std::size_t
>(
805 std::max(1, env_int_or(
"SIMILIE_MATRIX_FREE_APPLY_PROGRESS_STRIDE", 10)));
806 if (apply_count == 1 || apply_count % stride == 0) {
807 std::cout <<
"SimiLie matrix-free apply start: kind=" << apply_kind
808 <<
" count=" << apply_count << std::endl;
812 void log_apply_progress(
char const* apply_kind, std::size_t apply_count,
double duration)
const
814 if (!env_flag_enabled(
"SIMILIE_MATRIX_FREE_APPLY_PROGRESS")) {
817 std::size_t
const stride =
static_cast<std::size_t
>(
818 std::max(1, env_int_or(
"SIMILIE_MATRIX_FREE_APPLY_PROGRESS_STRIDE", 10)));
819 if (apply_count == 1 || apply_count % stride == 0) {
820 std::cout <<
"SimiLie matrix-free apply progress: kind=" << apply_kind
821 <<
" count=" << apply_count <<
" cumulative_duration=" << duration
826 void log_timing()
const
828 if (!env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
831 std::cout <<
"SimiLie matrix-free operator timing: apply_count=" << m_apply_count
832 <<
" apply_duration=" << m_apply_duration
833 <<
" advanced_apply_count=" << m_advanced_apply_count
834 <<
" advanced_apply_duration=" << m_advanced_apply_duration <<
'\n';
838template <
class ExecSpace,
class OperatorModel,
class StateView>
839class StateDependentMatrixFreeLinOp
840 :
public gko::EnableLinOp<StateDependentMatrixFreeLinOp<ExecSpace, OperatorModel, StateView>>
842 using value_type = double;
843 using dense_type = gko::matrix::Dense<value_type>;
844 using memory_space =
typename ExecSpace::memory_space;
846 = gko::EnableLinOp<StateDependentMatrixFreeLinOp<ExecSpace, OperatorModel, StateView>>;
848 ExecSpace m_exec_space;
849 std::shared_ptr<OperatorModel const> m_operator_model;
853 explicit StateDependentMatrixFreeLinOp(std::shared_ptr<gko::Executor const> exec)
854 : base_type(std::move(exec))
861 StateDependentMatrixFreeLinOp(
862 std::shared_ptr<gko::Executor const> exec,
863 ExecSpace exec_space,
864 std::shared_ptr<OperatorModel const> operator_model,
866 : base_type(exec, gko::dim<2>(operator_model->size(), operator_model->size()))
867 , m_exec_space(exec_space)
868 , m_operator_model(std::move(operator_model))
874 void apply_impl(gko::LinOp
const* b, gko::LinOp* x)
const override
876 auto const* b_dense =
dynamic_cast<dense_type const*
>(b);
877 auto* x_dense =
dynamic_cast<dense_type*
>(x);
878 if (b_dense ==
nullptr || x_dense ==
nullptr) {
879 throw std::invalid_argument(
880 "StateDependentMatrixFreeLinOp expects dense inputs and outputs");
882 auto b_view = gko::ext::kokkos::map_data<memory_space>(*b_dense);
883 auto x_view = gko::ext::kokkos::map_data<memory_space>(*x_dense);
884 apply_jacobian(m_exec_space, *m_operator_model, m_state, b_view, x_view);
888 gko::LinOp
const* alpha,
890 gko::LinOp
const* beta,
891 gko::LinOp* x)
const override
893 auto const* alpha_dense =
dynamic_cast<dense_type const*
>(alpha);
894 auto const* b_dense =
dynamic_cast<dense_type const*
>(b);
895 auto const* beta_dense =
dynamic_cast<dense_type const*
>(beta);
896 auto* x_dense =
dynamic_cast<dense_type*
>(x);
897 if (alpha_dense ==
nullptr || b_dense ==
nullptr || beta_dense ==
nullptr
898 || x_dense ==
nullptr) {
899 throw std::invalid_argument(
900 "StateDependentMatrixFreeLinOp expects dense alpha, beta, input, and output");
902 auto alpha_view = gko::ext::kokkos::map_data<memory_space>(*alpha_dense);
903 auto b_view = gko::ext::kokkos::map_data<memory_space>(*b_dense);
904 auto beta_view = gko::ext::kokkos::map_data<memory_space>(*beta_dense);
905 auto x_view = gko::ext::kokkos::map_data<memory_space>(*x_dense);
906 Kokkos::View<double**, Kokkos::LayoutRight, memory_space>
907 applied(
"similie_state_dependent_linop_apply", x_view.extent(0), x_view.extent(1));
908 apply_jacobian(m_exec_space, *m_operator_model, m_state, b_view, applied);
909 Kokkos::parallel_for(
910 "similie_state_dependent_linop_advanced_apply",
911 Kokkos::MDRangePolicy<
914 2>>(m_exec_space, {0, 0}, {x_view.extent(0), x_view.extent(1)}),
915 KOKKOS_LAMBDA(std::size_t row, std::size_t column) {
916 x_view(row, column) = alpha_view(0, 0) * applied(row, column)
917 + beta_view(0, 0) * x_view(row, column);
919 m_exec_space.fence();
923inline auto build_jacobi_preconditioner_factory(
924 std::shared_ptr<gko::Executor const>
const& gko_exec,
925 StrongFormulationSolverSettings
const& settings)
927 return gko::preconditioner::Jacobi<double>::build()
928 .with_max_block_size(settings.jacobi_max_block_size)
932inline PreconditionerType selected_preconditioner(StrongFormulationSolverSettings
const& settings)
934 char const*
const value = std::getenv(
"SIMILIE_PRECONDITIONER");
935 if (value ==
nullptr || value[0] ==
'\0') {
936 if (env_flag_enabled(
"SIMILIE_DISABLE_JACOBI_PRECONDITIONER")) {
939 return settings.preconditioner;
944inline std::shared_ptr<gko::LinOp const> build_identity_preconditioner(
945 std::shared_ptr<gko::Executor const>
const& gko_exec,
948 return gko::matrix::Identity<double>::create(gko_exec, size);
951inline std::shared_ptr<gko::LinOp const> build_preconditioner(
952 std::shared_ptr<gko::Executor const>
const& gko_exec,
953 std::shared_ptr<gko::LinOp const>
const& matrix,
954 StrongFormulationSolverSettings
const& settings)
957 std::cout <<
"SimiLie Ginkgo preconditioner: " <<
preconditioner_name(preconditioner) <<
'\n';
958 switch (preconditioner) {
960 return build_identity_preconditioner(gko_exec, matrix->get_size()[0]);
962 auto preconditioner_factory = build_jacobi_preconditioner_factory(gko_exec, settings);
963 return std::shared_ptr<gko::LinOp const>(
964 preconditioner_factory->generate(matrix).release());
967 auto preconditioner_factory
968 = gko::preconditioner::SpdIsai<double, gko::int32>::build().on(gko_exec);
969 return std::shared_ptr<gko::LinOp const>(
970 preconditioner_factory->generate(matrix).release());
973 auto preconditioner_factory
974 = gko::preconditioner::GeneralIsai<double, gko::int32>::build().on(gko_exec);
975 return std::shared_ptr<gko::LinOp const>(
976 preconditioner_factory->generate(matrix).release());
979 auto preconditioner_factory = gko::preconditioner::GaussSeidel<double, gko::int32>::build()
980 .with_symmetric(
true)
982 return std::shared_ptr<gko::LinOp const>(
983 preconditioner_factory->generate(matrix).release());
986 auto preconditioner_factory = gko::preconditioner::Sor<double, gko::int32>::build()
987 .with_symmetric(
true)
988 .with_relaxation_factor(env_double_or(
989 "SIMILIE_SOR_RELAXATION_FACTOR",
990 settings.sor_relaxation_factor))
992 return std::shared_ptr<gko::LinOp const>(
993 preconditioner_factory->generate(matrix).release());
996 auto jacobi_factory = build_jacobi_preconditioner_factory(gko_exec, settings);
997 auto iterations_criterion = gko::stop::Iteration::build()
998 .with_max_iters(settings.chebyshev_iterations)
1000 auto preconditioner_factory
1001 = gko::solver::Chebyshev<double>::build()
1002 .with_criteria(std::move(iterations_criterion))
1003 .with_preconditioner(std::move(jacobi_factory))
1004 .with_foci(settings.chebyshev_lower_bound, settings.chebyshev_upper_bound)
1005 .with_default_initial_guess(gko::solver::initial_guess_mode::zero)
1007 return std::shared_ptr<gko::LinOp const>(
1008 preconditioner_factory->generate(matrix).release());
1011 auto jacobi_factory = build_jacobi_preconditioner_factory(gko_exec, settings);
1012 auto iterations_criterion
1013 = gko::stop::Iteration::build().with_max_iters(settings.ir_iterations).on(gko_exec);
1014 auto preconditioner_factory
1015 = gko::solver::Ir<double>::build()
1016 .with_criteria(std::move(iterations_criterion))
1017 .with_solver(std::move(jacobi_factory))
1018 .with_relaxation_factor(settings.ir_relaxation_factor)
1019 .with_default_initial_guess(gko::solver::initial_guess_mode::zero)
1021 return std::shared_ptr<gko::LinOp const>(
1022 preconditioner_factory->generate(matrix).release());
1025 throw std::runtime_error(
"unsupported strong-formulation preconditioner");
1028template <
class ExecSpace,
class OperatorModel,
class RHSViewType,
class SolutionViewType>
1029StrongFormulationSolverDiagnostics solve_linearized_system(
1030 ExecSpace exec_space,
1031 std::shared_ptr<gko::Executor const>
const& gko_exec,
1032 OperatorModel
const& operator_model,
1034 SolutionViewType solution,
1035 StrongFormulationSolverSettings
const& settings,
1036 std::shared_ptr<gko::LinOp const>
const& assembled_matrix,
1037 std::shared_ptr<gko::LinOp const>
const& preconditioner)
1039 StrongFormulationSolverDiagnostics diagnostics;
1040 if (env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1041 std::cout <<
"SimiLie linear solve stage: computing initial residual norm" << std::endl;
1043 auto const initial_residual_start = std::chrono::steady_clock::now();
1044 diagnostics.initial_residual_l2 = residual_norm_l2(exec_space, rhs);
1045 auto const initial_residual_end = std::chrono::steady_clock::now();
1046 if (env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1047 std::cout <<
"SimiLie linear solve stage: initial residual norm computed duration="
1048 << std::chrono::duration<double>(initial_residual_end - initial_residual_start)
1052 diagnostics.final_residual_l2 = diagnostics.initial_residual_l2;
1053 diagnostics.final_relative_residual = diagnostics.initial_residual_l2 == 0.0 ? 0.0 : 1.0;
1054 if (diagnostics.initial_residual_l2 == 0.0) {
1055 fill(exec_space, solution, 0.0);
1059 if (env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1060 std::cout <<
"SimiLie linear solve stage: building solver factory" << std::endl;
1062 auto const solver_factory_start = std::chrono::steady_clock::now();
1063 auto residual_criterion = gko::stop::ResidualNorm<double>::build()
1064 .with_reduction_factor(settings.relative_tolerance)
1066 auto iterations_criterion
1067 = gko::stop::Iteration::build().with_max_iters(settings.max_iterations).on(gko_exec);
1068 std::unique_ptr<gko::LinOpFactory> solver_factory;
1069 if (detail::env_value_equals(
"SIMILIE_SOLVER",
"minres")) {
1070 solver_factory = gko::solver::Minres<double>::build()
1071 .with_generated_preconditioner(preconditioner)
1073 std::move(residual_criterion),
1074 std::move(iterations_criterion))
1076 }
else if (detail::env_value_equals(
"SIMILIE_SOLVER",
"fcg")) {
1077 solver_factory = gko::solver::Fcg<double>::build()
1078 .with_generated_preconditioner(preconditioner)
1080 std::move(residual_criterion),
1081 std::move(iterations_criterion))
1083 }
else if (detail::env_value_equals(
"SIMILIE_SOLVER",
"gmres")) {
1084 solver_factory = gko::solver::Gmres<double>::build()
1085 .with_generated_preconditioner(preconditioner)
1087 std::move(residual_criterion),
1088 std::move(iterations_criterion))
1090 static_cast<gko::size_type
>(
1091 env_int_or(
"SIMILIE_GMRES_KRYLOV_DIM", 100)))
1093 }
else if (detail::env_value_equals(
"SIMILIE_SOLVER",
"bicgstab")) {
1094 solver_factory = gko::solver::Bicgstab<double>::build()
1095 .with_generated_preconditioner(preconditioner)
1097 std::move(residual_criterion),
1098 std::move(iterations_criterion))
1100 }
else if (detail::env_value_equals(
"SIMILIE_SOLVER",
"gcr")) {
1102 = gko::solver::Gcr<double>::build()
1103 .with_generated_preconditioner(preconditioner)
1105 std::move(residual_criterion),
1106 std::move(iterations_criterion))
1108 static_cast<gko::size_type
>(
1109 std::max(1, env_int_or(
"SIMILIE_GCR_KRYLOV_DIM", 100))))
1111 }
else if (detail::env_value_equals(
"SIMILIE_SOLVER",
"idr")) {
1113 = gko::solver::Idr<double>::build()
1114 .with_generated_preconditioner(preconditioner)
1116 std::move(residual_criterion),
1117 std::move(iterations_criterion))
1119 static_cast<gko::size_type
>(
1120 std::max(1, env_int_or(
"SIMILIE_IDR_SUBSPACE_DIM", 8))))
1121 .with_deterministic(
true)
1124 solver_factory = gko::solver::Cg<double>::build()
1125 .with_generated_preconditioner(preconditioner)
1127 std::move(residual_criterion),
1128 std::move(iterations_criterion))
1131 auto const solver_factory_end = std::chrono::steady_clock::now();
1132 if (env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1133 std::cout <<
"SimiLie linear solve stage: solver factory built duration="
1134 << std::chrono::duration<double>(solver_factory_end - solver_factory_start)
1138 std::shared_ptr<MatrixFreeLinOp<ExecSpace, OperatorModel>
const> matrix_free_system_matrix;
1139 std::shared_ptr<gko::LinOp const> system_matrix;
1140 if (settings.use_matrix_free) {
1141 if (env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1142 std::cout <<
"SimiLie linear solve stage: constructing matrix-free LinOp" << std::endl;
1144 auto const matrix_free_linop_start = std::chrono::steady_clock::now();
1145 auto operator_model_ptr
1146 = std::shared_ptr<OperatorModel const>(&operator_model, [](OperatorModel
const*) {
1148 matrix_free_system_matrix = std::make_shared<MatrixFreeLinOp<
1150 OperatorModel>>(gko_exec, exec_space, std::move(operator_model_ptr));
1151 auto const matrix_free_linop_end = std::chrono::steady_clock::now();
1152 if (env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1153 std::cout <<
"SimiLie linear solve stage: matrix-free LinOp constructed duration="
1154 << std::chrono::duration<double>(
1155 matrix_free_linop_end - matrix_free_linop_start)
1159 system_matrix = matrix_free_system_matrix;
1161 system_matrix = assembled_matrix;
1163 if (settings.use_matrix_free && matrix_free_system_matrix !=
nullptr
1164 && assembled_matrix !=
nullptr && env_flag_enabled(
"SIMILIE_COMPARE_MATRIX_FREE_APPLY")) {
1165 using memory_space =
typename RHSViewType::memory_space;
1166 Kokkos::View<double**, memory_space> matrix_free_applied(
1167 "similie_compare_matrix_free_applied",
1170 Kokkos::View<double**, memory_space> assembled_applied(
1171 "similie_compare_assembled_applied",
1174 Kokkos::View<double**, memory_space>
1175 difference(
"similie_compare_apply_difference", rhs.extent(0), rhs.extent(1));
1176 auto probe_gko = to_gko_dense(gko_exec, rhs);
1177 auto matrix_free_applied_gko = to_gko_dense(gko_exec, matrix_free_applied);
1178 auto assembled_applied_gko = to_gko_dense(gko_exec, assembled_applied);
1179 matrix_free_system_matrix->apply(probe_gko.dense, matrix_free_applied_gko.dense);
1180 assembled_matrix->apply(probe_gko.dense, assembled_applied_gko.dense);
1181 gko_exec->synchronize();
1182 copy_back_from_gko_dense_bridge(matrix_free_applied, matrix_free_applied_gko);
1183 copy_back_from_gko_dense_bridge(assembled_applied, assembled_applied_gko);
1184 copy(exec_space, difference, matrix_free_applied);
1185 axpy_inplace(exec_space, difference, -1.0, assembled_applied);
1186 double const difference_norm = residual_norm_l2(exec_space, difference);
1187 double const assembled_norm = residual_norm_l2(exec_space, assembled_applied);
1188 std::cout <<
"SimiLie matrix-free apply comparison: difference_l2=" << difference_norm
1189 <<
" assembled_l2=" << assembled_norm <<
" relative_difference="
1190 << (assembled_norm == 0.0 ? 0.0 : difference_norm / assembled_norm) <<
'\n';
1191 if (rhs.extent(1) == 1 && rhs.extent(0) % 3 == 0) {
1192 auto const difference_host
1193 = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), difference);
1194 auto const assembled_host
1195 = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), assembled_applied);
1196 std::array<double, 3> component_difference_norms {};
1197 std::array<double, 3> component_assembled_norms {};
1198 for (std::size_t row = 0; row < rhs.extent(0); ++row) {
1199 std::size_t
const component = row % 3;
1200 component_difference_norms[component]
1201 += difference_host(row, 0) * difference_host(row, 0);
1202 component_assembled_norms[component]
1203 += assembled_host(row, 0) * assembled_host(row, 0);
1205 std::cout <<
"SimiLie matrix-free apply component comparison:";
1206 for (std::size_t component = 0; component < 3; ++component) {
1207 double const component_difference
1208 = std::sqrt(component_difference_norms[component]);
1209 double const component_assembled = std::sqrt(component_assembled_norms[component]);
1210 std::cout <<
" component" << component <<
"_difference_l2=" << component_difference
1211 <<
" component" << component <<
"_relative_difference="
1212 << (component_assembled == 0.0
1214 : component_difference / component_assembled);
1219 if (env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1220 std::cout <<
"SimiLie linear solve stage: generating solver" << std::endl;
1222 auto const solver_generate_start = std::chrono::steady_clock::now();
1223 auto solver = solver_factory->generate(system_matrix);
1224 auto const solver_generate_end = std::chrono::steady_clock::now();
1225 if (env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1226 std::cout <<
"SimiLie linear solve stage: solver generated duration="
1227 << std::chrono::duration<double>(solver_generate_end - solver_generate_start)
1231 auto convergence_logger = std::shared_ptr<gko::log::Convergence<double>>(
1232 gko::log::Convergence<double>::create().release());
1233 solver->add_logger(convergence_logger);
1234 std::shared_ptr<SolverProgressLogger> progress_logger;
1235 if (
unsigned int const progress_stride = solver_progress_stride(); progress_stride != 0U) {
1236 progress_logger = std::make_shared<SolverProgressLogger>(
1237 gko_exec->get_master(),
1238 diagnostics.initial_residual_l2,
1240 solver->add_logger(progress_logger);
1243 fill(exec_space, solution, 0.0);
1244 auto rhs_gko = to_gko_dense(gko_exec, rhs);
1245 auto solution_gko = to_gko_dense(gko_exec, solution);
1246 auto const optimization_start = std::chrono::steady_clock::now();
1247 if (env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1248 std::cout <<
"SimiLie linear solve stage: applying solver" << std::endl;
1250 solver->apply(rhs_gko.dense, solution_gko.dense);
1251 gko_exec->synchronize();
1252 copy_back_from_gko_dense_bridge(solution, solution_gko);
1253 auto const optimization_end = std::chrono::steady_clock::now();
1254 diagnostics.duration
1255 = std::chrono::duration<double>(optimization_end - optimization_start).count();
1256 if (matrix_free_system_matrix !=
nullptr) {
1257 matrix_free_system_matrix->log_timing();
1259 if (progress_logger !=
nullptr) {
1260 solver->remove_logger(progress_logger);
1262 solver->remove_logger(convergence_logger);
1263 diagnostics.iterations =
static_cast<unsigned int>(convergence_logger->get_num_iterations());
1264 diagnostics.converged = convergence_logger->has_converged();
1265 auto residual_norm_dense =
dynamic_cast<gko::matrix::Dense<double> const*
>(
1266 convergence_logger->get_residual_norm());
1267 if (residual_norm_dense !=
nullptr) {
1268 auto host_dense = gko::matrix::Dense<
1269 double>::create(gko_exec->get_master(), residual_norm_dense->get_size());
1270 residual_norm_dense->convert_to(host_dense.get());
1271 diagnostics.final_residual_l2 = host_dense->at(0, 0);
1273 diagnostics.final_relative_residual
1274 = diagnostics.initial_residual_l2 == 0.0
1276 : diagnostics.final_residual_l2 / diagnostics.initial_residual_l2;
1277 if (env_flag_enabled(
"SIMILIE_TRUE_RESIDUAL_DIAGNOSTICS")) {
1278 using memory_space =
typename SolutionViewType::memory_space;
1279 Kokkos::View<double**, memory_space>
1280 applied(
"similie_true_residual_applied", rhs.extent(0), rhs.extent(1));
1281 Kokkos::View<double**, memory_space>
1282 true_residual(
"similie_true_residual", rhs.extent(0), rhs.extent(1));
1283 if (settings.use_matrix_free && matrix_free_system_matrix !=
nullptr) {
1284 auto solution_gko_for_residual = to_gko_dense(gko_exec, solution);
1285 auto applied_gko_for_residual = to_gko_dense(gko_exec, applied);
1286 matrix_free_system_matrix
1287 ->apply(solution_gko_for_residual.dense, applied_gko_for_residual.dense);
1288 gko_exec->synchronize();
1289 copy_back_from_gko_dense_bridge(applied, applied_gko_for_residual);
1291 operator_model.apply(exec_space, solution, applied);
1293 copy(exec_space, true_residual, rhs);
1294 axpy_inplace(exec_space, true_residual, -1.0, applied);
1295 double const true_residual_l2 = residual_norm_l2(exec_space, true_residual);
1296 double const true_relative_residual
1297 = diagnostics.initial_residual_l2 == 0.0
1299 : true_residual_l2 / diagnostics.initial_residual_l2;
1300 std::cout <<
"SimiLie true residual diagnostics: residual_l2=" << true_residual_l2
1301 <<
" relative_residual=" << true_relative_residual <<
'\n';
1308template <
class ExecSpace,
class OperatorModel,
class RHSViewType,
class SolutionViewType>
1310 ExecSpace exec_space,
1311 OperatorModel
const& operator_model,
1313 SolutionViewType solution,
1316 StrongFormulationSolverDiagnostics diagnostics;
1318 detail::fill(exec_space, solution, 0.0);
1319 auto const gko_exec = gko::ext::kokkos::create_executor(exec_space);
1320 if constexpr (OperatorModel::IS_LINEAR) {
1321 diagnostics.initial_residual_l2 = detail::residual_norm_l2(exec_space, rhs);
1322 diagnostics.final_residual_l2 = diagnostics.initial_residual_l2;
1323 diagnostics.final_relative_residual = diagnostics.initial_residual_l2 == 0.0 ? 0.0 : 1.0;
1324 if (diagnostics.initial_residual_l2 == 0.0) {
1327 auto const matrix_build_start = std::chrono::steady_clock::now();
1328 std::shared_ptr<gko::matrix::Csr<double, gko::int32>> matrix;
1329 auto const matrix_build_end = std::chrono::steady_clock::now();
1330 std::shared_ptr<gko::LinOp const> preconditioner;
1331 PreconditionerType const preconditioner_type = detail::selected_preconditioner(settings);
1333 std::cout <<
"SimiLie Ginkgo preconditioner: "
1335 preconditioner = detail::build_identity_preconditioner(gko_exec, operator_model.size());
1337 matrix = detail::build_matrix(gko_exec, operator_model);
1338 auto const actual_matrix_build_end = std::chrono::steady_clock::now();
1339 preconditioner = detail::build_preconditioner(
1341 std::static_pointer_cast<gko::LinOp const>(matrix),
1343 if (detail::env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1344 std::cout <<
"SimiLie linear setup timing: matrix_build_duration="
1345 << std::chrono::duration<double>(
1346 actual_matrix_build_end - matrix_build_start)
1348 <<
" preconditioner_build_duration="
1349 << std::chrono::duration<double>(
1350 std::chrono::steady_clock::now() - actual_matrix_build_end)
1354 return detail::solve_linearized_system(
1361 std::static_pointer_cast<gko::LinOp const>(matrix),
1364 auto const preconditioner_build_end = std::chrono::steady_clock::now();
1365 if (detail::env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1366 std::cout <<
"SimiLie linear setup timing: matrix_build_duration="
1367 << std::chrono::duration<double>(matrix_build_end - matrix_build_start)
1369 <<
" preconditioner_build_duration="
1370 << std::chrono::duration<double>(preconditioner_build_end - matrix_build_end)
1374 return detail::solve_linearized_system(
1381 std::static_pointer_cast<gko::LinOp const>(matrix),
1384 using memory_space =
typename SolutionViewType::memory_space;
1385 Kokkos::View<double**, memory_space>
1386 residual(
"similie_nonlinear_residual", rhs.extent(0), rhs.extent(1));
1387 Kokkos::View<double**, memory_space>
1388 operator_value(
"similie_nonlinear_operator_value", rhs.extent(0), rhs.extent(1));
1389 Kokkos::View<double**, memory_space>
1390 correction_rhs(
"similie_nonlinear_correction_rhs", rhs.extent(0), rhs.extent(1));
1391 Kokkos::View<double**, memory_space>
1392 delta(
"similie_nonlinear_delta", rhs.extent(0), rhs.extent(1));
1393 Kokkos::View<double**, memory_space> candidate_solution(
1394 "similie_nonlinear_candidate_solution",
1397 Kokkos::View<double**, memory_space> candidate_residual(
1398 "similie_nonlinear_candidate_residual",
1401 Kokkos::View<double**, memory_space> candidate_operator_value(
1402 "similie_nonlinear_candidate_operator_value",
1405 auto const optimization_start = std::chrono::steady_clock::now();
1406 diagnostics.converged =
false;
1408 operator_model.apply(exec_space, solution, operator_value);
1409 detail::copy(exec_space, residual, rhs);
1410 detail::axpy_inplace(exec_space, residual, -1.0, operator_value);
1411 diagnostics.initial_residual_l2 = detail::residual_norm_l2(exec_space, residual);
1412 diagnostics.final_residual_l2 = diagnostics.initial_residual_l2;
1413 diagnostics.final_relative_residual = diagnostics.initial_residual_l2 == 0.0 ? 0.0 : 1.0;
1414 if (diagnostics.initial_residual_l2 == 0.0) {
1415 diagnostics.converged =
true;
1419 constexpr unsigned int NONLINEAR_MAX_ITERS = 30U;
1420 for (
unsigned int iteration = 0; iteration < NONLINEAR_MAX_ITERS; ++iteration) {
1421 diagnostics.final_residual_l2 = detail::residual_norm_l2(exec_space, residual);
1422 diagnostics.final_relative_residual
1423 = diagnostics.initial_residual_l2 == 0.0
1425 : diagnostics.final_residual_l2 / diagnostics.initial_residual_l2;
1426 detail::log_nonlinear_progress(iteration, diagnostics);
1427 if (diagnostics.final_relative_residual <= settings.relative_tolerance) {
1428 diagnostics.converged =
true;
1432 auto matrix = detail::build_matrix(gko_exec, operator_model, solution);
1433 auto preconditioner = detail::build_preconditioner(
1435 std::static_pointer_cast<gko::LinOp const>(matrix),
1438 detail::copy(exec_space, correction_rhs, residual);
1439 if (settings.use_matrix_free) {
1440 using solver_type = gko::solver::Cg<double>;
1441 auto residual_criterion
1442 = gko::stop::ResidualNorm<double>::build()
1443 .with_reduction_factor(settings.relative_tolerance)
1445 auto iterations_criterion = gko::stop::Iteration::build()
1446 .with_max_iters(settings.max_iterations)
1448 auto solver_factory = solver_type::build()
1449 .with_generated_preconditioner(preconditioner)
1451 std::move(residual_criterion),
1452 std::move(iterations_criterion))
1454 auto operator_model_ptr = std::shared_ptr<
1455 OperatorModel
const>(&operator_model, [](OperatorModel
const*) {});
1456 auto system_matrix = std::shared_ptr<gko::LinOp const>(
1457 std::make_shared<detail::StateDependentMatrixFreeLinOp<
1465 auto solver = solver_factory->generate(system_matrix);
1466 auto convergence_logger = std::shared_ptr<gko::log::Convergence<double>>(
1467 gko::log::Convergence<double>::create().release());
1468 solver->add_logger(convergence_logger);
1469 std::shared_ptr<detail::SolverProgressLogger> progress_logger;
1470 if (
unsigned int const progress_stride = detail::solver_progress_stride();
1471 progress_stride != 0U) {
1472 progress_logger = std::make_shared<detail::SolverProgressLogger>(
1473 gko_exec->get_master(),
1474 detail::residual_norm_l2(exec_space, correction_rhs),
1476 solver->add_logger(progress_logger);
1478 detail::fill(exec_space, delta, 0.0);
1479 auto rhs_gko = detail::to_gko_dense(gko_exec, correction_rhs);
1480 auto delta_gko = detail::to_gko_dense(gko_exec, delta);
1481 if (detail::env_flag_enabled(
"SIMILIE_SOLVER_TIMING")) {
1482 std::cout <<
"SimiLie nonlinear linear solve stage: applying solver"
1485 solver->apply(rhs_gko.dense, delta_gko.dense);
1486 gko_exec->synchronize();
1487 detail::copy_back_from_gko_dense_bridge(delta, delta_gko);
1488 if (progress_logger !=
nullptr) {
1489 solver->remove_logger(progress_logger);
1491 solver->remove_logger(convergence_logger);
1492 diagnostics.iterations
1493 +=
static_cast<unsigned int>(convergence_logger->get_num_iterations());
1495 auto inner = detail::solve_linearized_system(
1502 std::static_pointer_cast<gko::LinOp const>(matrix),
1504 diagnostics.iterations += inner.iterations;
1506 double const current_residual_l2 = detail::residual_norm_l2(exec_space, residual);
1508 double best_alpha = 0.0;
1509 double best_residual_l2 = std::numeric_limits<double>::infinity();
1510 unsigned int const max_line_search_steps =
static_cast<unsigned int>(
1511 std::max(1, detail::env_int_or(
"SIMILIE_NONLINEAR_LINE_SEARCH_STEPS", 8)));
1512 double const line_search_reduction = std::
1513 clamp(detail::env_double_or(
"SIMILIE_NONLINEAR_LINE_SEARCH_REDUCTION", 0.5),
1516 for (
unsigned int line_search_step = 0; line_search_step < max_line_search_steps;
1517 ++line_search_step) {
1518 detail::copy(exec_space, candidate_solution, solution);
1519 detail::axpy_inplace(exec_space, candidate_solution, alpha, delta);
1520 operator_model.apply(exec_space, candidate_solution, candidate_operator_value);
1521 detail::copy(exec_space, candidate_residual, rhs);
1522 detail::axpy_inplace(
1526 candidate_operator_value);
1527 double const candidate_residual_l2
1528 = detail::residual_norm_l2(exec_space, candidate_residual);
1529 if (candidate_residual_l2 < best_residual_l2) {
1531 best_residual_l2 = candidate_residual_l2;
1533 if (candidate_residual_l2 < current_residual_l2) {
1536 alpha *= line_search_reduction;
1538 if (best_alpha != alpha) {
1540 detail::copy(exec_space, candidate_solution, solution);
1541 detail::axpy_inplace(exec_space, candidate_solution, alpha, delta);
1542 operator_model.apply(exec_space, candidate_solution, candidate_operator_value);
1543 detail::copy(exec_space, candidate_residual, rhs);
1544 detail::axpy_inplace(
1548 candidate_operator_value);
1549 best_residual_l2 = detail::residual_norm_l2(exec_space, candidate_residual);
1551 if (detail::solver_progress_enabled()) {
1552 std::cout <<
"SimiLie nonlinear line search: iteration=" << iteration
1553 <<
" alpha=" << alpha <<
" residual_l2=" << best_residual_l2
1554 <<
" previous_residual_l2=" << current_residual_l2 << std::endl;
1556 detail::copy(exec_space, solution, candidate_solution);
1557 detail::copy(exec_space, operator_value, candidate_operator_value);
1558 detail::copy(exec_space, residual, candidate_residual);
1560 auto const optimization_end = std::chrono::steady_clock::now();
1561 diagnostics.duration
1562 = std::chrono::duration<double>(optimization_end - optimization_start).count();
1563 diagnostics.final_residual_l2 = detail::residual_norm_l2(exec_space, residual);
1564 diagnostics.final_relative_residual
1565 = diagnostics.initial_residual_l2 == 0.0
1567 : diagnostics.final_residual_l2 / diagnostics.initial_residual_l2;