Dune::Copasi
Loading...
Searching...
No Matches
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 =
50 -> 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:
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) {
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;
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 =
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
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
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
261 newton_op->get("convergence_condition.relative_tolerance") = nrel_tol;
262 newton_op->get("convergence_condition.iteration_range") = { nit_range[0], nit_range[1] };
263 if (nlsover_config.hasKey("convergence_condition.absolute_tolerance")) {
264 newton_op->get("convergence_condition.absolute_tolerance") =
265 nlsover_config.template get<double>("convergence_condition.absolute_tolerance");
266 }
267 newton_op->get("linearization_threshold") = lin_thld;
268 newton_op->get("dx_inverse_fixed_tolerance") = dxinv_fixed_tol;
269 newton_op->get("dx_inverse_min_relative_tolerance") = dxinv_min_rel_tol;
270
271 newton_op->get("dx_inverse") = linear_solver;
272 if (nnorm == "l_2") {
273 newton_op->setNorm(
274 [](const auto& residual) -> ResidualQuantity { return residual.two_norm2(); });
275 } else if (nnorm == "l_inf") {
276 newton_op->setNorm(
277 [](const auto& residual) -> ResidualQuantity { return residual.infinity_norm(); });
278 } else if (nnorm == "l_1") {
279 newton_op->setNorm(
280 [](const auto& residual) -> ResidualQuantity { return residual.one_norm(); });
281 } else {
282 throw format_exception(IOError{}, "Not known nonlinear norm solver of type '{}'", nnorm);
283 }
284
285 runge_kutta_inverse = std::move(newton_op);
286 }
287
288 std::shared_ptr<PDELab::Operator<RKCoefficients, RKResidual>> instationary_op;
289 if (matrix_free) { // matrix free
290 instationary_op = PDELab::makeInstationaryMatrixFreeAssembler<RKCoefficients, RKResidual>(
292 } else { // matrix based
294 const auto& /*op*/, RKJacobian& jac) {
295 // TODO(sospinar): resize outer jacobian wrt instationary coefficients
296 if (jac.buildStage() == RKJacobian::BuildStage::notAllocated) {
297 jac.setBuildMode(RKJacobian::implicit);
298 jac.setImplicitBuildModeParameters(1,1.);
299 jac.setSize(1,1);
300 jac.entry(0,0);
301 jac.compress();
302 // jac = RKJacobian(1,1,1,RKJacobian::row_wise);
303 // for(auto row=jac.createbegin(); row!=jac.createend(); ++row)
304 // for (std::size_t col = 0; col != row.size(); ++col)
305 // row.insert(col);
306 }
307
308 // all jacobian entries of the RK jacobian have the same pattern
309 auto& entry = jac[0][0];
310
311 // number of components per compartment
312 std::vector<std::size_t> comp_size(basis.degree(), 0);
313 for (std::size_t i = 0; i != comp_size.size(); ++i)
314 comp_size[i] = basis.subSpace(TypeTree::treePath(i )).degree();
315
316 using SizePrefix = typename Basis::SizePrefix;
317
318 // blocked case
319 auto comp_max = std::ranges::max(comp_size);
320 std::function<std::size_t(SizePrefix, SizePrefix)> block_size;
321 if (basis.degree() == basis.size()) { // compartment is blokced
322 block_size = [=](SizePrefix, SizePrefix j) -> std::size_t {
323 switch (j.size()) {
324 case 0:
325 return comp_size.size(); // number of compartmetns
326 case 1:
327 return comp_size[j[0]] * 6; // aprox number of neighboring entities time the number of
328 // components (assume 2D & P1)
329 default:
330 std::terminate();
331 }
332 };
333 } else { // component is blokced
334 block_size = [=](SizePrefix, SizePrefix j) -> std::size_t {
335 switch (j.size()) {
336 case 0:
337 return comp_max * 6; // max number of components times aprox number of neighboring
338 // entities (assume 2D)
339 case 1:
340 return comp_max; // number of components (assume P1)
341 default:
342 std::terminate();
343 }
344 };
345 }
346
347 // blocked-blocked case
348 auto block_block_size = [=]<class MultiIndex>(MultiIndex, MultiIndex j) -> std::size_t {
349 switch (j.size()) {
350 case 0:
351 return comp_size.size(); // number of compartmetns
352 case 1:
353 return 6; // aprox number of neighboring entities (assume 2D)
354 case 2:
355 return comp_size[j[1]]; // number of components (assume P1)
356 default:
357 std::terminate();
358 }
359 std::terminate();
360 };
361
363 [&](BCRSMatrix<double>&) -> PDELab::LeafSparsePattern<Basis, Basis> {
364 return { basis, {}, basis, {}, comp_max * 6 };
365 },
367 -> PDELab::BlockedSparsePattern<PDELab::LeafSparsePattern<Basis, Basis>> {
368 return { basis, {}, basis, {}, block_size };
369 },
371 -> PDELab::BlockedSparsePattern<
372 PDELab::BlockedSparsePattern<PDELab::LeafSparsePattern<Basis, Basis>>> {
373 return { basis, {}, basis, {}, block_block_size };
374 });
375
377
378 PDELab::basisToPattern(stiffness_local_operator, pattern);
379 PDELab::basisToPattern(mass_local_operator, pattern);
380 pattern.sort();
381 PDELab::patternToMatrix(pattern, entry);
382 entry = 0.;
383
384 // copy patterns into other runge-kutta jacobians
385 for (std::size_t i = 0; i != jac.N(); ++i) {
386 for (std::size_t j = 0; j != jac.M(); ++j) {
387 if (i == 0 and j == 0)
388 continue;
389 jac[i][j] = entry;
390 }
391 }
392
393 if (not svg_path.empty()) {
394 auto path = std::filesystem::path{ svg_path }.replace_extension("svg");
395 spdlog::info("Writing matrix pattern in svg file: '{}'", path.string());
396 std::ofstream file{ path.string() };
398 }
399 };
400
402 PDELab::makeInstationaryMatrixBasedAssembler<RKCoefficients, RKResidual, RKJacobian>(
404 }
405
406 using RungeKutta = PDELab::RungeKutta<RKCoefficients, RKResidual, TimeQuantity, TimeQuantity>;
407 auto runge_kutta = std::make_unique<RungeKutta>();
408
409 runge_kutta->get("inverse") = runge_kutta_inverse;
410 runge_kutta->get("inverse.forward") = instationary_op;
411 const auto& type = config.get("type", "Alexander2");
412 std::shared_ptr<PDELab::InstationaryCoefficients> inst_coeff;
413
414 auto pdelab2coeff = [](auto pdlab_param) {
415 return std::make_unique<PDELab::InstationaryCoefficients>(pdlab_param);
416 };
417
418 spdlog::info("Creating time-stepping solver with '{}' type", type);
419
420 if (type == "ExplicitEuler") {
421 inst_coeff = pdelab2coeff(Dune::PDELab::ExplicitEulerParameter<double>{});
422 } else if (type == "ImplicitEuler") {
423 inst_coeff = pdelab2coeff(Dune::PDELab::ImplicitEulerParameter<double>{});
424 } else if (type == "Heun") {
425 inst_coeff = pdelab2coeff(Dune::PDELab::HeunParameter<double>{});
426 } else if (type == "Shu3") {
427 inst_coeff = pdelab2coeff(Dune::PDELab::Shu3Parameter<double>{});
428 } else if (type == "RungeKutta4") {
429 inst_coeff = pdelab2coeff(Dune::PDELab::RK4Parameter<double>{});
430 } else if (type == "Alexander2") {
431 inst_coeff = pdelab2coeff(Dune::PDELab::Alexander2Parameter<double>{});
432 } else if (type == "FractionalStepTheta") {
433 inst_coeff = pdelab2coeff(Dune::PDELab::FractionalStepParameter<double>{});
434 } else if (type == "Alexander3") {
435 inst_coeff = pdelab2coeff(Dune::PDELab::Alexander3Parameter<double>{});
436 } else {
437 throw format_exception(IOError{}, "Not known linear solver of type '{}'", type);
438 }
439
440 runge_kutta->get("instationary_coefficients") = inst_coeff;
441 return runge_kutta;
442}
443
444} // namespace Dune::Copasi
445
446#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
constexpr bool is_bitflags_v
Alias for Bitflag indicator.
Definition bit_flags.hh:24