Dune::Copasi
Loading...
Searching...
No Matches
reduce.hh
Go to the documentation of this file.
1#ifndef DUNE_COPASI_MODEL_DIFFUSION_REACTION_REDUCE_HH
2#define DUNE_COPASI_MODEL_DIFFUSION_REACTION_REDUCE_HH
3
10
11#include <dune/pdelab/concepts/basis.hh>
12#include <dune/pdelab/common/local_container.hh>
13#include <dune/pdelab/common/trace.hh>
14#include <dune/pdelab/common/for_each_entity.hh>
15
16#include <dune/grid/common/partitionset.hh>
17
18#include <dune/common/float_cmp.hh>
19
20#include <function2/function2.hpp>
21#include <spdlog/spdlog.h>
22
23#include <fmt/core.h>
24
25#if HAVE_TBB
26#include <tbb/enumerable_thread_specific.h>
27#endif // HAVE_TBB
28
29#include <string>
30#include <map>
31
33
34class ReductionError : public Dune::Exception {};
35
36// Evaluates a evaluation/reduction/transformation algorigthm over the grid.
37// Starting with val = 0, the this function evaluates `val = reduction(val, evaluation(), weight)` for each quadrature point of the grid. Finally, the result gets transformed by `value = transformation(value)`
38// For each sub-tree in the parameter tree, the evaluation and reduce expressions are extracted to form the evaluation/reduce operation for its key.
39template<class ExecutionPolicy, PDELab::Concept::Basis Basis, class Coefficients>
41 PDELab::Execution::is_execution_policy_v<ExecutionPolicy> )
42static std::map<std::string, double>
43reduce(const ExecutionPolicy exec,
44 const Basis& basis,
45 const Coefficients& coefficients,
46 auto time,
47 const ParameterTree& config,
49 functor_factory = nullptr)
50{
51 constexpr std::size_t dim = Basis::EntitySet::GridView::dimension;
52 TRACE_EVENT("dune", "Reduce");
53
54 if (basis.entitySet().size(0) == 0) {
55 return {};
56 }
57
58 std::shared_ptr<const ParserContext> parser_context;
59 auto functor_factory_parser = std::dynamic_pointer_cast<const FunctorFactoryParser<dim>>(functor_factory);
61 parser_context = functor_factory_parser->parser_context();
62 }
63 if (not parser_context) {
64 parser_context = std::make_shared<const ParserContext>();
65 }
66
67 auto parser_type = string2parser.at(config.get("parser_type", default_parser_str));
68
69 auto first_compartment_finite_elment = [](const Concept::CompartmentLocalBasisNode auto& lnode) noexcept {
70 for (std::size_t i = 0; i != lnode.degree(); ++i)
71 if (lnode.child(i).size() != 0)
72 return lnode.child(i).finiteElement();
73 std::terminate();
74 };
75
77 [first_compartment_finite_elment](const Concept::MultiCompartmentLocalBasisNode auto& lnode) noexcept {
78 for (std::size_t i = 0; i != lnode.degree(); ++i)
79 if (lnode.child(i).size() != 0)
81 std::terminate();
82 });
83
84 using FEM = std::decay_t<decltype(first_finite_element(basis.localView().tree()))>;
85 using Geometry = typename Basis::EntitySet::template Codim<0>::Entity::Geometry;
86
87 struct ThreadLocalData
88 {
89 typename Basis::LocalView lbasis;
90 PDELab::LocalContainerBuffer<Basis, const Coefficients> lcoeff;
91 std::unique_ptr<LocalEquations<dim>> leqs;
92 LocalBasisCache<typename FEM::Traits::LocalBasisType::Traits> fe_cache = {};
93 std::optional<typename Geometry::JacobianInverse> geojacinv_opt = std::nullopt;
94 std::vector<double> values = {};
95 std::vector<fu2::unique_function<double() const>> evaluations = {};
96 std::vector<fu2::unique_function<double(double, double) const>> reductions = {};
97 };
98
99 auto init_thread_data = [&]() -> ThreadLocalData {
100 auto lbasis = basis.localView();
102 leqs_ptr->time = time;
103 const auto& leqs = *leqs_ptr;
104 ThreadLocalData data{ std::move(lbasis), { basis, coefficients }, std::move(leqs_ptr) };
105
106 auto sz = config.getSubKeys().size();
107 data.values.reserve(sz);
108 data.evaluations.reserve(sz);
109 data.reductions.reserve(sz);
110 for (const auto& key : config.getSubKeys()) {
111 const auto& sub_config = config.sub(key, true);
112 data.values.emplace_back(sub_config.get("initial.value", 0.));
113 auto& evaluation = data.evaluations.emplace_back();
114 if (sub_config.hasKey("evaluation.expression"))
115 evaluation =
116 functor_factory->make_scalar(key + ".evaluation", sub_config.sub("evaluation"), leqs);
117 if (not evaluation)
118 evaluation = [] { return 0.; };
119 auto& reduction = data.reductions.emplace_back(std::plus<>{});
120 if (sub_config.hasKey("reduction.expression")) {
121 auto [args, expr] =
122 parser_context->parse_function_expression(sub_config.sub("reduction")["expression"]);
123 if (args.size() == 3)
124 spdlog::warn(
125 "Reduction expression for '{0}' uses 3 arguments whereas 2 are now required.\nThis "
126 "functionality has changed: the third argument became the contextual keyword "
127 "'integration_factor' that may be used to express '{0}.evaluation.expression'.",
128 key);
129 if (args.size() != 2)
130 throw format_exception(IOError{}, "Reduction arguments must be exactly 2");
131 auto sub_parser_type = string2parser.at(
132 sub_config.get("reduction.parser_type", std::string{ parser2string.at(parser_type) }));
133 auto str_args = std::array{ std::string{ args[0] }, std::string{ args[1] } };
134 reduction = parser_context->make_function(sub_parser_type, str_args, expr);
135 }
136 }
137 return data;
138 };
139
140#if HAVE_TBB
141 tbb::enumerable_thread_specific<ThreadLocalData> thread_data{ std::move(init_thread_data) };
142#else
143 struct ThreadData : public std::array<ThreadLocalData,1> {
144 ThreadLocalData& local() {
145 return this->front();
146 }
147 };
149#endif
150
151 if (config.getSubKeys().empty()) {
152 return {};
153 }
154
155 spdlog::info("Evaluating reduce operators");
156 PDELab::forEachEntity(exec, basis.entitySetPartition(), [&thread_data](const auto& cell) {
157 auto& data = thread_data.local();
158 auto geo = cell.geometry();
159
160 data.lbasis.bind(cell);
161 data.lcoeff.load(data.lbasis, std::false_type{});
162
163 std::size_t const order = 4;
164 const auto& quad_rule =
165 QuadratureRules<typename Geometry::ctype, Geometry::coorddimension>::rule(geo.type(), order);
166
167 for (std::size_t q = 0; q != quad_rule.size(); ++q) {
168 const auto [position, weight] = quad_rule[q];
169 if (not geo.affine() or not data.geojacinv_opt)
170 data.geojacinv_opt.emplace(geo.jacobianInverse(position));
171 const auto& geojacinv = *(data.geojacinv_opt);
172 data.leqs->position = geo.global(position);
173 data.leqs->integration_factor = weight * geo.integrationElement(position);
174
175 // evaluate values at quadrature point
176 forEachLeafNode(data.lbasis.tree(), [&](const auto& node) {
177 if (node.size() == 0)
178 return;
179 auto& value = data.leqs->get_value(node);
180 auto& gradient = data.leqs->get_gradient(node);
181 value = 0.;
182 gradient = 0.;
183 data.fe_cache.bind(node.finiteElement(), quad_rule);
184 const auto& phi = data.fe_cache.evaluateFunction(q);
185 const auto& jacphi = data.fe_cache.evaluateJacobian(q);
186 for (std::size_t dof = 0; dof != node.size(); ++dof) {
187 value += data.lcoeff(node, dof) * phi[dof];
188 gradient += data.lcoeff(node, dof) * (jacphi[dof] * geojacinv)[0];
189 }
190 });
191
192 // evaluate all functions at the current quadrature point
193 for (std::size_t i = 0; i != data.values.size(); ++i)
194 data.values[i] = data.reductions[i](data.evaluations[i](), data.values[i]);
195 }
196
197 data.lbasis.unbind();
198 data.leqs->clear();
199 });
200
201 if (basis.entitySet().grid().comm().size() > 1){
202 throw format_exception(ParallelError{}, "Transoform and Reduce is not implemented in parallel");
203 }
204
205 // gather values from every thread_data
206 std::vector<double> values;
207 for (const auto& key : config.getSubKeys())
208 values.emplace_back(config.sub(key, true).get("initial.value", 0.));
209 for (std::size_t i = 0; i != values.size(); ++i)
210 for (const auto& data : thread_data)
211 values[i] = data.reductions[i](data.values[i], values[i]);
212
213 // report results
214 std::size_t max_key_chars = 0;
215 std::size_t max_val_chars = 5;
216 for (const auto& key : config.getSubKeys())
217 max_key_chars = std::max(max_key_chars, key.size());
218
219 std::map<std::string, double> key_value;
220
221 spdlog::info(" ┌{0:─^{1}}┐", "", max_key_chars + max_val_chars + 13);
222 std::string error_msg;
223 auto values_it = values.begin();
224 for (const auto& key : config.getSubKeys()) {
225 auto& value = key_value[key] = *(values_it++);
226
227 if (config.sub(key).hasKey("transformation.expression")) {
228 auto [args, expr] = parser_context->parse_function_expression(config.sub(key)["transformation.expression"]);
229 if (args.size() != 1)
230 throw format_exception(IOError{}, "Warning function must have exactly 1 argument");
231 auto sub_parser_type = string2parser.at(config.sub(key).get("transformation.parser_type", std::string{parser2string.at(parser_type)}));
232 auto transformation = parser_context->make_function(sub_parser_type, std::array{std::string{args[0]}}, expr);
233 value = transformation(value);
234 }
235
236 bool processed = false;
237
238 if (config.sub(key).hasKey("error.expression")) {
239 auto [args, expr] = parser_context->parse_function_expression(config.sub(key)["error.expression"]);
240 if (args.size() != 1)
241 throw format_exception(IOError{}, "Error function must have exactly 1 argument");
242 auto sub_parser_type = string2parser.at(config.sub(key).get("error.parser_type", std::string{parser2string.at(parser_type)}));
243 auto error = parser_context->make_function(sub_parser_type, std::array{std::string{args[0]}}, expr);
244 if (FloatCmp::ne(error(value), 0.)) {
245 processed = true;
246 spdlog::error(" | {0:>{2}} := {1: .{3}e} |", DUNE_COPASI_FMT_STYLED_BOLD(key), value, max_key_chars, max_val_chars);
247 if (not error_msg.empty())
248 error_msg += '\n';
249 error_msg += fmt::format("Reduction on the token '{}' raised an error because the "
250 "expression '{}' with evaluates to false with '{} := {}'",
251 key,
252 expr,
253 args[0],
254 value);
255 }
256 }
257
258 if (not processed and config.sub(key).hasKey("warn.expression")) {
259 auto [args, expr] = parser_context->parse_function_expression(config.sub(key)["warn.expression"]);
260 if (args.size() != 1)
261 throw format_exception(IOError{}, "Warning function must have exactly 1 argument");
262 auto sub_parser_type = string2parser.at(config.sub(key).get("warn.parser_type", std::string{parser2string.at(parser_type)}));
263 auto warn = parser_context->make_function(sub_parser_type, std::array{std::string{args[0]}}, expr);
264 if (FloatCmp::ne(warn(value), 0.)) {
265 processed = true;
266 spdlog::warn("| {0:>{2}} := {1: .{3}e} |", DUNE_COPASI_FMT_STYLED_BOLD(key), value, max_key_chars, max_val_chars);
267 }
268 }
269
270 if (not processed and not config.sub(key).get("quiet", false)) {
271 spdlog::info(" | {0:>{2}} := {1: .{3}e} |", DUNE_COPASI_FMT_STYLED_BOLD(key), value, max_key_chars, max_val_chars);
272 }
273 }
274 spdlog::info(" └{0:─^{1}}┘", "", max_key_chars+max_val_chars+13);
275
276 if (not error_msg.empty()) {
277 throw format_exception(ReductionError{}, "{}", error_msg);
278 }
279
280 return key_value;
281}
282
283} // namespace Dune::Copasi::DiffusionReaction
284
285#endif // DUNE_COPASI_MODEL_DIFFUSION_REACTION_REDUCE_HH
static std::unique_ptr< LocalEquations > make(const LocalBasis &lbasis, const ParameterTree &config={}, std::shared_ptr< const FunctorFactory< dim > > functor_factory=nullptr, std::shared_ptr< const CellData< CellDataGridView, CellDataType > > grid_cell_data=nullptr, BitFlags< FactoryFalgs > opts=BitFlags< FactoryFalgs >::no_flags())
Definition local_equations.hh:264
Definition functor_factory.hh:24
Definition compartment_tree.hh:99
#define DUNE_COPASI_FMT_STYLED_BOLD(key)
Definition fmt_style.hh:13
Definition factory.hh:28
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