OGS
AssemblyMixin.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#pragma once
5
6#include "BaseLib/MPI.h"
7#include "BaseLib/RunTime.h"
9#include "NumLib/Exceptions.h"
17
18namespace ProcessLib
19{
23{
30
31protected:
32 explicit AssemblyMixinBase(AbstractJacobianAssembler& jacobian_assembler,
33 bool const is_linear,
34 bool const use_monolithic_scheme);
35
37 MeshLib::Mesh& bulk_mesh,
38 std::vector<std::reference_wrapper<MeshLib::Mesh>> const& submeshes,
39 std::vector<std::vector<std::string>> const& residuum_names,
40 std::vector<std::vector<std::reference_wrapper<ProcessVariable>>> const&
41 pvs);
42
43 void updateActiveElements(ProcessLib::Process const& process);
44
45 bool isLinear() const { return is_linear_; }
46
48 GlobalVector const& rhs,
49 NumLib::LocalToGlobalIndexMap const& local_to_global_index_map,
50 std::vector<std::reference_wrapper<MeshLib::PropertyVector<double>>>
51 residuum_vectors);
52
54 int const process_id,
55 GlobalVector const& rhs,
56 NumLib::LocalToGlobalIndexMap const& local_to_global_index_map,
58
59private:
60 void updateActiveElementsImpl(Process const& process);
61
62protected:
64 std::vector<ProcessLib::Assembly::SubmeshAssemblyData>
66
68 std::vector<AssembledMatrixCache> submesh_matrix_cache_;
69
71 std::optional<ProcessLib::Assembly::BulkMeshAssemblyData>
73
75
77
81
82 std::optional<NumLib::NonlinearSolverTag> last_assembly_was_;
83
84private:
86};
87
93template <typename Process>
95{
96 // Enforce correct use of CRTP, i.e., that Process is derived from
97 // AssemblyMixin<Process>.
99 friend Process;
100
101public:
124 std::vector<std::reference_wrapper<MeshLib::Mesh>> const& submeshes,
125 std::vector<std::vector<std::string>> const& residuum_names)
126 {
128 derived().getMesh(), submeshes, residuum_names,
129 derived().getProcessVariables());
130 }
131
136
137 // sorted_element_subset can be used to restrict assembly to a custom
138 // element subset. That's used at the moment (Jan 2026) for some assembly
139 // optimizations in the HeatTransportBHE process.
141 double const t, double const dt, std::vector<GlobalVector*> const& x,
142 std::vector<GlobalVector*> const& x_prev, int const process_id,
144 std::vector<std::size_t> const* const sorted_element_subset = nullptr,
145 bool const copy_residua_to_mesh = false)
146 {
147 DBUG("AssemblyMixin assemble(t={:g}, dt={:g}, process_id={}).", t, dt,
148 process_id);
149
151
152 std::vector<NumLib::LocalToGlobalIndexMap const*> const dof_tables =
153 derived().getDOFTables(x.size());
154
155 std::exception_ptr const exception =
157 ? assembleOnBulkMesh(t, dt, x, x_prev, dof_tables, process_id,
158 M, K, b, sorted_element_subset)
159 : assembleOnSubmeshes(t, dt, x, x_prev, dof_tables, process_id,
160 M, K, b, sorted_element_subset,
161 copy_residua_to_mesh);
162
164
165 if (copy_residua_to_mesh && bulk_mesh_assembly_data_)
166 {
167 // Note: computeResiduum() uses bwd/fwd Euler scheme.
168 GlobalVector const neg_res_bulkmesh = ProcessLib::computeResiduum(
169 dt, *x[process_id], *x_prev[process_id], M, K, b);
170
172 neg_res_bulkmesh, *(dof_tables[process_id]),
173 bulk_mesh_assembly_data_->residuum_vectors[process_id]);
174 }
175 }
176
177 // sorted_element_subset can be used to restrict assembly to a custom
178 // element subset. That's used at the moment (Jan 2026) for some assembly
179 // optimizations in the HeatTransportBHE process.
181 double const t, double const dt, std::vector<GlobalVector*> const& x,
182 std::vector<GlobalVector*> const& x_prev, int const process_id,
183 GlobalVector& b, GlobalMatrix& Jac,
184 std::vector<std::size_t> const* const sorted_element_subset = nullptr,
185 bool const copy_residua_to_mesh = false)
186 {
187 DBUG(
188 "AssemblyMixin assembleWithJacobian(t={:g}, dt={:g}, "
189 "process_id={}).",
190 t, dt, process_id);
191
193
194 // Note: We do not cache the Jacobian or b vector. It does not make
195 // sense, because even in the linear case the b vector changes if
196 // the solution changes (unless steady state is reached).
197 // Todo: If we separated Jacobian and residuum assembly, we could
198 // cache at least the Jacobian and only update the residuum.
199 if (is_linear_)
200 {
201 DBUG(
202 "You specified that this process is linear. The assembly for "
203 "the Newton-Raphson method will be run anyways.");
204 }
205
206 std::vector<NumLib::LocalToGlobalIndexMap const*> const dof_tables =
207 derived().getDOFTables(x.size());
208
209 std::exception_ptr const exception =
212 *bulk_mesh_assembly_data_, t, dt, x, x_prev, dof_tables,
213 process_id, b, Jac, sorted_element_subset)
215 t, dt, x, x_prev, dof_tables, process_id, b, Jac,
216 sorted_element_subset, copy_residua_to_mesh);
217
219
221
222 // TODO better for submesh case, too?
223 // TODO check copy_residua_to_mesh, too. But that needs a refactoring of
224 // postTimestep(), computeSecondaryVariable() etc. hooks.
225 if (/*copy_residua_to_mesh &&*/ bulk_mesh_assembly_data_)
226 {
228 b, *(dof_tables[process_id]),
229 bulk_mesh_assembly_data_->residuum_vectors[process_id]);
230 }
231 }
232
233 void preOutput(double const t,
234 double const dt,
235 std::vector<GlobalVector*> const& x,
236 std::vector<GlobalVector*> const& x_prev,
237 int const process_id)
238 {
240 {
241 WARN("AssemblyMixin. Skipping preOutput() step.");
242 return;
243 }
244
245 switch (*last_assembly_was_)
246 {
248 preOutputPicard(t, dt, x, x_prev, process_id);
249 return;
251 preOutputNewton(t, dt, x, x_prev, process_id);
252 return;
253 }
254 }
255
256private:
257 Process& derived() { return static_cast<Process&>(*this); }
258 Process const& derived() const
259 {
260 return static_cast<Process const&>(*this);
261 }
262
264 [[nodiscard]] std::exception_ptr assembleOnBulkMeshOrOnSubmeshCommon(
265 Assembly::CommonAssemblyData const& assembly_data,
266 AssembledMatrixCache& mat_cache, double const t, double const dt,
267 std::vector<GlobalVector*> const& x,
268 std::vector<GlobalVector*> const& x_prev,
269 std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_tables,
270 int const process_id, GlobalMatrix& M, GlobalMatrix& K, GlobalVector& b,
271 std::vector<std::size_t> const* const sorted_element_subset)
272 {
273 std::exception_ptr exception = nullptr;
274
275 try
276 {
277 auto const& loc_asms = derived().local_assemblers_;
278
279 pvma_.assemble(
280 loc_asms,
281 assembly_data.activeElementIDsSorted(sorted_element_subset)
282 .get(),
283 dof_tables, t, dt, x, x_prev, process_id, M, K, b);
284 }
285 catch (NumLib::AssemblyException const&)
286 {
287 exception = std::current_exception();
288 }
289
293
294 if (is_linear_)
295 {
296 DBUG("Saving global M, K, b for later reuse.");
297
298 BaseLib::RunTime time_save;
299 time_save.start();
300
301 mat_cache.storeMKb(M, K, b);
302
303 INFO("[time] Saving global M, K, b took {:g} s",
304 time_save.elapsed());
305 }
306
307 return exception;
308 }
309
310 std::exception_ptr assembleOnBulkMesh(
311 double const t, double const dt, std::vector<GlobalVector*> const& x,
312 std::vector<GlobalVector*> const& x_prev,
313 std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_tables,
314 int const process_id, GlobalMatrix& M, GlobalMatrix& K, GlobalVector& b,
315 std::vector<std::size_t> const* const sorted_element_subset)
316 {
317 if (!bulk_mesh_matrix_cache_.hasMKb())
318 {
321 x_prev, dof_tables, process_id, M, K, b, sorted_element_subset);
322 }
323
324 DBUG("Reusing saved global M, K, b.");
325
326 BaseLib::RunTime time_restore;
327 time_restore.start();
328
329 auto const [M_, K_, b_] = bulk_mesh_matrix_cache_.MKb();
333
334 INFO("[time] Restoring global M, K, b took {:g} s",
335 time_restore.elapsed());
336
337 return nullptr;
338 }
339
340 [[nodiscard]] std::exception_ptr assembleOnSubmeshes(
341 double const t, double const dt, std::vector<GlobalVector*> const& x,
342 std::vector<GlobalVector*> const& x_prev,
343 std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_tables,
344 int const process_id, GlobalMatrix& M, GlobalMatrix& K, GlobalVector& b,
345 std::vector<std::size_t> const* const sorted_element_subset,
346 bool const copy_residua_to_mesh)
347 {
348 auto const mat_spec = derived().getMatrixSpecifications(process_id);
349
350 // Temporary matrices for assembly (reused across submeshes).
351 // They will remain nullptr if no assembly is necessary (linear case &
352 // cache populated).
353 std::unique_ptr<GlobalMatrix> M_tmp;
354 std::unique_ptr<GlobalMatrix> K_tmp;
355 std::unique_ptr<GlobalVector> b_tmp;
356
357 auto getMKb = [&M_tmp, &K_tmp, &b_tmp,
358 &mat_spec](AssembledMatrixCache const& cache)
359 {
360 if (cache.hasMKb())
361 {
362 return cache.MKb();
363 }
364
365 if (!(M_tmp && K_tmp && b_tmp))
366 {
368 mat_spec);
370 mat_spec);
372 mat_spec);
373 }
374
375 return std::tie(*M_tmp, *K_tmp, *b_tmp);
376 };
377
378 std::exception_ptr exception = nullptr;
379 for (auto const& [sad, mat_cache] :
380 ranges::zip_view(submesh_assembly_data_ | ranges::views::all,
381 submesh_matrix_cache_ | ranges::views::all))
382 {
383 auto [M_submesh, K_submesh, b_submesh] = getMKb(mat_cache);
384
385 if (!mat_cache.hasMKb())
386 {
387 M_submesh.setZero();
388 K_submesh.setZero();
389 b_submesh.setZero();
390
392 sad, mat_cache, t, dt, x, x_prev, dof_tables, process_id,
393 M_submesh, K_submesh, b_submesh, sorted_element_subset);
394 }
395
396 // TODO: This operates on the entire global vector b once
397 // for each submesh. If the number of submeshes is high,
398 // that might not perform well.
399 MathLib::LinAlg::axpy(M, 1.0, M_submesh);
400 MathLib::LinAlg::axpy(K, 1.0, K_submesh);
401 MathLib::LinAlg::axpy(b, 1.0, b_submesh);
402
403 if (copy_residua_to_mesh)
404 {
405 // Note: computeResiduum() uses bwd/fwd Euler scheme.
406 GlobalVector const neg_res_submesh =
407 computeResiduum(dt, *x[process_id], *x_prev[process_id],
408 M_submesh, K_submesh, b_submesh);
409
411 process_id, neg_res_submesh, *(dof_tables[process_id]),
412 sad);
413 }
414 }
415
416 return exception;
417 }
418
421 [[nodiscard]] std::exception_ptr
423 Assembly::CommonAssemblyData const& assembly_data, double const t,
424 double const dt, std::vector<GlobalVector*> const& x,
425 std::vector<GlobalVector*> const& x_prev,
426 std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_tables,
427 int const process_id, GlobalVector& b, GlobalMatrix& Jac,
428 std::vector<std::size_t> const* const sorted_element_subset)
429 {
430 auto const& loc_asms = derived().local_assemblers_;
431 std::exception_ptr exception = nullptr;
432
433 try
434 {
435 pvma_.assembleWithJacobian(
436 loc_asms,
437 assembly_data.activeElementIDsSorted(sorted_element_subset)
438 .get(),
439 dof_tables, t, dt, x, x_prev, process_id, b, Jac);
440 }
441 catch (NumLib::AssemblyException const&)
442 {
443 exception = std::current_exception();
444 }
445
447
448 return exception;
449 }
450
451 [[nodiscard]] std::exception_ptr assembleWithJacobianOnSubmeshes(
452 double const t, double const dt, std::vector<GlobalVector*> const& x,
453 std::vector<GlobalVector*> const& x_prev,
454 std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_tables,
455 int const process_id, GlobalVector& b, GlobalMatrix& Jac,
456 std::vector<std::size_t> const* const sorted_element_subset,
457 bool const /*copy_residua_to_mesh*/)
458 {
459 std::exception_ptr exception = nullptr;
460
461 auto const& mat_spec = derived().getMatrixSpecifications(process_id);
462 auto b_submesh =
464
465 for (auto const& sad : submesh_assembly_data_)
466 {
467 b_submesh->setZero();
468
470 sad, t, dt, x, x_prev, dof_tables, process_id, *b_submesh, Jac,
471 sorted_element_subset);
472
473 MathLib::LinAlg::axpy(b, 1.0, *b_submesh);
474
475 // TODO enabling this check needs a refactoring of postTimestep(),
476 // computeSecondaryVariable() etc. hooks.
477 if (/*copy_residua_to_mesh*/ true)
478 {
480 process_id, *b_submesh, *(dof_tables[process_id]), sad);
481 }
482 }
483
484 return exception;
485 }
486
487 void preOutputPicard(double const t,
488 double const dt,
489 std::vector<GlobalVector*> const& x,
490 std::vector<GlobalVector*> const& x_prev,
491 int const process_id)
492 {
493 auto const matrix_specification =
494 derived().getMatrixSpecifications(process_id);
495
497 matrix_specification);
499 matrix_specification);
501 matrix_specification);
502
503 M->setZero();
504 K->setZero();
505 b->setZero();
506
507 assemble(t, dt, x, x_prev, process_id, *M, *K, *b, nullptr, true);
508 }
509
510 void preOutputNewton(double const /*t*/,
511 double const /*dt*/,
512 std::vector<GlobalVector*> const& /*x*/,
513 std::vector<GlobalVector*> const& /*x_prev*/,
514 int const /*process_id*/)
515 {
516 // TODO enabling this needs a refactoring of postTimestep(),
517 // computeSecondaryVariable() etc. hooks.
518#if 0
519 auto const matrix_specification =
520 derived().getMatrixSpecifications(process_id);
521
523 matrix_specification);
525 matrix_specification);
526
527 Jac->setZero();
528 b->setZero();
529
530 assembleWithJacobian(t, dt, x, x_prev, process_id, *b, *Jac, nullptr,
531 true);
532#endif
533 }
534};
535
536} // namespace ProcessLib
MathLib::EigenMatrix GlobalMatrix
MathLib::EigenVector GlobalVector
void INFO(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:28
void DBUG(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:22
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:34
Count the running time.
Definition RunTime.h:18
double elapsed() const
Get the elapsed time in seconds.
Definition RunTime.h:31
void start()
Start the timer.
Definition RunTime.h:21
Base class for Jacobian assemblers.
void initializeAssemblyOnSubmeshes(MeshLib::Mesh &bulk_mesh, std::vector< std::reference_wrapper< MeshLib::Mesh > > const &submeshes, std::vector< std::vector< std::string > > const &residuum_names, std::vector< std::vector< std::reference_wrapper< ProcessVariable > > > const &pvs)
AssemblyMixinBase(AbstractJacobianAssembler &jacobian_assembler, bool const is_linear, bool const use_monolithic_scheme)
Assembly::ParallelVectorMatrixAssembler pvma_
static void copyResiduumVectorsToBulkMesh(GlobalVector const &rhs, NumLib::LocalToGlobalIndexMap const &local_to_global_index_map, std::vector< std::reference_wrapper< MeshLib::PropertyVector< double > > > residuum_vectors)
std::optional< NumLib::NonlinearSolverTag > last_assembly_was_
void updateActiveElementsImpl(Process const &process)
std::vector< AssembledMatrixCache > submesh_matrix_cache_
AssembledMatrixCache for each submesh.
static void copyResiduumVectorsToSubmesh(int const process_id, GlobalVector const &rhs, NumLib::LocalToGlobalIndexMap const &local_to_global_index_map, ProcessLib::Assembly::SubmeshAssemblyData const &sad)
ActiveElementIDsState ids_state_
void updateActiveElements(ProcessLib::Process const &process)
std::vector< ProcessLib::Assembly::SubmeshAssemblyData > submesh_assembly_data_
SubmeshAssemblyData for each submesh.
std::optional< ProcessLib::Assembly::BulkMeshAssemblyData > bulk_mesh_assembly_data_
Empty if submesh assembly is used.
AssembledMatrixCache bulk_mesh_matrix_cache_
AssemblyMixinBase(AbstractJacobianAssembler &jacobian_assembler, bool const is_linear, bool const use_monolithic_scheme)
void assemble(double const t, double const dt, std::vector< GlobalVector * > const &x, std::vector< GlobalVector * > const &x_prev, int const process_id, GlobalMatrix &M, GlobalMatrix &K, GlobalVector &b, std::vector< std::size_t > const *const sorted_element_subset=nullptr, bool const copy_residua_to_mesh=false)
std::exception_ptr assembleOnSubmeshes(double const t, double const dt, std::vector< GlobalVector * > const &x, std::vector< GlobalVector * > const &x_prev, std::vector< NumLib::LocalToGlobalIndexMap const * > const &dof_tables, int const process_id, GlobalMatrix &M, GlobalMatrix &K, GlobalVector &b, std::vector< std::size_t > const *const sorted_element_subset, bool const copy_residua_to_mesh)
std::exception_ptr assembleOnBulkMeshOrOnSubmeshCommon(Assembly::CommonAssemblyData const &assembly_data, AssembledMatrixCache &mat_cache, double const t, double const dt, std::vector< GlobalVector * > const &x, std::vector< GlobalVector * > const &x_prev, std::vector< NumLib::LocalToGlobalIndexMap const * > const &dof_tables, int const process_id, GlobalMatrix &M, GlobalMatrix &K, GlobalVector &b, std::vector< std::size_t > const *const sorted_element_subset)
Common code for Picard assembly on the bulk mesh or on submeshes.
Process const & derived() const
std::exception_ptr assembleWithJacobianOnBulkMeshOrOnSubmeshCommon(Assembly::CommonAssemblyData const &assembly_data, double const t, double const dt, std::vector< GlobalVector * > const &x, std::vector< GlobalVector * > const &x_prev, std::vector< NumLib::LocalToGlobalIndexMap const * > const &dof_tables, int const process_id, GlobalVector &b, GlobalMatrix &Jac, std::vector< std::size_t > const *const sorted_element_subset)
void preOutputPicard(double const t, double const dt, std::vector< GlobalVector * > const &x, std::vector< GlobalVector * > const &x_prev, int const process_id)
std::exception_ptr assembleOnBulkMesh(double const t, double const dt, std::vector< GlobalVector * > const &x, std::vector< GlobalVector * > const &x_prev, std::vector< NumLib::LocalToGlobalIndexMap const * > const &dof_tables, int const process_id, GlobalMatrix &M, GlobalMatrix &K, GlobalVector &b, std::vector< std::size_t > const *const sorted_element_subset)
void preOutput(double const t, double const dt, std::vector< GlobalVector * > const &x, std::vector< GlobalVector * > const &x_prev, int const process_id)
void preOutputNewton(double const, double const, std::vector< GlobalVector * > const &, std::vector< GlobalVector * > const &, int const)
void initializeAssemblyOnSubmeshes(std::vector< std::reference_wrapper< MeshLib::Mesh > > const &submeshes, std::vector< std::vector< std::string > > const &residuum_names)
void assembleWithJacobian(double const t, double const dt, std::vector< GlobalVector * > const &x, std::vector< GlobalVector * > const &x_prev, int const process_id, GlobalVector &b, GlobalMatrix &Jac, std::vector< std::size_t > const *const sorted_element_subset=nullptr, bool const copy_residua_to_mesh=false)
std::exception_ptr assembleWithJacobianOnSubmeshes(double const t, double const dt, std::vector< GlobalVector * > const &x, std::vector< GlobalVector * > const &x_prev, std::vector< NumLib::LocalToGlobalIndexMap const * > const &dof_tables, int const process_id, GlobalVector &b, GlobalMatrix &Jac, std::vector< std::size_t > const *const sorted_element_subset, bool const)
std::vector< NumLib::LocalToGlobalIndexMap const * > getDOFTables(int const number_of_processes) const
Definition Process.cpp:376
MathLib::MatrixSpecifications getMatrixSpecifications(const int process_id) const override
Definition Process.cpp:203
void allRanksThrowOrNone(std::exception_ptr const &exception, auto &&warning_callback)
Definition MPI.h:236
void finalizeAssembly(PETScMatrix &A)
Definition LinAlg.cpp:191
void copy(PETScVector const &x, PETScVector &y)
Definition LinAlg.cpp:30
void axpy(PETScVector &y, PetscScalar const a, PETScVector const &x)
Definition LinAlg.cpp:50
GlobalVector computeResiduum(double const dt, GlobalVector const &x, GlobalVector const &x_prev, GlobalMatrix const &M, GlobalMatrix const &K, GlobalVector const &b)
Stores assembled global M, K, b matrices/vectors for later reuse.
void storeMKb(GlobalMatrix const &M, GlobalMatrix const &K, GlobalVector const &b)
Store data.
std::shared_ptr< std::vector< std::size_t > const > activeElementIDsSorted(std::vector< std::size_t > const *const sorted_element_subset) const