1#ifndef DUNE_COPASI_MODEL_MAKE_STEP_OPERATOR_HH
2#define DUNE_COPASI_MODEL_MAKE_STEP_OPERATOR_HH
4#include <dune/pdelab/common/algebra.hh>
5#include <dune/pdelab/common/container_entry.hh>
6#include <dune/pdelab/common/convergence/reason.hh>
7#include <dune/pdelab/common/error_condition.hh>
8#include <dune/pdelab/common/trace.hh>
9#include <dune/pdelab/common/execution.hh>
10#include <dune/pdelab/concepts/basis.hh>
11#include <dune/pdelab/operator/adapter.hh>
12#include <dune/pdelab/operator/forward/instationary/assembler.hh>
13#include <dune/pdelab/operator/forward/instationary/coefficients.hh>
14#include <dune/pdelab/operator/forward/runge_kutta.hh>
15#include <dune/pdelab/operator/inverse/newton.hh>
16#include <dune/pdelab/operator/operator.hh>
17#include <dune/pdelab/pattern/basis_to_pattern.hh>
18#include <dune/pdelab/pattern/pattern_to_matrix.hh>
19#include <dune/pdelab/pattern/sparsity_pattern.hh>
22#include <dune/istl/io.hh>
23#include <dune/istl/operators.hh>
24#include <dune/istl/preconditioners.hh>
25#include <dune/istl/scalarproducts.hh>
26#include <dune/istl/solvers.hh>
27#include <dune/istl/superlu.hh>
28#include <dune/istl/umfpack.hh>
30#include <dune/common/overloadset.hh>
32#include <dune/istl/bvector.hh>
34#include <spdlog/spdlog.h>
40#if (__cpp_lib_memory_resource >= 201603L) && (__cpp_lib_polymorphic_allocator >= 201902L)
41#include <memory_resource>
47inline auto jacobian_selector =
48 overload([](BlockVector<BlockVector<double>>) -> BCRSMatrix<BCRSMatrix<double>> {
return {}; },
49 [](BlockVector<BlockVector<BlockVector<double>>>)
50 -> BCRSMatrix<BCRSMatrix<BCRSMatrix<double>>> {
return {}; },
51 [](BlockVector<BlockVector<BlockVector<BlockVector<double>>>>)
52 -> BCRSMatrix<BCRSMatrix<BCRSMatrix<BCRSMatrix<double>>>> {
return {}; });
55template<
class Domain, std::copy_constructible Range>
59 using Jacobian =
decltype(Impl::jacobian_selector(Range{}));
62 class MatrixFreeAdapter :
public Dune::LinearOperator<Domain, Range>
65 MatrixFreeAdapter(PDELab::Operator<Domain, Range>& forward_op)
66 : _forward{ forward_op }
70 void apply(
const Domain& x, Range& y)
const override
72 PDELab::forEachContainerEntry(
73 PDELab::Execution::par_unseq, y, []<
class T>(T& v) { v = T{ 0 }; });
74 _forward.apply(x, y).or_throw();
77 using typename Dune::LinearOperator<Domain, Range>::field_type;
80 void applyscaleadd(field_type alpha,
const Domain& x, Range& y)
const override
84 Dune::PDELab::axpy(PDELab::Execution::par_unseq, y, alpha, tmp);
87 SolverCategory::Category category()
const override
89 return SolverCategory::Category::sequential;
93 PDELab::Operator<Domain, Range>& _forward;
102 virtual PDELab::ErrorCondition
apply(Range& range, Domain& domain)
override
104 [[maybe_unused]] uint64_t solver_timestamp = perfetto::TrackEvent::GetTraceTimeNs();
105 TRACE_EVENT(
"dune",
"LinearSolver", solver_timestamp);
106 static_assert(std::is_same_v<Range, Domain>);
107 auto& forward = this->
template get<PDELab::Operator<Domain, Range>>(
"forward");
109 Jacobian
const * jac =
nullptr;
110 if (forward.hasKey(
"container"))
111 jac = &forward.template get<const Jacobian>(
"container");
114#if (__cpp_lib_memory_resource >= 201603L) && (__cpp_lib_polymorphic_allocator >= 201902L)
115 auto mr = std::pmr::monotonic_buffer_resource{};
116 std::pmr::polymorphic_allocator<std::byte> alloc{&mr};
118 auto alloc = std::allocator<void>{};
120 std::shared_ptr<InverseOperator<Domain, Range>> solver;
122 using Op = Dune::MatrixAdapter<Jacobian, Domain, Range>;
125 using Op = MatrixFreeAdapter;
129 throw format_exception(InvalidStateException{},
"Solver was configured incorrectly!");
132 auto rel_tol = this->
template get<double>(
"convergence_condition.relative_tolerance");
134 Dune::InverseOperatorResult result;
135 solver->apply(domain, range, rel_tol, result);
137 TRACE_COUNTER(
"dune",
"LinearSolver::Iterations", solver_timestamp, result.iterations);
138 TRACE_COUNTER(
"dune",
"LinearSolver::Reduction", solver_timestamp, result.reduction);
139 TRACE_COUNTER(
"dune",
"LinearSolver::Converged", solver_timestamp, result.converged);
141 if (result.converged) {
142 return PDELab::ErrorCondition{};
144 return make_error_condition(PDELab::Convergence::Reason::DivergedByDivergenceTolarance);
148 virtual PDELab::ErrorCondition
apply(
const Range& range, Domain& domain)
override
150 Domain tmp_range = range;
151 return this->
apply(tmp_range, domain);
155 std::shared_ptr<Preconditioner<Domain, Range>> _preconditioner;
156 ParameterTree _config;
159template<
class Coefficients,
161 class ResidualQuantity,
163 PDELab::Concept::Basis Basis>
164[[nodiscard]]
inline static std::unique_ptr<PDELab::OneStep<Coefficients>>
165make_step_operator(
const ParameterTree& config,
167 const auto& mass_local_operator,
168 const auto& stiffness_local_operator)
170 TRACE_EVENT(
"dune",
"StepOperator::SetUp");
172 using RKCoefficients = Dune::BlockVector<Coefficients>;
173 using RKResidual = Dune::BlockVector<Residual>;
175 if (basis.dimension() == 0) {
177 "Basis has dimension 0, make sure to have at least one 'scalar_field' "
178 "with a non-empty 'compartment'");
181 std::shared_ptr<PDELab::Operator<RKResidual, RKCoefficients>> runge_kutta_inverse;
183 bool const is_linear = mass_local_operator.localAssembleIsLinear() and
184 stiffness_local_operator.localAssembleIsLinear();
186 spdlog::info(
"Local operator is linear");
188 spdlog::info(
"Local operator is non-linear");
191 static_assert(std::same_as<Coefficients, Residual>);
193 using RKJacobian =
decltype(Impl::jacobian_selector(RKCoefficients{}));
196 const auto& lsover_config = config.sub(
"linear_solver");
197 std::shared_ptr<PDELab::Operator<RKResidual, RKCoefficients>>
const linear_solver =
198 std::make_shared<LinearSolver<RKResidual, RKCoefficients>>(lsover_config);
200 auto svg_path = lsover_config.get(
"layout.writer.svg.path",
"");
201 const bool matrix_free = lsover_config.get(
"matrix_free",
false);
202 const auto lin_solver = lsover_config.get(
"type", std::string{
"UMFPack" });
203 auto rel_tol = lsover_config.get(
"convergence_condition.relative_tolerance", 1e-4);
204 linear_solver->get(
"convergence_condition.relative_tolerance") = rel_tol;
206 if (lin_solver ==
"CG" and not is_linear) {
207 spdlog::warn(
"Using conjugate gradient in a non-linear solver is not recommended");
211 std::optional<RKCoefficients> coeff_zero;
212 std::shared_ptr<PDELab::Operator<RKCoefficients, RKResidual>> derivative;
213 auto linear_runge_kutta_inverse =
214 std::make_unique<PDELab::OperatorAdapter<RKResidual, RKCoefficients>>(
215 [coeff_zero, linear_solver, derivative](PDELab::Operator<RKResidual, RKCoefficients>& self,
217 RKCoefficients& x)
mutable {
218 TRACE_EVENT(
"dune",
"LinearSolver::DefectCorrection");
219 static_assert(std::is_same_v<Coefficients, Residual>);
221 self.template get<PDELab::Operator<RKCoefficients, RKResidual>>(
"forward");
223 forward.apply(x, b).or_throw();
225 if (not coeff_zero) {
226 coeff_zero.emplace(x);
227 PDELab::forEachContainerEntry(*coeff_zero, []<
class T>(T& v) { v = T{ 0 }; });
230 RKCoefficients z = *coeff_zero;
233 linear_solver->get(
"forward") = derivative = forward.derivative(x, derivative);
236 auto error_condition = linear_solver->apply(b, z);
238 if (error_condition) {
239 return error_condition;
241 PDELab::axpy(x, -1., z);
242 return Dune::PDELab::ErrorCondition{};
245 runge_kutta_inverse = std::move(linear_runge_kutta_inverse);
247 spdlog::info(
"Creating non-linear solver with 'Newton' type");
249 std::make_unique<PDELab::NewtonOperator<RKCoefficients, RKResidual, ResidualQuantity>>();
252 const auto& nlsover_config = config.sub(
"nonlinear_solver");
253 auto nrel_tol = nlsover_config.get(
"convergence_condition.relative_tolerance", 1e-4);
254 auto nit_range = nlsover_config.get(
"convergence_condition.iteration_range",
255 std::array<std::size_t, 2>{ 0, 40 });
256 auto lin_thld = nlsover_config.get(
"linearization_threshold", 0.);
257 auto dxinv_fixed_tol = nlsover_config.get(
"dx_inverse_fixed_tolerance",
false);
258 auto dxinv_min_rel_tol = nlsover_config.get(
"dx_inverse_min_relative_tolerance", 0.1);
259 auto nnorm = nlsover_config.get(
"norm",
"l_2");
260 auto verbosity = nlsover_config.get(
"verbosity", 1);
262 newton_op->get(
"verbosity") = verbosity;
263 newton_op->get(
"convergence_condition.relative_tolerance") = nrel_tol;
264 newton_op->get(
"convergence_condition.iteration_range") = { nit_range[0], nit_range[1] };
265 if (nlsover_config.hasKey(
"convergence_condition.absolute_tolerance")) {
266 newton_op->get(
"convergence_condition.absolute_tolerance") =
267 nlsover_config.template get<double>(
"convergence_condition.absolute_tolerance");
269 newton_op->get(
"linearization_threshold") = lin_thld;
270 newton_op->get(
"dx_inverse_fixed_tolerance") = dxinv_fixed_tol;
271 newton_op->get(
"dx_inverse_min_relative_tolerance") = dxinv_min_rel_tol;
273 newton_op->get(
"dx_inverse") = linear_solver;
274 if (nnorm ==
"l_2") {
276 [](
const auto& residual) -> ResidualQuantity {
return residual.two_norm2(); });
277 }
else if (nnorm ==
"l_inf") {
279 [](
const auto& residual) -> ResidualQuantity {
return residual.infinity_norm(); });
280 }
else if (nnorm ==
"l_1") {
282 [](
const auto& residual) -> ResidualQuantity {
return residual.one_norm(); });
284 throw format_exception(IOError{},
"Not known nonlinear norm solver of type '{}'", nnorm);
287 runge_kutta_inverse = std::move(newton_op);
290 std::shared_ptr<PDELab::Operator<RKCoefficients, RKResidual>> instationary_op;
292 instationary_op = PDELab::makeInstationaryMatrixFreeAssembler<RKCoefficients, RKResidual>(
293 basis, basis, mass_local_operator, stiffness_local_operator);
295 auto pattern_factory = [basis, stiffness_local_operator, mass_local_operator, svg_path](
296 const auto& , RKJacobian& jac) {
298 if (jac.buildStage() == RKJacobian::BuildStage::notAllocated) {
299 jac.setBuildMode(RKJacobian::implicit);
300 jac.setImplicitBuildModeParameters(1,1.);
311 auto& entry = jac[0][0];
314 std::vector<std::size_t> comp_size(basis.degree(), 0);
315 for (std::size_t i = 0; i != comp_size.size(); ++i)
316 comp_size[i] = basis.subSpace(TypeTree::treePath(i )).degree();
318 using SizePrefix =
typename Basis::SizePrefix;
321 auto comp_max = std::ranges::max(comp_size);
322 std::function<std::size_t(SizePrefix, SizePrefix)> block_size;
323 if (basis.degree() == basis.size()) {
324 block_size = [=](SizePrefix, SizePrefix j) -> std::size_t {
327 return comp_size.size();
329 return comp_size[j[0]] * 6;
336 block_size = [=](SizePrefix, SizePrefix j) -> std::size_t {
350 auto block_block_size = [=]<
class MultiIndex>(MultiIndex, MultiIndex j) -> std::size_t {
353 return comp_size.size();
357 return comp_size[j[1]];
364 auto pattern_selector = overload(
365 [&](BCRSMatrix<double>&) -> PDELab::LeafSparsePattern<Basis, Basis> {
366 return { basis, {}, basis, {}, comp_max * 6 };
368 [&](BCRSMatrix<BCRSMatrix<double>>&)
369 -> PDELab::BlockedSparsePattern<PDELab::LeafSparsePattern<Basis, Basis>> {
370 return { basis, {}, basis, {}, block_size };
372 [&](BCRSMatrix<BCRSMatrix<BCRSMatrix<double>>>&)
373 -> PDELab::BlockedSparsePattern<
374 PDELab::BlockedSparsePattern<PDELab::LeafSparsePattern<Basis, Basis>>> {
375 return { basis, {}, basis, {}, block_block_size };
378 auto pattern = pattern_selector(entry);
380 PDELab::basisToPattern(stiffness_local_operator, pattern);
381 PDELab::basisToPattern(mass_local_operator, pattern);
383 PDELab::patternToMatrix(pattern, entry);
387 for (std::size_t i = 0; i != jac.N(); ++i) {
388 for (std::size_t j = 0; j != jac.M(); ++j) {
389 if (i == 0 and j == 0)
395 if (not svg_path.empty()) {
396 auto path = std::filesystem::path{ svg_path }.replace_extension(
"svg");
397 spdlog::info(
"Writing matrix pattern in svg file: '{}'", path.string());
398 std::ofstream file{ path.string() };
399 writeSVGMatrix(file, jac);
404 PDELab::makeInstationaryMatrixBasedAssembler<RKCoefficients, RKResidual, RKJacobian>(
405 basis, basis, mass_local_operator, stiffness_local_operator, pattern_factory);
408 using RungeKutta = PDELab::RungeKutta<RKCoefficients, RKResidual, TimeQuantity, TimeQuantity>;
409 auto runge_kutta = std::make_unique<RungeKutta>();
411 runge_kutta->get(
"inverse") = runge_kutta_inverse;
412 runge_kutta->get(
"inverse.forward") = instationary_op;
413 const auto& type = config.get(
"type",
"Alexander2");
414 std::shared_ptr<PDELab::InstationaryCoefficients> inst_coeff;
416 auto pdelab2coeff = [](
auto pdlab_param) {
417 return std::make_unique<PDELab::InstationaryCoefficients>(pdlab_param);
420 spdlog::info(
"Creating time-stepping solver with '{}' type", type);
422 if (type ==
"ExplicitEuler") {
423 inst_coeff = pdelab2coeff(Dune::PDELab::ExplicitEulerParameter<double>{});
424 }
else if (type ==
"ImplicitEuler") {
425 inst_coeff = pdelab2coeff(Dune::PDELab::ImplicitEulerParameter<double>{});
426 }
else if (type ==
"Heun") {
427 inst_coeff = pdelab2coeff(Dune::PDELab::HeunParameter<double>{});
428 }
else if (type ==
"Shu3") {
429 inst_coeff = pdelab2coeff(Dune::PDELab::Shu3Parameter<double>{});
430 }
else if (type ==
"RungeKutta4") {
431 inst_coeff = pdelab2coeff(Dune::PDELab::RK4Parameter<double>{});
432 }
else if (type ==
"Alexander2") {
433 inst_coeff = pdelab2coeff(Dune::PDELab::Alexander2Parameter<double>{});
434 }
else if (type ==
"FractionalStepTheta") {
435 inst_coeff = pdelab2coeff(Dune::PDELab::FractionalStepParameter<double>{});
436 }
else if (type ==
"Alexander3") {
437 inst_coeff = pdelab2coeff(Dune::PDELab::Alexander3Parameter<double>{});
439 throw format_exception(IOError{},
"Not known linear solver of type '{}'", type);
442 runge_kutta->get(
"instationary_coefficients") = inst_coeff;
Definition: make_step_operator.hh:57
virtual PDELab::ErrorCondition apply(Range &range, Domain &domain) override
Definition: make_step_operator.hh:102
LinearSolver(const ParameterTree &config)
Definition: make_step_operator.hh:98
virtual PDELab::ErrorCondition apply(const Range &range, Domain &domain) override
Definition: make_step_operator.hh:148
std::shared_ptr< InverseOperator< typename Op::domain_type, typename Op::range_type > > makeInverseOperator(const std::shared_ptr< Op > &op, const ParameterTree &config, const Alloc &alloc=Alloc())
Definition: inverse.hh:65
Definition: axis_names.hh:7
auto format_exception(Exception &&e, fmt::format_string< Args... > format, Args &&... args)
Definition: exceptions.hh:23