Skip to content

Commit 8716455

Browse files
committed
API for consistent constraints via residuals
This is a relatively clean way to handle drift away from constraints at an elemental rather than a global level. The distinction between how we want to do heterogeneous constraints in linear vs in nonlinear systems gets a little tricky.
1 parent e011f96 commit 8716455

5 files changed

Lines changed: 302 additions & 26 deletions

File tree

include/base/dof_map.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,10 @@ class DofMap : public ReferenceCountedObject<DofMap>,
12491249
* appropriate for finding linearized updates to a solution in which
12501250
* heterogeneous constraints are already satisfied.
12511251
*
1252+
* Note the sign difference from the nonlinear heterogeneous constraint
1253+
* method: Solving u:=K\f has the opposite sign convention from
1254+
* u:=u_in-J\r, and we apply heterogeneous constraints accordingly.
1255+
*
12521256
* By default, the constraints for the primal solution of this
12531257
* system are used. If a non-negative \p qoi_index is passed in,
12541258
* then the constraints for the corresponding adjoint solution are
@@ -1272,7 +1276,67 @@ class DofMap : public ReferenceCountedObject<DofMap>,
12721276
(matrix, rhs, elem_dofs, asymmetric_constraint_rows, qoi_index);
12731277
}
12741278

1279+
/**
1280+
* Constrains the element Jacobian and residual. The element
1281+
* Jacobian is square, and the elem_dofs should correspond to the
1282+
* global DOF indices of both the rows and columns of the element
1283+
* matrix.
1284+
*
1285+
* The residual-constraining version of this method creates linear
1286+
* systems in which heterogeneously constrained degrees of freedom
1287+
* create non-zero residual terms when not at their correct offset
1288+
* values, as would be appropriate for finding a solution to a
1289+
* nonlinear problem in a quasi-Newton solve.
1290+
*
1291+
* Note the sign difference from the linear heterogeneous constraint
1292+
* method: Solving u:=u_in-J\r has the opposite sign convention from
1293+
* u:=K\f, and we apply heterogeneous constraints accordingly.
1294+
*
1295+
* The \p solution vector passed in should be a serialized or
1296+
* ghosted primal solution
1297+
*/
1298+
void heterogeneously_constrain_element_jacobian_and_residual (DenseMatrix<Number> & matrix,
1299+
DenseVector<Number> & rhs,
1300+
std::vector<dof_id_type> & elem_dofs,
1301+
NumericVector<Number> & solution_local) const;
1302+
1303+
/**
1304+
* Constrains the element residual. The element Jacobian is square,
1305+
* and the elem_dofs should correspond to the global DOF indices of
1306+
* both the rows and columns of the element matrix.
1307+
*
1308+
* The residual-constraining version of this method creates linear
1309+
* systems in which heterogeneously constrained degrees of freedom
1310+
* create non-zero residual terms when not at their correct offset
1311+
* values, as would be appropriate for finding a solution to a
1312+
* nonlinear problem in a quasi-Newton solve.
1313+
*
1314+
* The \p solution vector passed in should be a serialized or
1315+
* ghosted primal solution
1316+
*/
1317+
void heterogeneously_constrain_element_residual (DenseVector<Number> & rhs,
1318+
std::vector<dof_id_type> & elem_dofs,
1319+
NumericVector<Number> & solution_local) const;
1320+
12751321

1322+
/**
1323+
* Constrains the element residual. The element Jacobian is square,
1324+
* and the elem_dofs should correspond to the global DOF indices of
1325+
* both the rows and columns of the element matrix, and the dof
1326+
* constraint should not include any heterogeneous terms.
1327+
*
1328+
* The residual-constraining version of this method creates linear
1329+
* systems in which heterogeneously constrained degrees of freedom
1330+
* create non-zero residual terms when not at their correct offset
1331+
* values, as would be appropriate for finding a solution to a
1332+
* nonlinear problem in a quasi-Newton solve.
1333+
*
1334+
* The \p solution vector passed in should be a serialized or
1335+
* ghosted primal solution
1336+
*/
1337+
void constrain_element_residual (DenseVector<Number> & rhs,
1338+
std::vector<dof_id_type> & elem_dofs,
1339+
NumericVector<Number> & solution_local) const;
12761340

