22 requires std::integral<T> || std::floating_point<T>
23void dump_py(std::ostream& fh, std::string
const& var, T
const val)
25 fh << var <<
" = " << val <<
'\n';
29template <
typename Vec>
30void dump_py_vec(std::ostream& fh, std::string
const& var, Vec
const& val)
32 fh << var <<
" = np.array([";
33 for (
decltype(val.size()) i = 0; i < val.size(); ++i)
54void dump_py(std::ostream& fh, std::string
const& var,
55 std::vector<double>
const& val)
61template <
typename Derived>
62void dump_py(std::ostream& fh, std::string
const& var,
63 Eigen::ArrayBase<Derived>
const& val,
64 std::integral_constant<int, 1> )
70template <
typename Derived,
int ColsAtCompileTime>
71void dump_py(std::ostream& fh, std::string
const& var,
72 Eigen::ArrayBase<Derived>
const& val,
73 std::integral_constant<int, ColsAtCompileTime> )
75 fh << var <<
" = np.array([\n";
76 for (std::ptrdiff_t r = 0; r < val.rows(); ++r)
83 for (std::ptrdiff_t c = 0; c < val.cols(); ++c)
97template <
typename Derived>
98void dump_py(std::ostream& fh, std::string
const& var,
99 Eigen::ArrayBase<Derived>
const& val)
102 std::integral_constant<int, Derived::ColsAtCompileTime>{});
106template <
typename Derived>
107void dump_py(std::ostream& fh, std::string
const& var,
108 Eigen::MatrixBase<Derived>
const& val)
115 "The local matrices M or K or the local vectors b assembled with the two "
116 "different Jacobian assemblers differ.";
121 auto abs_diff = (mat_or_vec2 - mat_or_vec1).array().eval();
122 auto const rel_diff =
127 (mat_or_vec1.cwiseAbs() + mat_or_vec2.cwiseAbs()).array())
129 return std::pair{std::move(abs_diff), std::move(rel_diff)};
132template <
typename MatOrVec>
135 return (mat_or_vec <= threshold)
136 .select(MatOrVec::Zero(mat_or_vec.rows(), mat_or_vec.cols()),
137 MatOrVec::Ones(mat_or_vec.rows(), mat_or_vec.cols()))
142bool isSimilar(
auto const& mat_or_vec1,
auto const& mat_or_vec2,
143 double const abs_tol,
double const rel_tol)
145 if (mat_or_vec1.size() == 0 || mat_or_vec2.size() == 0)
150 if (mat_or_vec1.rows() != mat_or_vec2.rows() ||
151 mat_or_vec1.cols() != mat_or_vec2.cols())
156 auto const [abs_diff, rel_diff] =
158 auto const abs_tol_exceeded = abs_diff.abs() > abs_tol;
159 auto const rel_tol_exceeded = rel_diff.abs() > rel_tol;
162 return !(abs_tol_exceeded && rel_tol_exceeded).any();
177 std::unique_ptr<AbstractJacobianAssembler>&& asm1,
178 std::unique_ptr<AbstractJacobianAssembler>&& asm2,
184 std::string
const& log_file_path)
185 :
asm1_{std::move(asm1)},
186 asm2_{std::move(asm2)},
194 log_file_.precision(std::numeric_limits<double>::max_digits10);
196 "import numpy as np\n"
197 "from numpy import nan\n"
203 double const t,
double const dt,
204 std::vector<double>
const& local_x,
205 std::vector<double>
const& local_x_prev,
206 std::vector<double>& local_b_data,
207 std::vector<double>& local_Jac_data);
210 std::unique_ptr<AbstractJacobianAssembler>
asm1_;
211 std::unique_ptr<AbstractJacobianAssembler>
asm2_;
236 double const t,
double const dt, std::vector<double>
const& local_x,
237 std::vector<double>
const& local_x_prev, std::vector<double>& local_b_data,
238 std::vector<double>& local_Jac_data)
242 auto const num_dof = local_x.size();
246 asm1_->assembleWithJacobian(mesh_item_id, local_assembler, t, dt, local_x,
247 local_x_prev, local_b_data, local_Jac_data);
251 std::vector<double> local_b_data2;
252 std::vector<double> local_Jac_data2;
255 asm2_->assembleWithJacobian(mesh_item_id, local_assembler, t, dt, local_x,
256 local_x_prev, local_b_data2, local_Jac_data2);
261 auto const local_Jac2 =
264 auto const [abs_diff, rel_diff] =
265 signedAbsDiffRelDiff(local_Jac1, local_Jac2);
267 auto const abs_diff_mask =
268 oneIfAboveThresholdElseZero(abs_diff.abs(),
abs_tol_Jac_);
269 auto const rel_diff_mask =
270 oneIfAboveThresholdElseZero(rel_diff.abs(),
rel_tol_Jac_);
272 auto const abs_diff_OK = !abs_diff_mask.any();
273 auto const rel_diff_OK = !rel_diff_mask.any();
275 std::ostringstream msg_tolerance;
276 bool tol_exceeded =
true;
277 bool fatal_error =
false;
281 tol_exceeded =
false;
285 msg_tolerance <<
"absolute tolerance of " <<
abs_tol_Jac_
291 tol_exceeded =
false;
295 if (!msg_tolerance.str().empty())
297 msg_tolerance <<
" and ";
300 msg_tolerance <<
"relative tolerance of " <<
rel_tol_Jac_
306 Eigen::VectorXd res1 = Eigen::VectorXd::Zero(num_dof);
309 if (local_b1.size() != 0)
311 res1.noalias() -= local_b1;
314 Eigen::VectorXd res2 = Eigen::VectorXd::Zero(num_dof);
315 if (local_b2.size() != 0)
317 res2.noalias() -= local_b2;
324 WARN(
"Compare Jacobians: {:s}", msg_tolerance.str());
327 bool const output = tol_exceeded || fatal_error;
332 <<
", t: " << t <<
", element_id: " << mesh_item_id
339 <<
"#######################################################\n"
340 <<
"# FATAL ERROR: " << msg_fatal <<
'\n'
341 <<
"# You cannot expect any meaningful insights "
342 "from the Jacobian data printed below!\n"
343 <<
"# The reason for the mentioned differences "
345 <<
"# (a) that the assembly routine has side "
347 <<
"# (b) that the assembly routines for b "
348 "themselves differ.\n"
349 <<
"#######################################################\n"
355 log_file_ <<
"# " << msg_tolerance.str() <<
"\n\n";
364 dump_py(
log_file_,
"element_id", mesh_item_id);
375 dump_py(
log_file_,
"local_x_prev", local_x_prev);
379 dump_py(
log_file_,
"Jacobian_1", local_Jac1);
380 dump_py(
log_file_,
"Jacobian_2", local_Jac2);
384 log_file_ <<
"# Jacobian_2 - Jacobian_1\n";
385 dump_py(
log_file_,
"abs_diff", abs_diff);
386 log_file_ <<
"# max(|abs_diff|) = " << abs_diff.abs().maxCoeff()
388 log_file_ <<
"# Componentwise: 2 * abs_diff / (|Jacobian_1| + "
390 dump_py(
log_file_,
"rel_diff", rel_diff);
391 log_file_ <<
"# max(|rel_diff|) = " << rel_diff.abs().maxCoeff()
396 log_file_ <<
"# Masks: 0 ... tolerance met, 1 ... tolerance exceeded\n";
397 dump_py(
log_file_,
"abs_diff_mask", abs_diff_mask);
398 dump_py(
log_file_,
"rel_diff_mask", rel_diff_mask);
403 dump_py(
log_file_,
"b_2", local_b_data2);
404 if (fatal_error && local_b1.size() == local_b2.size())
406 dump_py(
log_file_,
"delta_b", local_b2 - local_b1);
414 dump_py(
log_file_,
"delta_res", res2 - res1);
432 "OGS failed, because the two Jacobian implementations returned "
433 "different results.");
439 std::unique_ptr<AbstractJacobianAssembler>&& asm1,
440 std::unique_ptr<AbstractJacobianAssembler>&& asm2,
double abs_tol_Jac,
441 double rel_tol_Jac,
double abs_tol_res,
double rel_tol_res,
442 bool fail_on_error, std::string
const& log_file_path)
443 :
impl_{std::make_shared<
detail::CompareJacobiansJacobianAssemblerImpl>(
444 std::move(asm1), std::move(asm2), abs_tol_Jac, rel_tol_Jac,
445 abs_tol_res, rel_tol_res, fail_on_error, log_file_path)}
450 std::shared_ptr<detail::CompareJacobiansJacobianAssemblerImpl> impl,
452 :
impl_{std::move(impl)}
458 double const t,
double const dt, std::vector<double>
const& local_x,
459 std::vector<double>
const& local_x_prev, std::vector<double>& local_b_data,
460 std::vector<double>& local_Jac_data)
462 impl_->assembleWithJacobian(mesh_item_id, local_assembler, t, dt, local_x,
463 local_x_prev, local_b_data, local_Jac_data);
466std::unique_ptr<AbstractJacobianAssembler>
470 if (omp_get_thread_num() != 0)
473 "CompareJacobiansJacobianAssembler cannot be used concurrently. "
474 "Please restrict yourself to one assembly thread "
475 "(OGS_ASM_THREADS=1).");
479 return std::make_unique<CompareJacobiansJacobianAssembler>(
impl_,
Key{});
483 int const max_non_deformation_dofs_per_node)
const
485 impl_->asm1_->checkPerturbationSize(max_non_deformation_dofs_per_node);
486 impl_->asm2_->checkPerturbationSize(max_non_deformation_dofs_per_node);
490 std::vector<int>
const& non_deformation_component_ids)
492 impl_->asm1_->setNonDeformationComponentIDs(non_deformation_component_ids);
493 impl_->asm2_->setNonDeformationComponentIDs(non_deformation_component_ids);
498 std::vector<int>
const& non_deformation_component_ids)
500 impl_->asm1_->setNonDeformationComponentIDsNoSizeCheck(
501 non_deformation_component_ids);
502 impl_->asm2_->setNonDeformationComponentIDsNoSizeCheck(
503 non_deformation_component_ids);
508 return impl_->asm1_->needsPicardAssembly() ||
509 impl_->asm2_->needsPicardAssembly();
517std::unique_ptr<CompareJacobiansJacobianAssembler>
549 return std::make_unique<CompareJacobiansJacobianAssembler>(
550 std::move(asm1), std::move(asm2), abs_tol, rel_tol, abs_tol_res,
551 rel_tol_res, fail_on_error, log_file);
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
T getConfigParameter(std::string const ¶m) const
ConfigTree getConfigSubtree(std::string const &root) const
void checkConfigParameter(std::string const ¶m, std::string_view const value) const
virtual void preIteration(const unsigned iter) override
void assembleWithJacobian(std::size_t const mesh_item_id, LocalAssemblerInterface &local_assembler, double const t, double const dt, std::vector< double > const &local_x, std::vector< double > const &local_x_prev, std::vector< double > &local_b_data, std::vector< double > &local_Jac_data) override
bool needsPicardAssembly() const override
std::unique_ptr< AbstractJacobianAssembler > copy() const override
CompareJacobiansJacobianAssembler(std::unique_ptr< AbstractJacobianAssembler > &&asm1, std::unique_ptr< AbstractJacobianAssembler > &&asm2, double abs_tol_Jac, double rel_tol_Jac, double abs_tol_res, double rel_tol_res, bool fail_on_error, std::string const &log_file_path)
void setNonDeformationComponentIDsNoSizeCheck(std::vector< int > const &non_deformation_component_ids) override
std::shared_ptr< detail::CompareJacobiansJacobianAssemblerImpl > impl_
void checkPerturbationSize(int const max_non_deformation_dofs_per_node) const override
void setNonDeformationComponentIDs(std::vector< int > const &non_deformation_component_ids) override
Eigen::Map< const Vector > toVector(std::vector< double > const &data, Eigen::VectorXd::Index size)
Creates an Eigen mapped vector from the given data vector.
Eigen::Map< const Matrix > toMatrix(std::vector< double > const &data, Eigen::MatrixXd::Index rows, Eigen::MatrixXd::Index cols)
std::unique_ptr< CompareJacobiansJacobianAssembler > createCompareJacobiansJacobianAssembler(BaseLib::ConfigTree const &config)
std::unique_ptr< AbstractJacobianAssembler > createJacobianAssembler(std::optional< BaseLib::ConfigTree > const &config)
bool isSimilar(auto const &mat_or_vec1, auto const &mat_or_vec2, double const abs_tol, double const rel_tol)
auto signedAbsDiffRelDiff(auto const &mat_or_vec1, auto const &mat_or_vec2)
(Signed) absolute and (symmetric) relative difference as Eigen::Array
auto oneIfAboveThresholdElseZero(MatOrVec &&mat_or_vec, double const threshold)
void dump_py_vec(std::ostream &fh, std::string const &var, Vec const &val)
Dumps an arbitrary vector as a Python script snippet.
void dump_py(std::ostream &fh, std::string const &var, T const val)
Dumps a numeric value as a Python script snippet.
const std::string msg_fatal
Will be printed if some consistency error is detected.
bool const fail_on_error_
Whether to abort if the tolerances are exceeded.
double const abs_tol_res_
std::unique_ptr< AbstractJacobianAssembler > asm2_
double const rel_tol_Jac_
void assembleWithJacobian(std::size_t const mesh_item_id, LocalAssemblerInterface &local_assembler, double const t, double const dt, std::vector< double > const &local_x, std::vector< double > const &local_x_prev, std::vector< double > &local_b_data, std::vector< double > &local_Jac_data)
CompareJacobiansJacobianAssemblerImpl(std::unique_ptr< AbstractJacobianAssembler > &&asm1, std::unique_ptr< AbstractJacobianAssembler > &&asm2, double abs_tol_Jac, double rel_tol_Jac, double abs_tol_res, double rel_tol_res, bool fail_on_error, std::string const &log_file_path)
double const abs_tol_Jac_
std::unique_ptr< AbstractJacobianAssembler > asm1_
double const rel_tol_res_