Dune::Copasi 2.1.0-git79717215+dune.gitlab.629933
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages Concepts
make_step_operator.hh
Go to the documentation of this file.
1#ifndef DUNE_COPASI_MODEL_MAKE_STEP_OPERATOR_HH
2#define DUNE_COPASI_MODEL_MAKE_STEP_OPERATOR_HH
3
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>
21
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>
29
30#include <dune/common/overloadset.hh>
31
32#include <dune/istl/bvector.hh>
33
34#include <spdlog/spdlog.h>
35
36#include <algorithm>
37#include <concepts>
38#include <functional>
39#include <memory>
40#if (__cpp_lib_memory_resource >= 201603L) && (__cpp_lib_polymorphic_allocator >= 201902L)
41#include <memory_resource>
42#endif
43
44namespace Dune::Copasi {
45
46namespace Impl {
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 {}; });
53}
54
55template<class Domain, std::copy_constructible Range>
56class LinearSolver : public PDELab::Operator<Range, Domain>
57{
58
59 using Jacobian = decltype(Impl::jacobian_selector(Range{}));
60
61 // converts a forward operator into a dune-istl linear operator
62 class MatrixFreeAdapter : public Dune::LinearOperator<Domain, Range>
63 {
64 public:
65 MatrixFreeAdapter(PDELab::Operator<Domain, Range>& forward_op)
66 : _forward{ forward_op }
67 {
68 }
69
70 void apply(const Domain& x, Range& y) const override
71 {
72 PDELab::forEachContainerEntry(
73 PDELab::Execution::par_unseq, y, []<class T>(T& v) { v = T{ 0 }; });
74 _forward.apply(x, y).or_throw();
75 }
76
77 using typename Dune::LinearOperator<Domain, Range>::field_type;
78
80 void applyscaleadd(field_type alpha, const Domain& x, Range& y) const override
81 {
82 tmp = y;
83 this->apply(x, tmp);
84 Dune::PDELab::axpy(PDELab::Execution::par_unseq, y, alpha, tmp);
85 }
86
87 SolverCategory::Category category() const override
88 {
89 return SolverCategory::Category::sequential;
90 }
91
92 private:
93 PDELab::Operator<Domain, Range>& _forward;
94 mutable Range tmp;
95 };
96
97public:
98 LinearSolver(const ParameterTree& config)
99 : _config{ config }
100 {}
101
102 virtual PDELab::ErrorCondition apply(Range& range, Domain& domain) override
103 {
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");
108
109 Jacobian const * jac = nullptr;
110 if (forward.hasKey("container"))
111 jac = &forward.template get<const Jacobian>("container");
112
113 // choose a solver from the solver factory based on whether the operator is matrix-free
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};
117#else
118 auto alloc = std::allocator<void>{};
119#endif
120 std::shared_ptr<InverseOperator<Domain, Range>> solver;
121 if (jac) {
122 using Op = Dune::MatrixAdapter<Jacobian, Domain, Range>;
123 solver = ISTL::makeInverseOperator(std::make_shared<Op>(*jac), _config, alloc);
124 } else {
125 using Op = MatrixFreeAdapter;
126 solver = ISTL::makeInverseOperator(std::make_shared<Op>(forward), _config, alloc);
127 }
128 if (not solver)
129 throw format_exception(InvalidStateException{}, "Solver was configured incorrectly!");
130
131 // relative tolerance may be dynamically changed by the newton solver
132 auto rel_tol = this->template get<double>("convergence_condition.relative_tolerance");
133 // do solve the linear system
134 Dune::InverseOperatorResult result;
135 solver->apply(domain, range, rel_tol, result);
136
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);
140
141 if (result.converged) {
142 return PDELab::ErrorCondition{};
143 } else {
144 return make_error_condition(PDELab::Convergence::Reason::DivergedByDivergenceTolarance);
145 }
146 }
147
148 virtual PDELab::ErrorCondition apply(const Range& range, Domain& domain) override
149 {
150 Domain tmp_range = range;
151 return this->apply(tmp_range, domain);
152 }
153
154private:
155 std::shared_ptr<Preconditioner<Domain, Range>> _preconditioner;
156 ParameterTree _config;
157};
158
159template<class Coefficients,
160 class Residual,
161 class ResidualQuantity,
162 class TimeQuantity,
163 PDELab::Concept::Basis Basis>
164[[nodiscard]] inline static std::unique_ptr<PDELab::OneStep<Coefficients>>
165make_step_operator(const ParameterTree& config,
166 const Basis& basis,
167 const auto& mass_local_operator,
168 const auto& stiffness_local_operator)
169{
170 TRACE_EVENT("dune", "StepOperator::SetUp");
171
172 using RKCoefficients = Dune::BlockVector<Coefficients>;
173 using RKResidual = Dune::BlockVector<Residual>;
174
175 if (basis.dimension() == 0) {
176 throw format_exception(InvalidStateException{},
177 "Basis has dimension 0, make sure to have at least one 'scalar_field' "
178 "with a non-empty 'compartment'");
179 }
180
181 std::shared_ptr<PDELab::Operator<RKResidual, RKCoefficients>> runge_kutta_inverse;
182
183 bool const is_linear = mass_local_operator.localAssembleIsLinear() and
184 stiffness_local_operator.localAssembleIsLinear();
185 if (is_linear) {
186 spdlog::info("Local operator is linear");
187 } else {
188 spdlog::info("Local operator is non-linear");
189 }
190
191 static_assert(std::same_as<Coefficients, Residual>);
192
193 using RKJacobian = decltype(Impl::jacobian_selector(RKCoefficients{}));
194
195 // configure linear solver
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);
199
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;
205
206 if (lin_solver == "CG" and not is_linear) {
207 spdlog::warn("Using conjugate gradient in a non-linear solver is not recommended");
208 }
209
210 if (is_linear) {
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,
216 RKResidual& b,
217 RKCoefficients& x) mutable {
218 TRACE_EVENT("dune", "LinearSolver::DefectCorrection");
219 static_assert(std::is_same_v<Coefficients, Residual>);
220 auto& forward =
221 self.template get<PDELab::Operator<RKCoefficients, RKResidual>>("forward");
222
223 forward.apply(x, b).or_throw(); // residual is additive b += F(x)
224
225 if (not coeff_zero) {
226 coeff_zero.emplace(x);
227 PDELab::forEachContainerEntry(*coeff_zero, []<class T>(T& v) { v = T{ 0 }; });
228 }
229
230 RKCoefficients z = *coeff_zero;
231
232 // set jacobian to the linear solver (note that we reuse the same matrix between timesteps)
233 linear_solver->get("forward") = derivative = forward.derivative(x, derivative);
234
235 // compute correction
236 auto error_condition = linear_solver->apply(b, z);
237
238 if (error_condition) {
239 return error_condition;
240 }
241 PDELab::axpy(x, -1., z);
242 return Dune::PDELab::ErrorCondition{};
243 });
244
245 runge_kutta_inverse = std::move(linear_runge_kutta_inverse);
246 } else {
247 spdlog::info("Creating non-linear solver with 'Newton' type");
248 auto newton_op =
249 std::make_unique<PDELab::NewtonOperator<RKCoefficients, RKResidual, ResidualQuantity>>();
250
251 // configure non-linear solver
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);
261
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");
268 }
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;
272
273 newton_op->get("dx_inverse") = linear_solver;
274 if (nnorm == "l_2") {
275 newton_op->setNorm(
276 [](const auto& residual) -> ResidualQuantity { return residual.two_norm2(); });
277 } else if (nnorm == "l_inf") {
278 newton_op->setNorm(
279 [](const auto& residual) -> ResidualQuantity { return residual.infinity_norm(); });
280 } else if (nnorm == "l_1") {
281 newton_op->setNorm(
282 [](const auto& residual) -> ResidualQuantity { return residual.one_norm(); });
283 } else {
284 throw format_exception(IOError{}, "Not known nonlinear norm solver of type '{}'", nnorm);
285 }
286
287 runge_kutta_inverse = std::move(newton_op);
288 }
289
290 std::shared_ptr<PDELab::Operator<RKCoefficients, RKResidual>> instationary_op;
291 if (matrix_free) { // matrix free
292 instationary_op = PDELab::makeInstationaryMatrixFreeAssembler<RKCoefficients, RKResidual>(
293 basis, basis, mass_local_operator, stiffness_local_operator);
294 } else { // matrix based
295 auto pattern_factory = [basis, stiffness_local_operator, mass_local_operator, svg_path](
296 const auto& /*op*/, RKJacobian& jac) {
297 // TODO(sospinar): resize outer jacobian wrt instationary coefficients
298 if (jac.buildStage() == RKJacobian::BuildStage::notAllocated) {
299 jac.setBuildMode(RKJacobian::implicit);
300 jac.setImplicitBuildModeParameters(1,1.);
301 jac.setSize(1,1);
302 jac.entry(0,0);
303 jac.compress();
304 // jac = RKJacobian(1,1,1,RKJacobian::row_wise);
305 // for(auto row=jac.createbegin(); row!=jac.createend(); ++row)
306 // for (std::size_t col = 0; col != row.size(); ++col)
307 // row.insert(col);
308 }
309
310 // all jacobian entries of the RK jacobian have the same pattern
311 auto& entry = jac[0][0];
312
313 // number of components per compartment
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();
317
318 using SizePrefix = typename Basis::SizePrefix;
319
320 // blocked case
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()) { // compartment is blokced
324 block_size = [=](SizePrefix, SizePrefix j) -> std::size_t {
325 switch (j.size()) {
326 case 0:
327 return comp_size.size(); // number of compartmetns
328 case 1:
329 return comp_size[j[0]] * 6; // aprox number of neighboring entities time the number of
330 // components (assume 2D & P1)
331 default:
332 std::terminate();
333 }
334 };
335 } else { // component is blokced
336 block_size = [=](SizePrefix, SizePrefix j) -> std::size_t {
337 switch (j.size()) {
338 case 0:
339 return comp_max * 6; // max number of components times aprox number of neighboring
340 // entities (assume 2D)
341 case 1:
342 return comp_max; // number of components (assume P1)
343 default:
344 std::terminate();
345 }
346 };
347 }
348
349 // blocked-blocked case
350 auto block_block_size = [=]<class MultiIndex>(MultiIndex, MultiIndex j) -> std::size_t {
351 switch (j.size()) {
352 case 0:
353 return comp_size.size(); // number of compartmetns
354 case 1:
355 return 6; // aprox number of neighboring entities (assume 2D)
356 case 2:
357 return comp_size[j[1]]; // number of components (assume P1)
358 default:
359 std::terminate();
360 }
361 std::terminate();
362 };
363
364 auto pattern_selector = overload(
365 [&](BCRSMatrix<double>&) -> PDELab::LeafSparsePattern<Basis, Basis> {
366 return { basis, {}, basis, {}, comp_max * 6 };
367 },
368 [&](BCRSMatrix<BCRSMatrix<double>>&)
369 -> PDELab::BlockedSparsePattern<PDELab::LeafSparsePattern<Basis, Basis>> {
370 return { basis, {}, basis, {}, block_size };
371 },
372 [&](BCRSMatrix<BCRSMatrix<BCRSMatrix<double>>>&)
373 -> PDELab::BlockedSparsePattern<
374 PDELab::BlockedSparsePattern<PDELab::LeafSparsePattern<Basis, Basis>>> {
375 return { basis, {}, basis, {}, block_block_size };
376 });
377
378 auto pattern = pattern_selector(entry);
379
380 PDELab::basisToPattern(stiffness_local_operator, pattern);
381 PDELab::basisToPattern(mass_local_operator, pattern);
382 pattern.sort();
383 PDELab::patternToMatrix(pattern, entry);
384 entry = 0.;
385
386 // copy patterns into other runge-kutta jacobians
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)
390 continue;
391 jac[i][j] = entry;
392 }
393 }
394
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);
400 }
401 };
402
403 instationary_op =
404 PDELab::makeInstationaryMatrixBasedAssembler<RKCoefficients, RKResidual, RKJacobian>(
405 basis, basis, mass_local_operator, stiffness_local_operator, pattern_factory);
406 }
407
408 using RungeKutta = PDELab::RungeKutta<RKCoefficients, RKResidual, TimeQuantity, TimeQuantity>;
409 auto runge_kutta = std::make_unique<RungeKutta>();
410
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;
415
416 auto pdelab2coeff = [](auto pdlab_param) {
417 return std::make_unique<PDELab::InstationaryCoefficients>(pdlab_param);
418 };
419
420 spdlog::info("Creating time-stepping solver with '{}' type", type);
421
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>{});
438 } else {
439 throw format_exception(IOError{}, "Not known linear solver of type '{}'", type);
440 }
441
442 runge_kutta->get("instationary_coefficients") = inst_coeff;
443 return runge_kutta;
444}
445
446} // namespace Dune::Copasi
447
448#endif // DUNE_COPASI_MODEL_MAKE_STEP_OPERATOR_HH
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