SimiLie
Loading...
Searching...
No Matches
minimize_strong_formulation_residual.hpp
1// SPDX-FileCopyrightText: 2026 Baptiste Legouix
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4#pragma once
5
6#include <algorithm>
7#include <array>
8#include <chrono>
9#include <cmath>
10#include <cstdint>
11#include <cstdlib>
12#include <iostream>
13#include <limits>
14#include <memory>
15#include <optional>
16#include <stdexcept>
17#include <string_view>
18#include <type_traits>
19#include <unordered_map>
20#include <vector>
21
22#include <ginkgo/core/base/lin_op.hpp>
23#include <ginkgo/core/log/convergence.hpp>
24#include <ginkgo/core/matrix/csr.hpp>
25#include <ginkgo/core/matrix/dense.hpp>
26#include <ginkgo/core/matrix/identity.hpp>
27#include <ginkgo/core/preconditioner/gauss_seidel.hpp>
28#include <ginkgo/core/preconditioner/isai.hpp>
29#include <ginkgo/core/preconditioner/jacobi.hpp>
30#include <ginkgo/core/preconditioner/sor.hpp>
31#include <ginkgo/core/solver/bicgstab.hpp>
32#include <ginkgo/core/solver/cg.hpp>
33#include <ginkgo/core/solver/chebyshev.hpp>
34#include <ginkgo/core/solver/fcg.hpp>
35#include <ginkgo/core/solver/gcr.hpp>
36#include <ginkgo/core/solver/gmres.hpp>
37#include <ginkgo/core/solver/idr.hpp>
38#include <ginkgo/core/solver/ir.hpp>
39#include <ginkgo/core/solver/minres.hpp>
40#include <ginkgo/core/stop/iteration.hpp>
41#include <ginkgo/core/stop/residual_norm.hpp>
42#include <ginkgo/extensions/kokkos.hpp>
43
44#include <Kokkos_Core.hpp>
45
47
53
64
65inline constexpr std::string_view preconditioner_name(PreconditionerType preconditioner)
66{
67 switch (preconditioner) {
69 return "Identity";
71 return "Jacobi";
73 return "SpdIsai";
75 return "SymmetricGaussSeidel";
77 return "Ssor";
79 return "ChebyshevJacobi";
81 return "IrJacobi";
83 return "GeneralIsai";
84 }
85 return "Jacobi";
86}
87
88inline PreconditionerType parse_preconditioner(std::string_view name)
89{
90 if (name == "Identity" || name == "identity") {
92 }
93 if (name == "Jacobi" || name == "jacobi") {
95 }
96 if (name == "SpdIsai" || name == "spd-isai" || name == "spd_isai") {
98 }
99 if (name == "SymmetricGaussSeidel" || name == "symmetric-gauss-seidel"
100 || name == "symmetric_gauss_seidel") {
102 }
103 if (name == "Ssor" || name == "SSOR" || name == "ssor") {
105 }
106 if (name == "ChebyshevJacobi" || name == "chebyshev-jacobi" || name == "chebyshev_jacobi") {
108 }
109 if (name == "IrJacobi" || name == "ir-jacobi" || name == "ir_jacobi") {
111 }
112 if (name == "GeneralIsai" || name == "general-isai" || name == "general_isai"
113 || name == "isai") {
115 }
116 throw std::runtime_error("unknown strong-formulation preconditioner: " + std::string(name));
117}
118
135
137{
138 unsigned int iterations = 0U;
140 double final_residual_l2 = 0.0;
142 double duration = 0.0;
143 bool converged = true;
144};
145
146namespace detail {
147
148inline unsigned int solver_progress_stride()
149{
150 char const* const value = std::getenv("SIMILIE_SOLVER_PROGRESS_STRIDE");
151 if (value == nullptr || value[0] == '\0') {
152 return 1000U;
153 }
154
155 char* parse_end = nullptr;
156 unsigned long const parsed = std::strtoul(value, &parse_end, 10);
157 if (parse_end == value) {
158 return 1U;
159 }
160 if (parsed == 0UL) {
161 return 0U;
162 }
163 if (parsed > std::numeric_limits<unsigned int>::max()) {
164 return std::numeric_limits<unsigned int>::max();
165 }
166 return static_cast<unsigned int>(parsed);
167}
168
169inline bool solver_progress_enabled()
170{
171 return solver_progress_stride() != 0U;
172}
173
174inline bool env_flag_enabled(char const* name)
175{
176 char const* const value = std::getenv(name);
177 return value != nullptr && value[0] != '\0' && value[0] != '0';
178}
179
180inline bool env_value_equals(char const* name, char const* expected)
181{
182 char const* const value = std::getenv(name);
183 return value != nullptr && std::string_view(value) == expected;
184}
185
186inline double env_double_or(char const* name, double default_value)
187{
188 char const* const value = std::getenv(name);
189 if (value == nullptr || value[0] == '\0') {
190 return default_value;
191 }
192 char* parse_end = nullptr;
193 double const parsed = std::strtod(value, &parse_end);
194 if (parse_end == value) {
195 return default_value;
196 }
197 return parsed;
198}
199
200inline int env_int_or(char const* name, int default_value)
201{
202 char const* const value = std::getenv(name);
203 if (value == nullptr || value[0] == '\0') {
204 return default_value;
205 }
206 char* parse_end = nullptr;
207 long const parsed = std::strtol(value, &parse_end, 10);
208 if (parse_end == value) {
209 return default_value;
210 }
211 if (parsed > std::numeric_limits<int>::max()) {
212 return std::numeric_limits<int>::max();
213 }
214 if (parsed < std::numeric_limits<int>::min()) {
215 return std::numeric_limits<int>::min();
216 }
217 return static_cast<int>(parsed);
218}
219
220template <class MatrixData>
221void log_matrix_diagnostics(MatrixData const& matrix_data)
222{
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;
233 } else {
234 off_diagonal_abs_row_sum[static_cast<std::size_t>(entry.row)] += std::abs(entry.value);
235 }
236 }
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) {
241 if (value < 0.0) {
242 ++negative_diagonal_count;
243 }
244 if (value == 0.0) {
245 ++zero_diagonal_count;
246 }
247 min_diagonal = std::min(min_diagonal, value);
248 max_diagonal = std::max(max_diagonal, value);
249 }
250 for (std::size_t row = 0; row < size; ++row) {
251 if (diagonal[row] == 0.0) {
252 continue;
253 }
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);
259 }
260
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);
271 };
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))]
276 += entry.value;
277 }
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;
287 }
288 double const asymmetry = std::abs(value - transposed_value);
289 if (asymmetry
290 > 1.0e-10 * std::max({1.0, std::abs(value), std::abs(transposed_value)})) {
291 ++asymmetric_entry_count;
292 }
293 max_abs_asymmetry = std::max(max_abs_asymmetry, asymmetry);
294 }
295 }
296
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';
308}
309
310class SolverProgressLogger final : public gko::log::Logger
311{
312public:
313 SolverProgressLogger(
314 std::shared_ptr<gko::Executor const> master_executor,
315 double initial_residual_l2,
316 unsigned int stride)
317 : Logger(gko::log::Logger::iteration_complete_mask)
318 , m_master_executor(std::move(master_executor))
319 , m_initial_residual_l2(initial_residual_l2)
320 , m_stride(stride)
321 {
322 }
323
324protected:
325 using gko::log::Logger::on_iteration_complete;
326
327 void on_iteration_complete(
328 gko::LinOp const*,
329 gko::LinOp const*,
330 gko::LinOp const*,
331 gko::size_type const& num_iterations,
332 gko::LinOp const* residual,
333 gko::LinOp const* residual_norm,
334 gko::LinOp const*,
335 gko::array<gko::stopping_status> const*,
336 bool) const override
337 {
338 if (m_stride == 0U || num_iterations % m_stride != 0U) {
339 return;
340 }
341 std::optional<double> residual_l2 = residual_norm_value(residual_norm);
342 if (!residual_l2.has_value()) {
343 residual_l2 = residual_vector_l2(residual);
344 }
345 if (!residual_l2.has_value()) {
346 return;
347 }
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
352 << std::endl;
353 }
354
355private:
356 std::optional<double> residual_norm_value(gko::LinOp const* residual_norm) const
357 {
358 auto const* residual_norm_dense
359 = dynamic_cast<gko::matrix::Dense<double> const*>(residual_norm);
360 if (residual_norm_dense == nullptr) {
361 return std::nullopt;
362 }
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);
367 }
368
369 std::optional<double> residual_vector_l2(gko::LinOp const* residual) const
370 {
371 auto const* residual_dense = dynamic_cast<gko::matrix::Dense<double> const*>(residual);
372 if (residual_dense == nullptr) {
373 return std::nullopt;
374 }
375 auto host_dense
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;
383 }
384 }
385 return std::sqrt(squared_norm);
386 }
387
388 std::shared_ptr<gko::Executor const> m_master_executor;
389 double m_initial_residual_l2;
390 unsigned int m_stride;
391};
392
393inline void log_nonlinear_progress(
394 unsigned int iteration,
395 StrongFormulationSolverDiagnostics const& diagnostics)
396{
397 if (!solver_progress_enabled()) {
398 return;
399 }
400 std::cout << "SimiLie nonlinear progress: iteration=" << iteration
401 << " residual_l2=" << diagnostics.final_residual_l2
402 << " relative_residual=" << diagnostics.final_relative_residual << std::endl;
403}
404
405template <class ExecSpace, class OperatorModel, class = void>
406struct MatrixFreeWorkspaceTraits
407{
408 static constexpr bool enabled = false;
409 struct type
410 {
411 };
412};
413
414template <class ExecSpace, class OperatorModel>
415struct MatrixFreeWorkspaceTraits<
416 ExecSpace,
417 OperatorModel,
418 std::void_t<decltype(std::declval<OperatorModel const&>().create_matrix_free_workspace(
419 std::declval<ExecSpace>()))>>
420{
421 static constexpr bool enabled = true;
422 using type = decltype(std::declval<OperatorModel const&>().create_matrix_free_workspace(
423 std::declval<ExecSpace>()));
424};
425
426template <class OperatorModel>
427bool uses_precomputed_matrix_free_stencils(OperatorModel const& operator_model)
428{
429 if constexpr (requires(OperatorModel const& model) { model.has_precomputed_stencils(); }) {
430 return operator_model.has_precomputed_stencils();
431 }
432 return false;
433}
434
435template <class ExecSpace, class ViewType1, class ViewType2>
436double dot(ExecSpace exec_space, ViewType1 lhs, ViewType2 rhs)
437{
438 double result = 0.0;
439 Kokkos::parallel_reduce(
440 "similie_dot",
441 Kokkos::MDRangePolicy<
442 ExecSpace,
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);
446 },
447 result);
448 exec_space.fence();
449 return result;
450}
451
452template <class ExecSpace, class ViewType>
453double residual_norm_l2(ExecSpace exec_space, ViewType residual)
454{
455 return std::sqrt(dot(exec_space, residual, residual));
456}
457
458template <class ExecSpace, class ViewType>
459void fill(ExecSpace exec_space, ViewType view, double value)
460{
461 Kokkos::parallel_for(
462 "similie_fill",
463 Kokkos::MDRangePolicy<
464 ExecSpace,
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; });
467 exec_space.fence();
468}
469
470template <class ExecSpace, class DestinationView, class SourceView>
471void copy(ExecSpace exec_space, DestinationView destination, SourceView source)
472{
473 Kokkos::parallel_for(
474 "similie_copy",
475 Kokkos::MDRangePolicy<
476 ExecSpace,
477 Kokkos::Rank<
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);
481 });
482 exec_space.fence();
483}
484
485template <class ExecSpace, class ViewType1, class ViewType2, class ViewType3>
486void update_axpby(
487 ExecSpace exec_space,
488 ViewType1 destination,
489 double alpha,
490 ViewType2 x,
491 double beta,
492 ViewType3 y)
493{
494 Kokkos::parallel_for(
495 "similie_axpby",
496 Kokkos::MDRangePolicy<
497 ExecSpace,
498 Kokkos::Rank<
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);
502 });
503 exec_space.fence();
504}
505
506template <class ExecSpace, class ViewType1, class ViewType2>
507void axpy_inplace(ExecSpace exec_space, ViewType1 destination, double alpha, ViewType2 source)
508{
509 Kokkos::parallel_for(
510 "similie_axpy_inplace",
511 Kokkos::MDRangePolicy<
512 ExecSpace,
513 Kokkos::Rank<
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);
517 });
518 exec_space.fence();
519}
520
521template <class KokkosViewType>
522struct GkoDenseHandle
523{
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**,
527 Kokkos::LayoutRight,
528 typename KokkosViewType::memory_space>>
529 owned_view;
530};
531
532template <class KokkosViewType>
533auto to_gko_dense(std::shared_ptr<gko::Executor const> const& gko_exec, KokkosViewType const& view)
534{
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**,
540 Kokkos::LayoutRight,
541 typename KokkosViewType::memory_space>;
542
543 GkoDenseHandle<KokkosViewType> handle;
544 if (view.stride(1) == 1) {
545 handle.dense = gko::matrix::Dense<value_type>::
546 create(gko_exec,
547 gko::dim<2>(view.extent(0), view.extent(1)),
548 gko::array<value_type>::view(gko_exec, view.span(), view.data()),
549 view.stride(0));
550 return handle;
551 }
552
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>::
556 create(gko_exec,
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));
561 return handle;
562}
563
564template <class DestinationView, class KokkosViewType>
565void copy_back_from_gko_dense_bridge(
566 DestinationView destination,
567 GkoDenseHandle<KokkosViewType> const& handle)
568{
569 if (handle.owned_view.has_value()) {
570 Kokkos::deep_copy(destination, *handle.owned_view);
571 }
572}
573
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)
577{
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];
584 }
585 for (std::size_t row = 0; row < matrix_data.size[0]; ++row) {
586 row_ptrs[row + 1] += row_ptrs[row];
587 }
588
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(),
602 0,
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;
611 });
612 Kokkos::DefaultHostExecutionSpace().fence();
613
614 return std::shared_ptr<matrix_type>(
615 matrix_type::
616 create(gko_exec,
617 matrix_data.size,
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()))
621 .release());
622}
623
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)
628{
629 auto matrix_data = assemble_matrix_data(operator_model);
630 if (env_flag_enabled("SIMILIE_MATRIX_DIAGNOSTICS")) {
631 log_matrix_diagnostics(matrix_data);
632 }
633 return csr_from_matrix_data(gko_exec, matrix_data);
634}
635
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,
640 StateView state)
641{
642 auto matrix_data = assemble_matrix_data(operator_model, state);
643 if (env_flag_enabled("SIMILIE_MATRIX_DIAGNOSTICS")) {
644 log_matrix_diagnostics(matrix_data);
645 }
646 return csr_from_matrix_data(gko_exec, matrix_data);
647}
648
649template <class ExecSpace, class OperatorModel>
650class MatrixFreeLinOp : public gko::EnableLinOp<MatrixFreeLinOp<ExecSpace, OperatorModel>>
651{
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;
658
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;
666
667public:
668 explicit MatrixFreeLinOp(std::shared_ptr<gko::Executor const> exec)
669 : base_type(std::move(exec))
670 , m_exec_space()
671 , m_operator_model()
672 {
673 }
674
675 MatrixFreeLinOp(
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))
682 {
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));
687 }
688 }
689 }
690
691public:
692 void apply_impl(gko::LinOp const* b, gko::LinOp* x) const override
693 {
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");
700 }
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,
706 ExecSpace ex,
707 decltype(b_view) input,
708 decltype(x_view) output,
709 workspace_type& workspace) {
710 model.apply(ex, input, output, workspace);
711 }) {
712 if (uses_precomputed_matrix_free_stencils(*m_operator_model)) {
713 m_operator_model->apply(m_exec_space, b_view, x_view);
714 } else {
715 if (m_workspace == nullptr) {
716 m_workspace = std::make_shared<workspace_type>(
717 m_operator_model->create_matrix_free_workspace(m_exec_space));
718 }
719 m_operator_model->apply(m_exec_space, b_view, x_view, *m_workspace);
720 }
721 } else {
722 m_operator_model->apply(m_exec_space, b_view, x_view);
723 }
724 } else {
725 m_operator_model->apply(m_exec_space, b_view, x_view);
726 }
727 m_exec_space.fence();
728 auto const apply_end = std::chrono::steady_clock::now();
729 ++m_apply_count;
730 m_apply_duration += std::chrono::duration<double>(apply_end - apply_start).count();
731 log_apply_progress("simple", m_apply_count, m_apply_duration);
732 }
733
734 void apply_impl(
735 gko::LinOp const* alpha,
736 gko::LinOp const* b,
737 gko::LinOp const* beta,
738 gko::LinOp* x) const override
739 {
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");
750 }
751
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,
761 ExecSpace ex,
762 decltype(b_view) input,
763 decltype(applied) output,
764 workspace_type& workspace) {
765 model.apply(ex, input, output, workspace);
766 }) {
767 if (uses_precomputed_matrix_free_stencils(*m_operator_model)) {
768 m_operator_model->apply(m_exec_space, b_view, applied);
769 } else {
770 if (m_workspace == nullptr) {
771 m_workspace = std::make_shared<workspace_type>(
772 m_operator_model->create_matrix_free_workspace(m_exec_space));
773 }
774 m_operator_model->apply(m_exec_space, b_view, applied, *m_workspace);
775 }
776 } else {
777 m_operator_model->apply(m_exec_space, b_view, applied);
778 }
779 } else {
780 m_operator_model->apply(m_exec_space, b_view, applied);
781 }
782 Kokkos::parallel_for(
783 "similie_matrix_free_linop_advanced_apply",
784 Kokkos::MDRangePolicy<
785 ExecSpace,
786 Kokkos::Rank<
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);
791 });
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);
797 }
798
799 void log_apply_start(char const* apply_kind, std::size_t apply_count) const
800 {
801 if (!env_flag_enabled("SIMILIE_MATRIX_FREE_APPLY_PROGRESS")) {
802 return;
803 }
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;
809 }
810 }
811
812 void log_apply_progress(char const* apply_kind, std::size_t apply_count, double duration) const
813 {
814 if (!env_flag_enabled("SIMILIE_MATRIX_FREE_APPLY_PROGRESS")) {
815 return;
816 }
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
822 << std::endl;
823 }
824 }
825
826 void log_timing() const
827 {
828 if (!env_flag_enabled("SIMILIE_SOLVER_TIMING")) {
829 return;
830 }
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';
835 }
836};
837
838template <class ExecSpace, class OperatorModel, class StateView>
839class StateDependentMatrixFreeLinOp
840 : public gko::EnableLinOp<StateDependentMatrixFreeLinOp<ExecSpace, OperatorModel, StateView>>
841{
842 using value_type = double;
843 using dense_type = gko::matrix::Dense<value_type>;
844 using memory_space = typename ExecSpace::memory_space;
845 using base_type
846 = gko::EnableLinOp<StateDependentMatrixFreeLinOp<ExecSpace, OperatorModel, StateView>>;
847
848 ExecSpace m_exec_space;
849 std::shared_ptr<OperatorModel const> m_operator_model;
850 StateView m_state;
851
852public:
853 explicit StateDependentMatrixFreeLinOp(std::shared_ptr<gko::Executor const> exec)
854 : base_type(std::move(exec))
855 , m_exec_space()
856 , m_operator_model()
857 , m_state()
858 {
859 }
860
861 StateDependentMatrixFreeLinOp(
862 std::shared_ptr<gko::Executor const> exec,
863 ExecSpace exec_space,
864 std::shared_ptr<OperatorModel const> operator_model,
865 StateView state)
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))
869 , m_state(state)
870 {
871 }
872
873public:
874 void apply_impl(gko::LinOp const* b, gko::LinOp* x) const override
875 {
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");
881 }
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);
885 }
886
887 void apply_impl(
888 gko::LinOp const* alpha,
889 gko::LinOp const* b,
890 gko::LinOp const* beta,
891 gko::LinOp* x) const override
892 {
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");
901 }
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<
912 ExecSpace,
913 Kokkos::Rank<
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);
918 });
919 m_exec_space.fence();
920 }
921};
922
923inline auto build_jacobi_preconditioner_factory(
924 std::shared_ptr<gko::Executor const> const& gko_exec,
925 StrongFormulationSolverSettings const& settings)
926{
927 return gko::preconditioner::Jacobi<double>::build()
928 .with_max_block_size(settings.jacobi_max_block_size)
929 .on(gko_exec);
930}
931
932inline PreconditionerType selected_preconditioner(StrongFormulationSolverSettings const& settings)
933{
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")) {
938 }
939 return settings.preconditioner;
940 }
941 return parse_preconditioner(value);
942}
943
944inline std::shared_ptr<gko::LinOp const> build_identity_preconditioner(
945 std::shared_ptr<gko::Executor const> const& gko_exec,
946 gko::size_type size)
947{
948 return gko::matrix::Identity<double>::create(gko_exec, size);
949}
950
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)
955{
956 PreconditionerType const preconditioner = selected_preconditioner(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());
965 }
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());
971 }
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());
977 }
979 auto preconditioner_factory = gko::preconditioner::GaussSeidel<double, gko::int32>::build()
980 .with_symmetric(true)
981 .on(gko_exec);
982 return std::shared_ptr<gko::LinOp const>(
983 preconditioner_factory->generate(matrix).release());
984 }
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))
991 .on(gko_exec);
992 return std::shared_ptr<gko::LinOp const>(
993 preconditioner_factory->generate(matrix).release());
994 }
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)
999 .on(gko_exec);
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)
1006 .on(gko_exec);
1007 return std::shared_ptr<gko::LinOp const>(
1008 preconditioner_factory->generate(matrix).release());
1009 }
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)
1020 .on(gko_exec);
1021 return std::shared_ptr<gko::LinOp const>(
1022 preconditioner_factory->generate(matrix).release());
1023 }
1024 }
1025 throw std::runtime_error("unsupported strong-formulation preconditioner");
1026}
1027
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,
1033 RHSViewType rhs,
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)
1038{
1039 StrongFormulationSolverDiagnostics diagnostics;
1040 if (env_flag_enabled("SIMILIE_SOLVER_TIMING")) {
1041 std::cout << "SimiLie linear solve stage: computing initial residual norm" << std::endl;
1042 }
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)
1049 .count()
1050 << std::endl;
1051 }
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);
1056 return diagnostics;
1057 }
1058
1059 if (env_flag_enabled("SIMILIE_SOLVER_TIMING")) {
1060 std::cout << "SimiLie linear solve stage: building solver factory" << std::endl;
1061 }
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)
1065 .on(gko_exec);
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)
1072 .with_criteria(
1073 std::move(residual_criterion),
1074 std::move(iterations_criterion))
1075 .on(gko_exec);
1076 } else if (detail::env_value_equals("SIMILIE_SOLVER", "fcg")) {
1077 solver_factory = gko::solver::Fcg<double>::build()
1078 .with_generated_preconditioner(preconditioner)
1079 .with_criteria(
1080 std::move(residual_criterion),
1081 std::move(iterations_criterion))
1082 .on(gko_exec);
1083 } else if (detail::env_value_equals("SIMILIE_SOLVER", "gmres")) {
1084 solver_factory = gko::solver::Gmres<double>::build()
1085 .with_generated_preconditioner(preconditioner)
1086 .with_criteria(
1087 std::move(residual_criterion),
1088 std::move(iterations_criterion))
1089 .with_krylov_dim(
1090 static_cast<gko::size_type>(
1091 env_int_or("SIMILIE_GMRES_KRYLOV_DIM", 100)))
1092 .on(gko_exec);
1093 } else if (detail::env_value_equals("SIMILIE_SOLVER", "bicgstab")) {
1094 solver_factory = gko::solver::Bicgstab<double>::build()
1095 .with_generated_preconditioner(preconditioner)
1096 .with_criteria(
1097 std::move(residual_criterion),
1098 std::move(iterations_criterion))
1099 .on(gko_exec);
1100 } else if (detail::env_value_equals("SIMILIE_SOLVER", "gcr")) {
1101 solver_factory
1102 = gko::solver::Gcr<double>::build()
1103 .with_generated_preconditioner(preconditioner)
1104 .with_criteria(
1105 std::move(residual_criterion),
1106 std::move(iterations_criterion))
1107 .with_krylov_dim(
1108 static_cast<gko::size_type>(
1109 std::max(1, env_int_or("SIMILIE_GCR_KRYLOV_DIM", 100))))
1110 .on(gko_exec);
1111 } else if (detail::env_value_equals("SIMILIE_SOLVER", "idr")) {
1112 solver_factory
1113 = gko::solver::Idr<double>::build()
1114 .with_generated_preconditioner(preconditioner)
1115 .with_criteria(
1116 std::move(residual_criterion),
1117 std::move(iterations_criterion))
1118 .with_subspace_dim(
1119 static_cast<gko::size_type>(
1120 std::max(1, env_int_or("SIMILIE_IDR_SUBSPACE_DIM", 8))))
1121 .with_deterministic(true)
1122 .on(gko_exec);
1123 } else {
1124 solver_factory = gko::solver::Cg<double>::build()
1125 .with_generated_preconditioner(preconditioner)
1126 .with_criteria(
1127 std::move(residual_criterion),
1128 std::move(iterations_criterion))
1129 .on(gko_exec);
1130 }
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)
1135 .count()
1136 << std::endl;
1137 }
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;
1143 }
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*) {
1147 });
1148 matrix_free_system_matrix = std::make_shared<MatrixFreeLinOp<
1149 ExecSpace,
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)
1156 .count()
1157 << std::endl;
1158 }
1159 system_matrix = matrix_free_system_matrix;
1160 } else {
1161 system_matrix = assembled_matrix;
1162 }
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",
1168 rhs.extent(0),
1169 rhs.extent(1));
1170 Kokkos::View<double**, memory_space> assembled_applied(
1171 "similie_compare_assembled_applied",
1172 rhs.extent(0),
1173 rhs.extent(1));
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);
1204 }
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
1213 ? 0.0
1214 : component_difference / component_assembled);
1215 }
1216 std::cout << '\n';
1217 }
1218 }
1219 if (env_flag_enabled("SIMILIE_SOLVER_TIMING")) {
1220 std::cout << "SimiLie linear solve stage: generating solver" << std::endl;
1221 }
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)
1228 .count()
1229 << std::endl;
1230 }
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,
1239 progress_stride);
1240 solver->add_logger(progress_logger);
1241 }
1242
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;
1249 }
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();
1258 }
1259 if (progress_logger != nullptr) {
1260 solver->remove_logger(progress_logger);
1261 }
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);
1272 }
1273 diagnostics.final_relative_residual
1274 = diagnostics.initial_residual_l2 == 0.0
1275 ? 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);
1290 } else {
1291 operator_model.apply(exec_space, solution, applied);
1292 }
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
1298 ? 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';
1302 }
1303 return diagnostics;
1304}
1305
1306} // namespace detail
1307
1308template <class ExecSpace, class OperatorModel, class RHSViewType, class SolutionViewType>
1310 ExecSpace exec_space,
1311 OperatorModel const& operator_model,
1312 RHSViewType rhs,
1313 SolutionViewType solution,
1314 StrongFormulationSolverSettings settings = {})
1315{
1316 StrongFormulationSolverDiagnostics diagnostics;
1317
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) {
1325 return diagnostics;
1326 }
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);
1332 if (settings.use_matrix_free && preconditioner_type == PreconditionerType::Identity) {
1333 std::cout << "SimiLie Ginkgo preconditioner: "
1334 << preconditioner_name(preconditioner_type) << '\n';
1335 preconditioner = detail::build_identity_preconditioner(gko_exec, operator_model.size());
1336 } else {
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(
1340 gko_exec,
1341 std::static_pointer_cast<gko::LinOp const>(matrix),
1342 settings);
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)
1347 .count()
1348 << " preconditioner_build_duration="
1349 << std::chrono::duration<double>(
1350 std::chrono::steady_clock::now() - actual_matrix_build_end)
1351 .count()
1352 << '\n';
1353 }
1354 return detail::solve_linearized_system(
1355 exec_space,
1356 gko_exec,
1357 operator_model,
1358 rhs,
1359 solution,
1360 settings,
1361 std::static_pointer_cast<gko::LinOp const>(matrix),
1362 preconditioner);
1363 }
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)
1368 .count()
1369 << " preconditioner_build_duration="
1370 << std::chrono::duration<double>(preconditioner_build_end - matrix_build_end)
1371 .count()
1372 << '\n';
1373 }
1374 return detail::solve_linearized_system(
1375 exec_space,
1376 gko_exec,
1377 operator_model,
1378 rhs,
1379 solution,
1380 settings,
1381 std::static_pointer_cast<gko::LinOp const>(matrix),
1382 preconditioner);
1383 } else {
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",
1395 rhs.extent(0),
1396 rhs.extent(1));
1397 Kokkos::View<double**, memory_space> candidate_residual(
1398 "similie_nonlinear_candidate_residual",
1399 rhs.extent(0),
1400 rhs.extent(1));
1401 Kokkos::View<double**, memory_space> candidate_operator_value(
1402 "similie_nonlinear_candidate_operator_value",
1403 rhs.extent(0),
1404 rhs.extent(1));
1405 auto const optimization_start = std::chrono::steady_clock::now();
1406 diagnostics.converged = false;
1407
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;
1416 return diagnostics;
1417 }
1418
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
1424 ? 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;
1429 break;
1430 }
1431
1432 auto matrix = detail::build_matrix(gko_exec, operator_model, solution);
1433 auto preconditioner = detail::build_preconditioner(
1434 gko_exec,
1435 std::static_pointer_cast<gko::LinOp const>(matrix),
1436 settings);
1437
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)
1444 .on(gko_exec);
1445 auto iterations_criterion = gko::stop::Iteration::build()
1446 .with_max_iters(settings.max_iterations)
1447 .on(gko_exec);
1448 auto solver_factory = solver_type::build()
1449 .with_generated_preconditioner(preconditioner)
1450 .with_criteria(
1451 std::move(residual_criterion),
1452 std::move(iterations_criterion))
1453 .on(gko_exec);
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<
1458 ExecSpace,
1459 OperatorModel,
1460 SolutionViewType>>(
1461 gko_exec,
1462 exec_space,
1463 operator_model_ptr,
1464 solution));
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),
1475 progress_stride);
1476 solver->add_logger(progress_logger);
1477 }
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"
1483 << std::endl;
1484 }
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);
1490 }
1491 solver->remove_logger(convergence_logger);
1492 diagnostics.iterations
1493 += static_cast<unsigned int>(convergence_logger->get_num_iterations());
1494 } else {
1495 auto inner = detail::solve_linearized_system(
1496 exec_space,
1497 gko_exec,
1498 operator_model,
1499 correction_rhs,
1500 delta,
1501 settings,
1502 std::static_pointer_cast<gko::LinOp const>(matrix),
1503 preconditioner);
1504 diagnostics.iterations += inner.iterations;
1505 }
1506 double const current_residual_l2 = detail::residual_norm_l2(exec_space, residual);
1507 double alpha = 1.0;
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),
1514 1.0e-3,
1515 0.99);
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(
1523 exec_space,
1524 candidate_residual,
1525 -1.0,
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) {
1530 best_alpha = alpha;
1531 best_residual_l2 = candidate_residual_l2;
1532 }
1533 if (candidate_residual_l2 < current_residual_l2) {
1534 break;
1535 }
1536 alpha *= line_search_reduction;
1537 }
1538 if (best_alpha != alpha) {
1539 alpha = best_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(
1545 exec_space,
1546 candidate_residual,
1547 -1.0,
1548 candidate_operator_value);
1549 best_residual_l2 = detail::residual_norm_l2(exec_space, candidate_residual);
1550 }
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;
1555 }
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);
1559 }
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
1566 ? 0.0
1567 : diagnostics.final_residual_l2 / diagnostics.initial_residual_l2;
1568 return diagnostics;
1569 }
1570}
1571
1572} // namespace similie::solvers
StrongFormulationSolverDiagnostics minimize_strong_formulation_residual(ExecSpace exec_space, OperatorModel const &operator_model, RHSViewType rhs, SolutionViewType solution, StrongFormulationSolverSettings settings={})
constexpr std::string_view preconditioner_name(PreconditionerType preconditioner)
PreconditionerType parse_preconditioner(std::string_view name)