12771341
/**
12781342
* Constrains a dyadic element matrix B = v w'. This method

src/base/dof_map_constraints.C

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2622,6 +2622,207 @@ void DofMap::heterogeneously_constrain_element_matrix_and_vector (DenseMatrix<Nu
26222622
}
26232623

26242624

2625+
void DofMap::heterogeneously_constrain_element_jacobian_and_residual
2626+
(DenseMatrix<Number> & matrix,
2627+
DenseVector<Number> & rhs,
2628+
std::vector<dof_id_type> & elem_dofs,
2629+
NumericVector<Number> & solution_local) const
2630+
{
2631+
libmesh_assert_equal_to (elem_dofs.size(), matrix.m());
2632+
libmesh_assert_equal_to (elem_dofs.size(), matrix.n());
2633+
libmesh_assert_equal_to (elem_dofs.size(), rhs.size());
2634+
2635+
libmesh_assert (solution_local.type() == SERIAL ||
2636+
solution_local.type() == GHOSTED);
2637+
2638+
// check for easy return
2639+
if (this->_dof_constraints.empty())
2640+
return;
2641+
2642+
// The constrained matrix is built up as C^T K C.
2643+
// The constrained RHS is built up as C^T F
2644+
// Asymmetric residual terms are added if we do not have x = Cx+h
2645+
DenseMatrix<Number> C;
2646+
DenseVector<Number> H;
2647+
2648+
this->build_constraint_matrix_and_vector (C, H, elem_dofs);
2649+
2650+
LOG_SCOPE("hetero_cnstrn_elem_jac_res()", "DofMap");
2651+
2652+
// It is possible that the matrix is not constrained at all.
2653+
if ((C.m() != matrix.m()) ||
2654+
(C.n() != elem_dofs.size()))
2655+
return;
2656+
2657+
// Compute the matrix-vector product C^T F
2658+
DenseVector<Number> old_rhs(rhs);
2659+
C.vector_mult_transpose(rhs, old_rhs);
2660+
2661+
// Compute the matrix-matrix-matrix product C^T K C
2662+
matrix.left_multiply_transpose (C);
2663+
matrix.right_multiply (C);
2664+
2665+
libmesh_assert_equal_to (matrix.m(), matrix.n());
2666+
libmesh_assert_equal_to (matrix.m(), elem_dofs.size());
2667+
libmesh_assert_equal_to (matrix.n(), elem_dofs.size());
2668+
2669+
for (unsigned int i=0,
2670+
n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
2671+
i != n_elem_dofs; i++)
2672+
{
2673+
const dof_id_type dof_id = elem_dofs[i];
2674+
2675+
const DofConstraints::const_iterator
2676+
pos = _dof_constraints.find(dof_id);
2677+
2678+
if (pos != _dof_constraints.end())
2679+
{
2680+
for (auto j : make_range(matrix.n()))
2681+
matrix(i,j) = 0.;
2682+
2683+
// If the DOF is constrained
2684+
matrix(i,i) = 1.;
2685+
2686+
// This will put a nonsymmetric entry in the constraint
2687+
// row to ensure that the linear system produces the
2688+
// correct value for the constrained DOF.
2689+
const DofConstraintRow & constraint_row = pos->second;
2690+
2691+
for (const auto & item : constraint_row)
2692+
for (unsigned int j=0; j != n_elem_dofs; j++)
2693+
if (elem_dofs[j] == item.first)
2694+
matrix(i,j) = -item.second;
2695+
2696+
const DofConstraintValueMap::const_iterator valpos =
2697+
_primal_constraint_values.find(dof_id);
2698+
2699+
Number & rhs_val = rhs(i);
2700+
rhs_val = (valpos == _primal_constraint_values.end()) ?
2701+
0 : -valpos->second;
2702+
for (const auto & [constraining_dof, coef] : constraint_row)
2703+
rhs_val -= coef * solution_local(constraining_dof);
2704+
rhs_val += solution_local(dof_id);
2705+
}
2706+
}
2707+
}
2708+
2709+
2710+
void DofMap::heterogeneously_constrain_element_residual
2711+
(DenseVector<Number> & rhs,
2712+
std::vector<dof_id_type> & elem_dofs,
2713+
NumericVector<Number> & solution_local) const
2714+
{
2715+
libmesh_assert_equal_to (elem_dofs.size(), rhs.size());
2716+
2717+
libmesh_assert (solution_local.type() == SERIAL ||
2718+
solution_local.type() == GHOSTED);
2719+
2720+
// check for easy return
2721+
if (this->_dof_constraints.empty())
2722+
return;
2723+
2724+
// The constrained RHS is built up as C^T F
2725+
// Asymmetric residual terms are added if we do not have x = Cx+h
2726+
DenseMatrix<Number> C;
2727+
DenseVector<Number> H;
2728+
2729+
this->build_constraint_matrix_and_vector (C, H, elem_dofs);
2730+
2731+
LOG_SCOPE("hetero_cnstrn_elem_res()", "DofMap");
2732+
2733+
// It is possible that the element is not constrained at all.
2734+
if ((C.m() != rhs.size()) ||
2735+
(C.n() != elem_dofs.size()))
2736+
return;
2737+
2738+
// Compute the matrix-vector product C^T F
2739+
DenseVector<Number> old_rhs(rhs);
2740+
C.vector_mult_transpose(rhs, old_rhs);
2741+
2742+
for (unsigned int i=0,
2743+
n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
2744+
i != n_elem_dofs; i++)
2745+
{
2746+
const dof_id_type dof_id = elem_dofs[i];
2747+
2748+
const DofConstraints::const_iterator
2749+
pos = _dof_constraints.find(dof_id);
2750+
2751+
if (pos != _dof_constraints.end())
2752+
{
2753+
// This will put a nonsymmetric entry in the constraint
2754+
// row to ensure that the linear system produces the
2755+
// correct value for the constrained DOF.
2756+
const DofConstraintRow & constraint_row = pos->second;
2757+
2758+
const DofConstraintValueMap::const_iterator valpos =
2759+
_primal_constraint_values.find(dof_id);
2760+
2761+
Number & rhs_val = rhs(i);
2762+
rhs_val = (valpos == _primal_constraint_values.end()) ?
2763+
0 : -valpos->second;
2764+
for (const auto & [constraining_dof, coef] : constraint_row)
2765+
rhs_val -= coef * solution_local(constraining_dof);
2766+
rhs_val += solution_local(dof_id);
2767+
}
2768+
}
2769+
}
2770+
2771+
2772+
void DofMap::constrain_element_residual
2773+
(DenseVector<Number> & rhs,
2774+
std::vector<dof_id_type> & elem_dofs,
2775+
NumericVector<Number> & solution_local) const
2776+
{
2777+
libmesh_assert_equal_to (elem_dofs.size(), rhs.size());
2778+
2779+
libmesh_assert (solution_local.type() == SERIAL ||
2780+
solution_local.type() == GHOSTED);
2781+
2782+
// check for easy return
2783+
if (this->_dof_constraints.empty())
2784+
return;
2785+
2786+
// The constrained RHS is built up as C^T F
2787+
DenseMatrix<Number> C;
2788+
2789+
this->build_constraint_matrix (C, elem_dofs);
2790+
2791+
LOG_SCOPE("cnstrn_elem_residual()", "DofMap");
2792+
2793+
// It is possible that the matrix is not constrained at all.
2794+
if (C.n() != elem_dofs.size())
2795+
return;
2796+
2797+
// Compute the matrix-vector product C^T F
2798+
DenseVector<Number> old_rhs(rhs);
2799+
C.vector_mult_transpose(rhs, old_rhs);
2800+
2801+
for (unsigned int i=0,
2802+
n_elem_dofs = cast_int<unsigned int>(elem_dofs.size());
2803+
i != n_elem_dofs; i++)
2804+
{
2805+
const dof_id_type dof_id = elem_dofs[i];
2806+
2807+
const DofConstraints::const_iterator
2808+
pos = _dof_constraints.find(dof_id);
2809+
2810+
if (pos != _dof_constraints.end())
2811+
{
2812+
// This will put a nonsymmetric entry in the constraint
2813+
// row to ensure that the linear system produces the
2814+
// correct value for the constrained DOF.
2815+
const DofConstraintRow & constraint_row = pos->second;
2816+
2817+
Number & rhs_val = rhs(i);
2818+
rhs_val = 0;
2819+
for (const auto & [constraining_dof, coef] : constraint_row)
2820+
rhs_val -= coef * solution_local(constraining_dof);
2821+
rhs_val += solution_local(dof_id);
2822+
}
2823+
}
2824+
}
2825+
26252826

26262827
void DofMap::heterogeneously_constrain_element_vector (const DenseMatrix<Number> & matrix,
26272828
DenseVector<Number> & rhs,

src/solvers/newton_solver.C

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Real NewtonSolver::line_search(Real tol,
8383
_system.update();
8484

8585
// Check residual with fractional Newton step
86-
_system.assembly (true, false);
86+
_system.assembly(true, false, !this->_exact_constraint_enforcement);
8787

8888
rhs.close();
8989
current_residual = rhs.l2_norm();
@@ -191,7 +191,7 @@ Real NewtonSolver::line_search(Real tol,
191191

192192
// We may need to localize a parallel solution
193193
_system.update();
194-
_system.assembly (true, false);
194+
_system.assembly(true, false, !this->_exact_constraint_enforcement);
195195

196196
rhs.close();
197197
Real fu = current_residual = rhs.l2_norm();
@@ -317,7 +317,7 @@ unsigned int NewtonSolver::solve()
317317
if (verbose)
318318
libMesh::out << "Assembling the System" << std::endl;
319319

320-
_system.assembly(true, true);
320+
_system.assembly(true, true, !this->_exact_constraint_enforcement);
321321
rhs.close();
322322
Real current_residual = rhs.l2_norm();
323323

@@ -472,7 +472,7 @@ unsigned int NewtonSolver::solve()
472472
!continue_after_max_iterations)
473473
{
474474
_system.update ();
475-
_system.assembly(true, false);
475+
_system.assembly(true, false, !this->_exact_constraint_enforcement);
476476

477477
rhs.close();
478478
current_residual = rhs.l2_norm();

src/solvers/petsc_diff_solver.C

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ extern "C"
119119
sys.get_dof_map().enforce_constraints_exactly(sys, sys.current_local_solution.get());
120120

121121
// Do DiffSystem assembly
122-
sys.assembly(true, false);
122+
sys.assembly(true, false, !solver.exact_constraint_enforcement());
123123
R_system.close();
124124

125125
// Swap back
@@ -173,7 +173,7 @@ extern "C"
173173
sys.get_dof_map().enforce_constraints_exactly(sys, sys.current_local_solution.get());
174174

175175
// Do DiffSystem assembly
176-
sys.assembly(false, true);
176+
sys.assembly(false, true, !solver.exact_constraint_enforcement());
177177
J_system.close();
178178

179179
// Swap back

0 commit comments

Comments
 (0)