diff --git a/dune/gdt/operators/advection-fv-entropybased.hh b/dune/gdt/operators/advection-fv-entropybased.hh index 9177813509e4a3a5236adb0895006811700c47dd..44d30970508f0d015b8dcce6b45ba68c6f72dbd8 100644 --- a/dune/gdt/operators/advection-fv-entropybased.hh +++ b/dune/gdt/operators/advection-fv-entropybased.hh @@ -11,6 +11,8 @@ #ifndef DUNE_GDT_OPERATORS_FV_ENTROPYBASED_HH #define DUNE_GDT_OPERATORS_FV_ENTROPYBASED_HH +#include <boost/config.hpp> + #include <dune/xt/common/numeric.hh> #include <dune/gdt/operators/interfaces.hh> @@ -158,10 +160,12 @@ public: static constexpr size_t dimDomain = AdvectionOperatorType::SGV::dimension; static constexpr size_t dimRange = AdvectionOperatorType::s_r; static constexpr double interval_length = 2. / (dimRange - 1.); + static constexpr size_t first_positive_index_1d = dimRange / 2; using RangeFieldType = typename BaseType::FieldType; using ParameterFunctionType = typename ProblemType::ScalarFunctionType; using DomainType = FieldVector<RangeFieldType, dimDomain>; using BoundaryFluxesMapType = std::map<DomainType, DynamicVector<RangeFieldType>, XT::Common::FieldVectorFloatLess>; + using IntersectionType = XT::Grid::extract_intersection_t<typename AdvectionOperatorType::SGV>; EntropicCoordinatesMasslumpedOperator(const AdvectionOperatorType& advection_op, const ProblemType& problem, @@ -174,6 +178,7 @@ public: , sigma_s_(problem.sigma_s()) , Q_(problem.Q()) , exp_evaluations_(source_space().mapper().size()) + , dummy_range_(dimRange) , dx_(dx) , h_inv_(1. / dx_) , quad_points_(dimDomain * dimRange) @@ -235,48 +240,30 @@ public: assert(source.size() < std::numeric_limits<int>::max()); XT::Common::Mkl::exp(static_cast<int>(source.size()), source.data(), exp_evaluations_.data()); auto* range_data = range.data(); - std::fill(range_data, range_data + range.size(), 0.); const auto& grid_view = source_space().grid_view(); const size_t num_entities = grid_view.size(0); - DomainType entity_center; if constexpr (dimDomain == 1) { + // left boundary flux + const auto& left_boundary_flux = boundary_fluxes_.at(lower_left_); + for (size_t jj = 0; jj < dimRange; ++jj) + range_data[jj] += left_boundary_flux[jj] * h_inv_; + // inner fluxes and rhs for (size_t ii = 0; ii < num_entities; ++ii) { - const bool left_boundary = (ii == 0); - const bool right_boundary = (ii == num_entities - 1); const auto offset = ii * dimRange; auto* range_entity = range_data + offset; - auto* range_left = range_entity - dimRange; - auto* range_right = range_entity + dimRange; + auto* range_left = (ii == 0 ? dummy_range_.data() : range_entity - dimRange); + auto* range_right = (ii == num_entities - 1 ? dummy_range_.data() : range_entity + dimRange); const auto* psi = exp_evaluations_.data() + offset; - const auto* boundary_flux = - left_boundary ? &(boundary_fluxes_.at(lower_left_)) : &(boundary_fluxes_.at(upper_right_)); - entity_center[0] = lower_left_[0] + (ii + 0.5) * dx_; - const auto sigma_a_value = sigma_a_->evaluate(entity_center)[0]; - const auto sigma_s_value = sigma_s_->evaluate(entity_center)[0]; - const auto sigma_t_value = sigma_a_value + sigma_s_value; - const auto Q_value = Q_->evaluate(entity_center)[0]; - auto density = XT::Common::reduce(psi + 1, psi + dimRange - 1, 0.); - density += 0.5 * (psi[0] + psi[dimRange - 1]); - density *= interval_length; - for (size_t jj = 0; jj < dimRange; ++jj) { - // flux - const auto& v = quad_points_[jj]; - const auto weight = (jj == 0 || jj == dimRange - 1) ? interval_length / 2. : interval_length; - const auto flux_jj = std::abs(v) * psi[jj] * h_inv_ * weight; - range_entity[jj] -= flux_jj; - if (v > 0.) { - if (!right_boundary) - range_right[jj] += flux_jj; - } else if (!left_boundary) { - range_left[jj] += flux_jj; - } - if (left_boundary || right_boundary) - range_entity[jj] += (*boundary_flux)[jj] * h_inv_; - // rhs - range_entity[jj] += - sigma_s_value * density * u_iso_[jj] + Q_value * basis_integrated_[jj] - psi[jj] * weight * sigma_t_value; - } // jj + // flux + flux_1d(range_entity, range_left, range_right, psi); + // rhs + entity_center_[0] = lower_left_[0] + (ii + 0.5) * dx_; + rhs_1d(range_entity, psi); } // entities + // left boundary flux + const auto& right_boundary_flux = boundary_fluxes_.at(upper_right_); + for (size_t jj = 0; jj < dimRange; ++jj) + range_data[(num_entities - 1) * dimRange + jj] += right_boundary_flux[jj] * h_inv_; } else if constexpr (dimDomain == 3) { for (auto&& entity : Dune::elements(grid_view)) { const auto entity_index = grid_view.indexSet().index(entity); @@ -286,54 +273,124 @@ public: // flux for (auto&& intersection : Dune::intersections(grid_view, entity)) { const bool boundary = !intersection.neighbor(); - const auto& outside_entity = boundary ? entity : intersection.outside(); - const auto outside_index = grid_view.indexSet().index(outside_entity); - auto* range_outside = range_data + outside_index * dimRange; - const auto dd = intersection.indexInInside() / 2; - const auto& n = intersection.centerUnitOuterNormal(); - const auto* boundary_flux = boundary ? &(boundary_fluxes_.at(intersection.geometry().center())) : nullptr; - for (size_t jj = 0; jj < dimRange; ++jj) { - const auto v_times_n = quad_points_[dd * dimRange + jj] * n[dd]; - if (v_times_n > 0.) { - const auto flux_jj = v_times_n * psi[jj] * quad_weights_[jj] * h_inv_; - range_entity[jj] -= flux_jj; - if (!boundary) - range_outside[jj] += flux_jj; - } else if (boundary) { - range_entity[jj] += (*boundary_flux)[jj] * h_inv_; - } - } // jj + const auto& outside_entity = intersection.outside(); + auto* range_outside = + boundary ? dummy_range_.data() : range_data + grid_view.indexSet().index(outside_entity) * dimRange; + flux_3d(range_entity, range_outside, psi, intersection); } // intersections // rhs - entity_center = entity.geometry().center(); - const auto sigma_a_value = sigma_a_->evaluate(entity_center)[0]; - const auto sigma_s_value = sigma_s_->evaluate(entity_center)[0]; - const auto sigma_t_value = sigma_a_value + sigma_s_value; - const auto Q_value = Q_->evaluate(entity_center)[0]; - const auto density = XT::Common::transform_reduce(psi, psi + dimRange, quad_weights_.begin(), 0.); - for (size_t jj = 0; jj < dimRange; ++jj) { - range_entity[jj] += sigma_s_value * density * u_iso_[jj] + Q_value * basis_integrated_[jj] - - psi[jj] * quad_weights_[jj] * sigma_t_value; - } // jj + entity_center_ = entity.geometry().center(); + rhs_3d(range_entity, psi); } // entities } // inverse Hessian const auto* psi = exp_evaluations_.data(); + if constexpr (dimDomain == 1) + apply_inverse_hessian_1d(num_entities, range_data, psi); + else + apply_inverse_hessian_3d(num_entities, range_data, psi); + } // void apply(...) + + void flux_1d(RangeFieldType* BOOST_RESTRICT range_entity, + RangeFieldType* BOOST_RESTRICT range_left, + RangeFieldType* BOOST_RESTRICT range_right, + const RangeFieldType* BOOST_RESTRICT psi) const + { + // fluxes in negative direction + for (size_t jj = 0; jj < first_positive_index_1d; ++jj) { + const auto weight = (jj == 0 ? interval_length * 0.5 : interval_length); + const auto flux_jj = -quad_points_[jj] * psi[jj] * h_inv_ * weight; + range_entity[jj] -= flux_jj; + range_left[jj] += flux_jj; + } + // fluxes in positive direction + for (size_t jj = first_positive_index_1d; jj < dimRange; ++jj) { + const auto weight = (jj == dimRange - 1 ? interval_length * 0.5 : interval_length); + const auto flux_jj = quad_points_[jj] * psi[jj] * h_inv_ * weight; + range_entity[jj] -= flux_jj; + range_right[jj] += flux_jj; + } + } + + void flux_3d(RangeFieldType* BOOST_RESTRICT range_entity, + RangeFieldType* BOOST_RESTRICT range_outside, + const RangeFieldType* BOOST_RESTRICT psi, + const IntersectionType& intersection) const + { + const bool boundary = !intersection.neighbor(); + const auto dd = intersection.indexInInside() / 2; + const auto n_dd = intersection.centerUnitOuterNormal()[dd]; + const auto quad_offset = dd * dimRange; + for (size_t jj = 0; jj < dimRange; ++jj) { + const auto v_times_n = quad_points_[quad_offset + jj] * n_dd; + if (v_times_n > 0.) { + const auto flux_jj = psi[jj] * quad_weights_[jj] * h_inv_ * v_times_n; + range_entity[jj] -= flux_jj; + range_outside[jj] += flux_jj; + } + } // jj + if (boundary) { + const auto& boundary_flux = boundary_fluxes_.at(intersection.geometry().center()); + for (size_t jj = 0; jj < dimRange; ++jj) + range_entity[jj] += boundary_flux[jj] * h_inv_; + } + } + + // Note: entity_center_ has to be set to entity.geometry().center() before calling this function + void rhs_1d(RangeFieldType* BOOST_RESTRICT range_entity, const RangeFieldType* BOOST_RESTRICT psi) const + { + const auto sigma_a_value = sigma_a_->evaluate(entity_center_)[0]; + const auto sigma_s_value = sigma_s_->evaluate(entity_center_)[0]; + const auto Q_value = Q_->evaluate(entity_center_)[0]; + auto density = XT::Common::reduce(psi + 1, psi + dimRange - 1, 0.); + density += 0.5 * (psi[0] + psi[dimRange - 1]); + density *= interval_length; + const auto sigma_s_factor = sigma_s_value * density; + const auto sigma_t_factor = (sigma_a_value + sigma_s_value) * interval_length; + range_entity[0] += u_iso_[0] * sigma_s_factor + basis_integrated_[0] * Q_value - psi[0] * sigma_t_factor * 0.5; + for (size_t jj = 1; jj < dimRange - 1; ++jj) + range_entity[jj] += u_iso_[jj] * sigma_s_factor + basis_integrated_[jj] * Q_value - psi[jj] * sigma_t_factor; + range_entity[dimRange - 1] += u_iso_[dimRange - 1] * sigma_s_factor + basis_integrated_[dimRange - 1] * Q_value + - psi[dimRange - 1] * sigma_t_factor * 0.5; + } + + // Note: entity_center_ has to be set to entity.geometry().center() before calling this function + void rhs_3d(RangeFieldType* BOOST_RESTRICT range_entity, const RangeFieldType* BOOST_RESTRICT psi) const + { + const auto sigma_a_value = sigma_a_->evaluate(entity_center_)[0]; + const auto sigma_s_value = sigma_s_->evaluate(entity_center_)[0]; + const auto sigma_t_value = sigma_a_value + sigma_s_value; + const auto Q_value = Q_->evaluate(entity_center_)[0]; + const auto density = XT::Common::transform_reduce(psi, psi + dimRange, quad_weights_.begin(), 0.); + const auto sigma_s_factor = sigma_s_value * density; + for (size_t jj = 0; jj < dimRange; ++jj) + range_entity[jj] += + u_iso_[jj] * sigma_s_factor + basis_integrated_[jj] * Q_value - psi[jj] * quad_weights_[jj] * sigma_t_value; + } + + static void apply_inverse_hessian_1d(const size_t num_entities, + RangeFieldType* BOOST_RESTRICT range_data, + const RangeFieldType* BOOST_RESTRICT psi) + { for (size_t ii = 0; ii < num_entities; ++ii) { const auto offset = ii * dimRange; - for (size_t jj = 0; jj < dimRange; ++jj) { - auto& val = range_data[offset + jj]; - if constexpr (dimDomain == 1) { - const auto weight = (jj == 0 || jj == dimRange - 1) ? interval_length / 2. : interval_length; - val /= psi[offset + jj] * weight; - } else { - val /= psi[offset + jj] * quad_weights_[jj]; - } - if (std::isnan(val) || std::isinf(val)) - DUNE_THROW(Dune::MathError, "inf or nan in range!"); - } // jj + range_data[offset] /= psi[offset] * interval_length * 0.5; + for (size_t jj = 1; jj < dimRange - 1; ++jj) + range_data[offset + jj] /= psi[offset + jj] * interval_length; + range_data[offset + dimRange - 1] /= psi[offset + dimRange - 1] * interval_length * 0.5; } // ii - } // void apply(...) + } + + void apply_inverse_hessian_3d(const size_t num_entities, + RangeFieldType* BOOST_RESTRICT range_data, + const RangeFieldType* BOOST_RESTRICT psi) const + { + for (size_t ii = 0; ii < num_entities; ++ii) { + const auto offset = ii * dimRange; + for (size_t jj = 0; jj < dimRange; ++jj) + range_data[offset + jj] /= psi[offset + jj] * quad_weights_[jj]; + } // ii + } const AdvectionOperatorType& advection_op_; DynamicVector<RangeFieldType> u_iso_; @@ -342,6 +399,8 @@ public: std::unique_ptr<ParameterFunctionType> sigma_s_; std::unique_ptr<ParameterFunctionType> Q_; mutable std::vector<RangeFieldType> exp_evaluations_; + mutable DomainType entity_center_; + mutable std::vector<RangeFieldType> dummy_range_; const RangeFieldType dx_; const RangeFieldType h_inv_; std::vector<RangeFieldType> quad_points_; @@ -349,7 +408,7 @@ public: const BoundaryFluxesMapType& boundary_fluxes_; const DomainType lower_left_; const DomainType upper_right_; -}; // class EntropicCoordinatesMasslumpedOperator<...> +}; // namespace Dune template <class AdvectionOperatorImp, class EntropySolverImp> diff --git a/dune/gdt/test/momentmodels/entropyflux_implementations.hh b/dune/gdt/test/momentmodels/entropyflux_implementations.hh index 012121be623992a16df74fee54f6ca81f9d5cf25..9ef798fc3a1fd41ec57b1a8fb84ce01d964541d5 100644 --- a/dune/gdt/test/momentmodels/entropyflux_implementations.hh +++ b/dune/gdt/test/momentmodels/entropyflux_implementations.hh @@ -5277,7 +5277,7 @@ public: evaluate_eta_ast_prime(alpha_j, eta_ast_prime_vals[1]); // calculate \sum_{i=1}^d < \omega_i m G_\alpha(u) > n_i DomainType ret(0); - const size_t first_positive = dimRange / 2; + constexpr size_t first_positive = dimRange / 2; const auto& first_exp_vals = n_ij[dd] < 0. ? eta_ast_prime_vals[0] : eta_ast_prime_vals[1]; for (size_t ll = 0; ll < first_positive; ++ll) ret[ll] = first_exp_vals[ll] * grid_points_[ll]; @@ -5296,7 +5296,7 @@ public: evaluate_eta_ast_prime(alpha_i, eta_ast_prime_vals); // calculate \sum_{i=1}^d < \omega_i m G_\alpha(u) > n_i DomainType ret(0); - const size_t first_positive = dimRange / 2; + constexpr size_t first_positive = dimRange / 2; if (n_ij[dd] < 0.) { for (size_t ll = 0; ll < first_positive; ++ll) ret[ll] = eta_ast_prime_vals[ll] * grid_points_[ll]; @@ -5334,7 +5334,7 @@ public: const auto& psi_right = (*ansatz_distribution_values[2]); constexpr bool reconstruct = (slope_type != SlopeLimiterType::no_slope); // reconstruct densities - const size_t first_positive = dimRange / 2; + constexpr size_t first_positive = dimRange / 2; for (size_t ll = 0; ll < first_positive; ++ll) { const RangeFieldType slope = reconstruct ? slope_func(psi_entity[ll] - psi_left[ll], psi_right[ll] - psi_entity[ll]) : 0.; diff --git a/dune/gdt/test/momentmodels/kinetictransport/checkerboard.hh b/dune/gdt/test/momentmodels/kinetictransport/checkerboard.hh index 4f90aa9a75323873558ac3021d083cc9469435a5..9e9916a1126d5c4dd8fb995ee891f463c43e8546 100644 --- a/dune/gdt/test/momentmodels/kinetictransport/checkerboard.hh +++ b/dune/gdt/test/momentmodels/kinetictransport/checkerboard.hh @@ -102,10 +102,10 @@ public: protected: static bool is_absorbing(const FieldVector<RangeFieldType, 2>& x) { - size_t row = XT::Common::numeric_cast<size_t>(std::floor(x[1])); + size_t row = static_cast<size_t>(std::floor(x[1])); if (row == 7) row = 6; - size_t col = XT::Common::numeric_cast<size_t>(std::floor(x[0])); + size_t col = static_cast<size_t>(std::floor(x[0])); if (col == 7) col = 6; assert(row < 7 && col < 7); @@ -115,9 +115,9 @@ protected: static bool is_absorbing(const FieldVector<RangeFieldType, 3>& x) { - size_t plane = XT::Common::numeric_cast<size_t>(std::floor(x[2])); - size_t col = XT::Common::numeric_cast<size_t>(std::floor(x[0])); - size_t row = XT::Common::numeric_cast<size_t>(std::floor(x[1])); + size_t plane = static_cast<size_t>(std::floor(x[2])); + size_t col = static_cast<size_t>(std::floor(x[0])); + size_t row = static_cast<size_t>(std::floor(x[1])); assert(plane <= 7 && row <= 7 && col <= 7); if (plane == 0 || plane >= 6) return false; @@ -130,16 +130,16 @@ protected: static bool is_center(const FieldVector<RangeFieldType, 2>& x) { - size_t row = XT::Common::numeric_cast<size_t>(std::floor(x[1])); - size_t col = XT::Common::numeric_cast<size_t>(std::floor(x[0])); + size_t row = static_cast<size_t>(std::floor(x[1])); + size_t col = static_cast<size_t>(std::floor(x[0])); return row == 3 && col == 3; } static bool is_center(const FieldVector<RangeFieldType, 3>& x) { - size_t plane = XT::Common::numeric_cast<size_t>(std::floor(x[2])); - size_t row = XT::Common::numeric_cast<size_t>(std::floor(x[1])); - size_t col = XT::Common::numeric_cast<size_t>(std::floor(x[0])); + size_t plane = static_cast<size_t>(std::floor(x[2])); + size_t row = static_cast<size_t>(std::floor(x[1])); + size_t col = static_cast<size_t>(std::floor(x[0])); return plane == 3 && row == 3 && col == 3; } }; // class CheckerboardPn<...> diff --git a/dune/gdt/tools/timestepper/adaptive-rungekutta-kinetic.hh b/dune/gdt/tools/timestepper/adaptive-rungekutta-kinetic.hh index b8806d268cb797cd1f2afe47646bea564d8f4e6d..b3e0be8cb6bdb93985d31b46466c90dc39378904 100644 --- a/dune/gdt/tools/timestepper/adaptive-rungekutta-kinetic.hh +++ b/dune/gdt/tools/timestepper/adaptive-rungekutta-kinetic.hh @@ -200,20 +200,22 @@ public: auto& t = current_time(); auto& alpha_n = current_solution(); + const auto num_dofs = alpha_n.dofs().vector().size(); size_t first_stage_to_compute = 0; if (first_same_as_last_ && last_stage_of_previous_step_) { stages_k_[0].dofs().vector() = last_stage_of_previous_step_->dofs().vector(); first_stage_to_compute = 1; } first_same_as_last_ = true; - while (mixed_error > 1.) { + while (!(mixed_error < 1.)) { bool skip_error_computation = false; actual_dt *= time_step_scale_factor; for (size_t ii = first_stage_to_compute; ii < num_stages_ - 1; ++ii) { - set_op_param("t", t + actual_dt * c_[ii]), stages_k_[ii].dofs().vector() *= 0.; + set_op_param("t", t + actual_dt * c_[ii]); + std::fill_n(&(stages_k_[ii].dofs().vector()[0]), num_dofs, 0.); alpha_tmp_.dofs().vector() = alpha_n.dofs().vector(); for (size_t jj = 0; jj < ii; ++jj) - alpha_tmp_.dofs().vector().axpy(actual_dt * r_ * (A_[ii][jj]), stages_k_[jj].dofs().vector()); + alpha_tmp_.dofs().vector().axpy(actual_dt * r_ * A_[ii][jj], stages_k_[jj].dofs().vector()); try { op_.apply(alpha_tmp_.dofs().vector(), stages_k_[ii].dofs().vector(), op_param_); if (regularize_if_needed(consider_regularization, r_it, r_sequence)) { @@ -247,7 +249,7 @@ public: // calculate last stage set_op_param("t", t + actual_dt * c_[num_stages_ - 1]); - stages_k_[num_stages_ - 1].dofs().vector() *= 0.; + std::fill_n(&(stages_k_[num_stages_ - 1].dofs().vector()[0]), num_dofs, 0.); try { op_.apply(alpha_np1_.dofs().vector(), stages_k_[num_stages_ - 1].dofs().vector(), op_param_); if (regularize_if_needed(consider_regularization, r_it, r_sequence)) { @@ -273,31 +275,40 @@ public: for (size_t ii = 0; ii < num_stages_; ++ii) alpha_tmp_.dofs().vector().axpy(actual_dt * r_ * b_2_[ii], stages_k_[ii].dofs().vector()); - // calculate error const auto* alpha_tmp_data = XT::Common::VectorAbstraction<typename DiscreteFunctionType::VectorType>::data(alpha_tmp_.dofs().vector()); const auto* alpha_np1_data = XT::Common::VectorAbstraction<typename DiscreteFunctionType::VectorType>::data(alpha_np1_.dofs().vector()); - mixed_error = - XT::Common::transform_reduce(alpha_tmp_data, - alpha_tmp_data + alpha_tmp_.dofs().vector().size(), - alpha_np1_data, - 0., - /*reduction*/ [](const auto& a, const auto& b) { return std::max(a, b); }, - /*transformation*/ - [atol = atol_, rtol = rtol_](const auto& a, const auto& b) { - return std::abs(a - b) / (atol + std::max(std::abs(a), std::abs(b)) * rtol); - }); - // std::cout << mixed_error << std::endl; - // scale dt to get the estimated optimal time step length - time_step_scale_factor = - std::min(std::max(0.8 * std::pow(1. / mixed_error, 1. / (q + 1.)), scale_factor_min_), scale_factor_max_); - - // maybe adjust alpha to enforce a minimum density or avoid problems with matrix conditions - if (!(mixed_error > 1.) - && min_density_setter_.apply_with_dt(alpha_np1_.dofs().vector(), alpha_np1_.dofs().vector(), actual_dt)) { - // we cannot use the first-same-as-last property for the next time step if we changed alpha - first_same_as_last_ = false; + + // if b == NaN, std::max(a, b) might return a, so mixed_error might be non-NaN though there are NaNs in the + // vectors So check for NaNs before + bool nan_found = check_for_nan(alpha_tmp_data, alpha_np1_data, num_dofs); + if (nan_found) { + mixed_error = 1e10; + time_step_scale_factor = 0.5; + } else { + // calculate error + mixed_error = XT::Common::transform_reduce( + alpha_tmp_data, + alpha_tmp_data + num_dofs, + alpha_np1_data, + 0., + /*reduction*/ [](const auto& a, const auto& b) { return std::max(a, b); }, + /*transformation*/ + [atol = atol_, rtol = rtol_](const auto& a, const auto& b) { + return std::abs(a - b) / (atol + std::max(std::abs(a), std::abs(b)) * rtol); + }); + // std::cout << mixed_error << std::endl; + // scale dt to get the estimated optimal time step length + time_step_scale_factor = + std::min(std::max(0.8 * std::pow(1. / mixed_error, 1. / (q + 1.)), scale_factor_min_), scale_factor_max_); + + // maybe adjust alpha to enforce a minimum density or avoid problems with matrix conditions + if (mixed_error < 1. + && min_density_setter_.apply_with_dt(alpha_np1_.dofs().vector(), alpha_np1_.dofs().vector(), actual_dt)) { + // we cannot use the first-same-as-last property for the next time step if we changed alpha + first_same_as_last_ = false; + } } } // if (!skip_error_computation) } // while (mixed_error > 1.) @@ -315,6 +326,14 @@ public: } // ... step(...) private: + bool check_for_nan(const RangeFieldType* vec1, const RangeFieldType* vec2, const size_t num_dofs) + { + for (size_t ii = 0; ii < num_dofs; ++ii) + if (std::isnan(vec1[ii] + vec2[ii])) + return true; + return false; + } + const MinDensitySetterType min_density_setter_; const OperatorType& op_; const EntropyFluxType& entropy_flux_;