Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
contact_data.cpp
1 // Copyright (c) Lawrence Livermore National Security, LLC and
2 // other Smith Project Developers. See the top-level LICENSE file for
3 // details.
4 //
5 // SPDX-License-Identifier: (BSD-3-Clause)
6 
8 
9 #include <cstddef>
10 #include <format>
11 #include <memory>
12 
13 #include "axom/slic.hpp"
14 #include "mpi.h"
15 
17 
18 #ifdef SMITH_USE_TRIBOL
19 #include "tribol/interface/tribol.hpp"
20 #include "tribol/interface/mfem_tribol.hpp"
21 #include "tribol/mesh/CouplingScheme.hpp"
22 #endif
23 
24 namespace smith {
25 
26 #ifdef SMITH_USE_TRIBOL
27 
28 ContactData::ContactData(const mfem::ParMesh& mesh)
29  : mesh_{mesh},
30  reference_nodes_{static_cast<const mfem::ParGridFunction*>(mesh.GetNodes())},
31  current_coords_{*reference_nodes_},
32  have_lagrange_multipliers_{false},
33  num_pressure_dofs_{0},
34  offsets_up_to_date_{false}
35 {
36 }
37 
38 ContactData::~ContactData() { tribol::finalize(); }
39 
40 void ContactData::addContactInteraction(int interaction_id, const std::set<int>& bdry_attr_surf1,
41  const std::set<int>& bdry_attr_surf2, ContactOptions contact_opts)
42 {
43  // Disallow duplicate ids globally: Tribol coupling schemes are keyed by cs_id (interaction_id) globally.
44  auto* cs = tribol::CouplingSchemeManager::getInstance().findData(static_cast<tribol::IndexT>(interaction_id));
45  SLIC_ERROR_ROOT_IF(cs != nullptr,
46  std::format("Contact interaction id {} is already registered with Tribol.", interaction_id));
47 
48  interactions_.emplace_back(interaction_id, mesh_, bdry_attr_surf1, bdry_attr_surf2, current_coords_, contact_opts);
49  if (contact_opts.enforcement == ContactEnforcement::LagrangeMultiplier) {
50  have_lagrange_multipliers_ = true;
51  num_pressure_dofs_ += interactions_.back().numPressureDofs();
52  offsets_up_to_date_ = false;
53  }
54  // specify all contact boundaries
55  mfem::Array<int> contact_bdry_attribs;
56  contact_bdry_attribs.SetSize(mesh_.bdr_attributes.Max());
57  contact_bdry_attribs = 0;
58  // attributes start at 1,
59  // shift by -1 to account for zero-based array indexing
60  for (const auto& bdry_attr : bdry_attr_surf1) {
61  contact_bdry_attribs[bdry_attr - 1] = 1;
62  }
63  for (const auto& bdry_attr : bdry_attr_surf2) {
64  contact_bdry_attribs[bdry_attr - 1] = 1;
65  }
66  // dofs for the current contact interaction
67  mfem::Array<int> contact_interaction_dofs;
68  reference_nodes_->ParFESpace()->GetEssentialTrueDofs(contact_bdry_attribs, contact_interaction_dofs);
69  // add dofs for current contact interaction call to all contact_dofs_
70  contact_dofs_.Append(contact_interaction_dofs);
71  // sort and delete duplicates
72  contact_dofs_.Sort();
73  contact_dofs_.Unique();
74 }
75 
76 void ContactData::reset()
77 {
78  for (auto& interaction : interactions_) {
79  FiniteElementState zero = interaction.pressure();
80  zero = 0.0;
81  interaction.setPressure(zero);
82  }
83 }
84 
85 void ContactData::updateGeometry(int cycle, double time, double& dt,
86  std::optional<std::reference_wrapper<const mfem::Vector>> u_shape,
87  std::optional<std::reference_wrapper<const mfem::Vector>> u, bool eval_jacobian)
88 {
89  cycle_ = cycle;
90  time_ = time;
91  dt_ = dt;
92 
93  if (u_shape && u) {
94  setDisplacements(u_shape->get(), u->get());
95  }
96 
97  for (auto& interaction : interactions_) {
98  interaction.evalJacobian(eval_jacobian);
99  }
100  // This updates the redecomposed surface mesh based on the current displacement, then transfers field quantities to
101  // the updated mesh.
102  if (u_shape && u) {
103  tribol::updateMfemParallelDecomposition();
104  }
105  // This function computes gaps (and optionally geometric Jacobian blocks) based on the current mesh.
106  tribol::update(cycle, time, dt);
107 }
108 
109 void ContactData::updateForcesAndJacobian(int cycle, double time, double& dt,
110  std::optional<std::reference_wrapper<const mfem::Vector>> u_shape,
111  std::optional<std::reference_wrapper<const mfem::Vector>> u,
112  std::optional<std::reference_wrapper<const mfem::Vector>> p)
113 {
114  if (u_shape && u) {
115  updateGeometry(cycle, time, dt, u_shape, u, false);
116  } else {
117  cycle_ = cycle;
118  time_ = time;
119  dt_ = dt;
120  }
121 
122  SLIC_ERROR_ROOT_IF(haveLagrangeMultipliers() && !p,
123  "updateForcesAndJacobian() requires pressure values for Lagrange multiplier contact.");
124 
125  mfem::Vector zero_pressures;
126  if (!p) {
127  zero_pressures.SetSize(numPressureDofs());
128  zero_pressures = 0.0;
129  }
130  const mfem::Vector& pressures = p ? p->get() : zero_pressures;
131 
132  // With updated gaps, we can update pressure for contact interactions (active set detection and penalty).
133  setPressures(pressures);
134 
135  for (auto& interaction : interactions_) {
136  interaction.evalJacobian(true);
137  }
138  // This second call is required to synchronize the updated pressures to Tribol's internal redecomposed surface mesh
139  // and to ensure Tribol's internal state is correctly reset for the second pass.
140  tribol::updateMfemParallelDecomposition();
141  tribol::update(cycle, time, dt);
142 }
143 
144 FiniteElementDual ContactData::forces() const
145 {
146  FiniteElementDual f(*reference_nodes_->ParFESpace(), "contact force");
147  for (const auto& interaction : interactions_) {
148  f += interaction.forces();
149  }
150  return f;
151 }
152 
153 mfem::HypreParVector ContactData::mergedPressures() const
154 {
155  updateDofOffsets();
156  mfem::HypreParVector merged_p(mesh_.GetComm(), global_pressure_dof_offsets_[global_pressure_dof_offsets_.Size() - 1],
157  global_pressure_dof_offsets_.GetData());
158  for (size_t i{0}; i < interactions_.size(); ++i) {
159  if (interactions_[i].getContactOptions().enforcement == ContactEnforcement::LagrangeMultiplier) {
160  mfem::Vector p_interaction;
161  p_interaction.MakeRef(
162  merged_p, pressure_dof_offsets_[static_cast<int>(i)],
163  pressure_dof_offsets_[static_cast<int>(i) + 1] - pressure_dof_offsets_[static_cast<int>(i)]);
164  p_interaction.Set(1.0, interactions_[i].pressure());
165  }
166  }
167  return merged_p;
168 }
169 
170 mfem::HypreParVector ContactData::mergedGaps(bool zero_inactive) const
171 {
172  updateDofOffsets();
173  mfem::HypreParVector merged_g(mesh_.GetComm(), global_pressure_dof_offsets_[global_pressure_dof_offsets_.Size() - 1],
174  global_pressure_dof_offsets_.GetData());
175  for (size_t i{0}; i < interactions_.size(); ++i) {
176  if (interactions_[i].getContactOptions().enforcement == ContactEnforcement::LagrangeMultiplier) {
177  auto g = interactions_[i].gaps();
178  if (zero_inactive) {
179  for (auto dof : interactions_[i].inactiveDofs()) {
180  g[dof] = 0.0;
181  }
182  }
183  mfem::Vector g_interaction(
184  merged_g, pressure_dof_offsets_[static_cast<int>(i)],
185  pressure_dof_offsets_[static_cast<int>(i) + 1] - pressure_dof_offsets_[static_cast<int>(i)]);
186  g_interaction.Set(1.0, g);
187  }
188  }
189  return merged_g;
190 }
191 
192 std::unique_ptr<mfem::BlockOperator> ContactData::mergedJacobian() const
193 {
194  updateDofOffsets();
195  // this is the BlockOperator we are returning with the following blocks:
196  // | df_(contact)/dx df_(contact)/dp |
197  // | dg/dx I_(inactive) |
198  // where I_(inactive) is a matrix with ones on the diagonal of inactive pressure true degrees of freedom
199  auto block_J = std::make_unique<mfem::BlockOperator>(jacobian_offsets_);
200  block_J->owns_blocks = true;
201  // rather than returning different blocks for each contact interaction with Lagrange multipliers, merge them all into
202  // a single block
203  mfem::Array2D<const mfem::HypreParMatrix*> dgdu_blocks(static_cast<int>(interactions_.size()), 1);
204  mfem::Array2D<const mfem::HypreParMatrix*> dfdp_blocks(1, static_cast<int>(interactions_.size()));
205  for (size_t i{0}; i < interactions_.size(); ++i) {
206  dgdu_blocks(static_cast<int>(i), 0) = nullptr;
207  dfdp_blocks(0, static_cast<int>(i)) = nullptr;
208  }
209 
210  for (size_t i{0}; i < interactions_.size(); ++i) {
211  // this is the BlockOperator for one of the contact interactions, post-processed for Smith's assembly conventions
212  auto interaction_J = interactions_[i].jacobianContribution();
213  interaction_J->owns_blocks = false; // we'll manage the ownership of the blocks on our own...
214 
215  // add the contact interaction's contribution to df_(contact)/dx (the 0, 0 block)
216  if (!interaction_J->IsZeroBlock(0, 0)) {
217  SLIC_ERROR_ROOT_IF(!dynamic_cast<mfem::HypreParMatrix*>(&interaction_J->GetBlock(0, 0)),
218  "Only HypreParMatrix constraint matrix blocks are currently supported.");
219  if (block_J->IsZeroBlock(0, 0)) {
220  block_J->SetBlock(0, 0, &interaction_J->GetBlock(0, 0));
221  } else {
222  block_J->SetBlock(0, 0,
223  mfem::Add(1.0, static_cast<mfem::HypreParMatrix&>(block_J->GetBlock(0, 0)), 1.0,
224  static_cast<mfem::HypreParMatrix&>(interaction_J->GetBlock(0, 0))));
225  delete &interaction_J->GetBlock(0, 0);
226  }
227  }
228 
229  // add the contact interaction's contribution to df_(contact)/dp and dg/dx (for Lagrange multipliers)
230  if (!interaction_J->IsZeroBlock(1, 0) && !interaction_J->IsZeroBlock(0, 1)) {
231  auto dgdu = dynamic_cast<mfem::HypreParMatrix*>(&interaction_J->GetBlock(1, 0));
232  auto dfdp = dynamic_cast<mfem::HypreParMatrix*>(&interaction_J->GetBlock(0, 1));
233  SLIC_ERROR_ROOT_IF(!dgdu, "Only HypreParMatrix constraint matrix blocks are currently supported.");
234  SLIC_ERROR_ROOT_IF(!dfdp, "Only HypreParMatrix constraint matrix blocks are currently supported.");
235 
236  dgdu_blocks(static_cast<int>(i), 0) = dgdu;
237  dfdp_blocks(0, static_cast<int>(i)) = dfdp;
238  }
239  }
240  if (haveLagrangeMultipliers()) {
241  // merge all of the contributions from all of the contact interactions
242  block_J->SetBlock(1, 0, mfem::HypreParMatrixFromBlocks(dgdu_blocks));
243  // store the transpose explicitly (rather than as a TransposeOperator) for solvers that need HypreParMatrixs
244  block_J->SetBlock(0, 1, mfem::HypreParMatrixFromBlocks(dfdp_blocks));
245  // explicitly delete the blocks
246  for (size_t i{0}; i < interactions_.size(); ++i) {
247  delete dgdu_blocks(static_cast<int>(i), 0);
248  delete dfdp_blocks(0, static_cast<int>(i));
249  }
250  // build I_(inactive): a diagonal matrix with ones on inactive dofs and zeros elsewhere
251  mfem::Array<const mfem::Array<int>*> inactive_tdofs_vector(static_cast<int>(interactions_.size()));
252  int inactive_tdofs_ct = 0;
253  for (int i{0}; i < inactive_tdofs_vector.Size(); ++i) {
254  inactive_tdofs_vector[i] = &interactions_[static_cast<size_t>(i)].inactiveDofs();
255  inactive_tdofs_ct += inactive_tdofs_vector[i]->Size();
256  }
257  mfem::Array<int> inactive_tdofs(inactive_tdofs_ct);
258  inactive_tdofs_ct = 0;
259  for (int i{0}; i < inactive_tdofs_vector.Size(); ++i) {
260  if (inactive_tdofs_vector[i]) {
261  for (int d{0}; d < inactive_tdofs_vector[i]->Size(); ++d) {
262  inactive_tdofs[d + inactive_tdofs_ct] = (*inactive_tdofs_vector[i])[d] + pressure_dof_offsets_[i];
263  }
264  inactive_tdofs_ct += inactive_tdofs_vector[i]->Size();
265  }
266  }
267  inactive_tdofs.GetMemory().SetHostPtrOwner(false);
268  mfem::Array<int> rows(numPressureDofs() + 1);
269  rows = 0;
270  inactive_tdofs_ct = 0;
271  for (int i{0}; i < numPressureDofs(); ++i) {
272  if (inactive_tdofs_ct < inactive_tdofs.Size() && inactive_tdofs[inactive_tdofs_ct] == i) {
273  ++inactive_tdofs_ct;
274  }
275  rows[i + 1] = inactive_tdofs_ct;
276  }
277  rows.GetMemory().SetHostPtrOwner(false);
278  mfem::Vector ones(inactive_tdofs_ct);
279  ones = 1.0;
280  ones.GetMemory().SetHostPtrOwner(false);
281  mfem::SparseMatrix inactive_diag(rows.GetData(), inactive_tdofs.GetData(), ones.GetData(), numPressureDofs(),
282  numPressureDofs(), false, false, true);
283  // if the size of ones is zero, SparseMatrix creates its own memory which it
284  // owns. explicitly prevent this...
285  inactive_diag.SetDataOwner(false);
286  auto block_1_1 =
287  new mfem::HypreParMatrix(mesh_.GetComm(), global_pressure_dof_offsets_[global_pressure_dof_offsets_.Size() - 1],
288  global_pressure_dof_offsets_, &inactive_diag);
289  block_1_1->SetOwnerFlags(3, 3, 1);
290  block_J->SetBlock(1, 1, block_1_1);
291  // end building I_(inactive)
292  }
293  return block_J;
294 }
295 
296 void ContactData::residualFunction(const mfem::Vector& u_shape, const mfem::Vector& u, mfem::Vector& r)
297 {
298  const int disp_size = reference_nodes_->ParFESpace()->GetTrueVSize();
299 
300  // u_const should not change in this method; const cast is to create vector views which are copied to Tribol
301  // displacements and pressures and used to compute the (non-contact) residual
302  auto& u_const = const_cast<mfem::Vector&>(u);
303  const mfem::Vector u_blk(u_const, 0, disp_size);
304  const mfem::Vector p_blk(u_const, disp_size, numPressureDofs());
305 
306  mfem::Vector r_blk(r, 0, disp_size);
307  mfem::Vector g_blk(r, disp_size, numPressureDofs());
308 
309  updateForcesAndJacobian(cycle_, time_, dt_, u_shape, u_blk, p_blk);
310 
311  r_blk += forces();
312  // calling mergedGaps() with true will zero out gap on inactive dofs (so the residual converges and the linearized
313  // system makes sense)
314  g_blk.Set(1.0, mergedGaps(true));
315 }
316 
317 std::unique_ptr<mfem::BlockOperator> ContactData::jacobianFunction(std::unique_ptr<mfem::HypreParMatrix> orig_J) const
318 {
319  auto J_contact = mergedJacobian();
320  if (J_contact->IsZeroBlock(0, 0)) {
321  J_contact->SetBlock(0, 0, orig_J.release());
322  } else {
323  J_contact->SetBlock(0, 0,
324  mfem::Add(1.0, *orig_J, 1.0, static_cast<mfem::HypreParMatrix&>(J_contact->GetBlock(0, 0))));
325  }
326 
327  return J_contact;
328 }
329 
330 void ContactData::setPressures(const mfem::Vector& merged_pressures) const
331 {
332  updateDofOffsets();
333  for (size_t i{0}; i < interactions_.size(); ++i) {
334  FiniteElementState p_interaction(interactions_[i].pressureSpace());
335  if (interactions_[i].getContactOptions().enforcement == ContactEnforcement::LagrangeMultiplier) {
336  // merged_pressures_const should not change; const cast is to create a vector view for copying to tribol pressures
337  auto& merged_pressures_const = const_cast<mfem::Vector&>(merged_pressures);
338  const mfem::Vector p_interaction_ref(
339  merged_pressures_const, pressure_dof_offsets_[static_cast<int>(i)],
340  pressure_dof_offsets_[static_cast<int>(i) + 1] - pressure_dof_offsets_[static_cast<int>(i)]);
341  p_interaction.Set(1.0, p_interaction_ref);
342  } else // enforcement == ContactEnforcement::Penalty
343  {
344  p_interaction.Set(interactions_[i].getContactOptions().penalty, interactions_[i].gaps());
345  }
346  for (auto dof : interactions_[i].inactiveDofs()) {
347  p_interaction[dof] = 0.0;
348  }
349  interactions_[i].setPressure(p_interaction);
350  }
351 }
352 
353 void ContactData::setDisplacements(const mfem::Vector& shape_u, const mfem::Vector& u)
354 {
355  mfem::ParGridFunction prolonged_shape_disp{current_coords_};
356  reference_nodes_->ParFESpace()->GetProlongationMatrix()->Mult(u, current_coords_);
357  reference_nodes_->ParFESpace()->GetProlongationMatrix()->Mult(shape_u, prolonged_shape_disp);
358 
359  current_coords_ += *reference_nodes_;
360  current_coords_ += prolonged_shape_disp;
361 }
362 
363 void ContactData::updateDofOffsets() const
364 {
365  if (offsets_up_to_date_) {
366  return;
367  }
368  jacobian_offsets_ = mfem::Array<int>({0, reference_nodes_->ParFESpace()->GetTrueVSize(),
369  numPressureDofs() + reference_nodes_->ParFESpace()->GetTrueVSize()});
370  pressure_dof_offsets_.SetSize(static_cast<int>(interactions_.size()) + 1);
371  pressure_dof_offsets_ = 0;
372  for (size_t i{0}; i < interactions_.size(); ++i) {
373  pressure_dof_offsets_[static_cast<int>(i + 1)] =
374  pressure_dof_offsets_[static_cast<int>(i)] + interactions_[i].numPressureDofs();
375  }
376  global_pressure_dof_offsets_.SetSize(mesh_.GetNRanks() + 1);
377  global_pressure_dof_offsets_ = 0;
378  global_pressure_dof_offsets_[mesh_.GetMyRank() + 1] = numPressureDofs();
379  MPI_Allreduce(MPI_IN_PLACE, global_pressure_dof_offsets_.GetData(), global_pressure_dof_offsets_.Size(), MPI_INT,
380  MPI_SUM, mesh_.GetComm());
381  for (int i{1}; i < mesh_.GetNRanks(); ++i) {
382  global_pressure_dof_offsets_[i + 1] += global_pressure_dof_offsets_[i];
383  }
384  if (HYPRE_AssumedPartitionCheck()) {
385  auto total_dofs = global_pressure_dof_offsets_[global_pressure_dof_offsets_.Size() - 1];
386  // If the number of ranks is less than 2, ensure the size of global_pressure_dof_offsets_ is large enough
387  if (mesh_.GetNRanks() < 2) {
388  global_pressure_dof_offsets_.SetSize(3);
389  }
390  global_pressure_dof_offsets_[0] = global_pressure_dof_offsets_[mesh_.GetMyRank()];
391  global_pressure_dof_offsets_[1] = global_pressure_dof_offsets_[mesh_.GetMyRank() + 1];
392  global_pressure_dof_offsets_[2] = total_dofs;
393  // If the number of ranks is greater than 2, shrink the size of global_pressure_dof_offsets_
394  if (mesh_.GetNRanks() > 2) {
395  global_pressure_dof_offsets_.SetSize(3);
396  }
397  }
398  offsets_up_to_date_ = true;
399 }
400 
401 std::unique_ptr<mfem::HypreParMatrix> ContactData::contactSubspaceTransferOperator()
402 {
403  const MPI_Comm comm = reference_nodes_->ParFESpace()->GetComm();
404  HYPRE_BigInt* col_offsets = reference_nodes_->ParFESpace()->GetTrueDofOffsets();
405  HYPRE_BigInt ncols_glb = reference_nodes_->ParFESpace()->GlobalTrueVSize();
406 
407  // number of rows of the restriction
408  // operator owned by the local MPI process
409  int nrows_loc = contact_dofs_.Size();
410  // should nrows_glb be of type HYPRE_BigInt?
411  // global number of rows of restriction
412  // operator
413  int nrows_glb = 0;
414  MPI_Allreduce(&nrows_loc, &nrows_glb, 1, MPI_INT, MPI_SUM, comm);
415  // determine rows offsets of the restriction operator
416  int row_offset = 0;
417  MPI_Scan(&nrows_loc, &row_offset, 1, MPI_INT, MPI_SUM, comm);
418  row_offset -= nrows_loc;
419  HYPRE_BigInt row_offsets[2];
420  row_offsets[0] = row_offset;
421  row_offsets[1] = row_offset + nrows_loc;
422 
423  // create mfem::SparseMatrix restriction matrix
424  // restriction from displacement dofs to
425  // contact dofs
426  // one nonzero (unit) entry per row
427  mfem::SparseMatrix Rsparse(nrows_loc, ncols_glb);
428  mfem::Array<int> col(1);
429  col = 0;
430  mfem::Vector entry(1);
431  entry = 1.0;
432  HYPRE_BigInt col_offset = col_offsets[0];
433  for (int k = 0; k < nrows_loc; k++) {
434  // local per process contact dof
435  // to global column number
436  col[0] = col_offset + contact_dofs_[k];
437  Rsparse.SetRow(k, col, entry);
438  }
439  Rsparse.Finalize();
440 
441  // convert local sparse restriction matrix
442  // to distributed mfem::HypreParMatrix
443  int* I = Rsparse.GetI();
444  HYPRE_BigInt* J = Rsparse.GetJ();
445  double* data = Rsparse.GetData();
446 
447  std::unique_ptr<mfem::HypreParMatrix> restriction_operator = std::make_unique<mfem::HypreParMatrix>(
448  comm, nrows_loc, nrows_glb, ncols_glb, I, J, data, row_offsets, col_offsets);
449  // convert restriction operator
450  // to a contact dof to displacement
451  // dof transfer operator
452  std::unique_ptr<mfem::HypreParMatrix> transfer_operator;
453  transfer_operator.reset(restriction_operator->Transpose());
454  return transfer_operator;
455 }
456 
457 #else
458 
459 ContactData::ContactData([[maybe_unused]] const mfem::ParMesh& mesh)
460  : have_lagrange_multipliers_{false}, num_pressure_dofs_{0}
461 {
462 }
463 
465 
466 void ContactData::addContactInteraction([[maybe_unused]] int interaction_id,
467  [[maybe_unused]] const std::set<int>& bdry_attr_surf1,
468  [[maybe_unused]] const std::set<int>& bdry_attr_surf2,
469  [[maybe_unused]] ContactOptions contact_opts)
470 {
471  SLIC_WARNING_ROOT("Smith built without Tribol support. No contact interaction will be added.");
472 }
473 
474 void ContactData::updateGeometry([[maybe_unused]] int cycle, [[maybe_unused]] double time, [[maybe_unused]] double& dt,
475  [[maybe_unused]] std::optional<std::reference_wrapper<const mfem::Vector>> u_shape,
476  [[maybe_unused]] std::optional<std::reference_wrapper<const mfem::Vector>> u,
477  [[maybe_unused]] bool eval_jacobian)
478 {
479 }
480 
482  [[maybe_unused]] int cycle, [[maybe_unused]] double time, [[maybe_unused]] double& dt,
483  [[maybe_unused]] std::optional<std::reference_wrapper<const mfem::Vector>> u_shape,
484  [[maybe_unused]] std::optional<std::reference_wrapper<const mfem::Vector>> u,
485  [[maybe_unused]] std::optional<std::reference_wrapper<const mfem::Vector>> p)
486 {
487 }
488 
490 {
491  FiniteElementDual f(*reference_nodes_->ParFESpace(), "contact force");
492  f = 0.0;
493  return f;
494 }
495 
496 mfem::HypreParVector ContactData::mergedPressures() const { return mfem::HypreParVector(); }
497 
498 mfem::HypreParVector ContactData::mergedGaps([[maybe_unused]] bool zero_inactive) const
499 {
500  return mfem::HypreParVector();
501 }
502 
503 std::unique_ptr<mfem::BlockOperator> ContactData::mergedJacobian() const
504 {
505  jacobian_offsets_ = mfem::Array<int>(
506  {0, reference_nodes_->ParFESpace()->GetTrueVSize(), reference_nodes_->ParFESpace()->GetTrueVSize()});
507  return std::make_unique<mfem::BlockOperator>(jacobian_offsets_);
508 }
509 
510 void ContactData::residualFunction([[maybe_unused]] const mfem::Vector& u_shape, [[maybe_unused]] const mfem::Vector& u,
511  [[maybe_unused]] mfem::Vector& r)
512 {
513 }
514 
515 std::unique_ptr<mfem::BlockOperator> ContactData::jacobianFunction(std::unique_ptr<mfem::HypreParMatrix> orig_J) const
516 {
517  auto J_contact = mergedJacobian();
518  if (J_contact->IsZeroBlock(0, 0)) {
519  J_contact->SetBlock(0, 0, orig_J.release());
520  } else {
521  J_contact->SetBlock(0, 0,
522  mfem::Add(1.0, *orig_J, 1.0, static_cast<mfem::HypreParMatrix&>(J_contact->GetBlock(0, 0))));
523  }
524 
525  return J_contact;
526 }
527 
528 void ContactData::setPressures([[maybe_unused]] const mfem::Vector& true_pressures) const {}
529 
530 void ContactData::setDisplacements([[maybe_unused]] const mfem::Vector& u_shape,
531  [[maybe_unused]] const mfem::Vector& true_displacement)
532 {
533 }
534 
535 std::unique_ptr<mfem::HypreParMatrix> ContactData::contactSubspaceTransferOperator()
536 {
537  std::unique_ptr<mfem::HypreParMatrix> transfer_operator = nullptr;
538  return transfer_operator;
539 }
540 
541 #endif
542 
543 } // namespace smith
void residualFunction(const mfem::Vector &u_shape, const mfem::Vector &u, mfem::Vector &r)
Computes the residual including contact terms.
mfem::HypreParVector mergedGaps(bool zero_inactive=false) const
Returns nodal gaps from all contact interactions on the contact surface true degrees of freedom.
std::unique_ptr< mfem::BlockOperator > jacobianFunction(std::unique_ptr< mfem::HypreParMatrix > orig_J) const
Computes the Jacobian including contact terms, given the non-contact Jacobian terms.
ContactData(const mfem::ParMesh &mesh)
The constructor.
void setPressures(const mfem::Vector &merged_pressures) const
Set the pressure field.
~ContactData()
Destructor to finalize Tribol.
void updateForcesAndJacobian(int cycle, double time, double &dt, std::optional< std::reference_wrapper< const mfem::Vector >> u_shape=std::nullopt, std::optional< std::reference_wrapper< const mfem::Vector >> u=std::nullopt, std::optional< std::reference_wrapper< const mfem::Vector >> p=std::nullopt)
Updates the contact forces and Jacobian contributions for the current configuration.
mfem::HypreParVector mergedPressures() const
Returns pressures from all contact interactions on the contact surface true degrees of freedom.
FiniteElementDual forces() const
Get the contact constraint residual (i.e. nodal forces) from all contact interactions.
std::unique_ptr< mfem::BlockOperator > mergedJacobian() const
Returns a 2x2 block Jacobian on displacement/pressure true degrees of freedom from contact constraint...
void updateGeometry(int cycle, double time, double &dt, std::optional< std::reference_wrapper< const mfem::Vector >> u_shape=std::nullopt, std::optional< std::reference_wrapper< const mfem::Vector >> u=std::nullopt, bool eval_jacobian=false)
Updates the contact geometry and gap data.
void addContactInteraction(int interaction_id, const std::set< int > &bdry_attr_surf1, const std::set< int > &bdry_attr_surf2, ContactOptions contact_opts)
Add another contact interaction.
void setDisplacements(const mfem::Vector &u_shape, const mfem::Vector &u)
Update the current coordinates based on the new displacement field.
std::unique_ptr< mfem::HypreParMatrix > contactSubspaceTransferOperator()
Computes the subspace transfer operator.
Class for encapsulating the dual vector space of a finite element space (i.e. the space of linear for...
Class for storing contact data.
This file contains the declaration of structure that manages the MFEM objects that make up the state ...
Accelerator functionality.
Definition: smith.cpp:36
Stores the options for a contact pair.