v0.16.3
Loading...
Searching...
No Matches
tao_null_space.cpp
Go to the documentation of this file.
1/** \file tao_null_space.cpp
2 * \brief Test null-space TAO optimization of a scaled epigraph constraint.
3 */
4
5#include <MoFEM.hpp>
6#include <limits>
7
8using namespace MoFEM;
9
10static char help[] =
11 "Test the null-space TAO solver on an epigraph problem.\n\n";
12
13namespace {
14
15template <size_t N>
16MoFEMErrorCode readValues(Vec vec, std::array<PetscScalar, N> &values) {
18 values.fill(0);
19 PetscInt first, last;
20 const PetscScalar *data;
21 CHKERR VecGetOwnershipRange(vec, &first, &last);
22 CHKERR VecGetArrayRead(vec, &data);
23 for (auto i = first; i != last; ++i)
24 values[i] = data[i - first];
25 CHKERR VecRestoreArrayRead(vec, &data);
26 CHKERR MPI_Allreduce(MPI_IN_PLACE, values.data(), N, MPIU_SCALAR, MPI_SUM,
27 PetscObjectComm(getPetscObject(vec)));
29}
30
31template <size_t N>
32MoFEMErrorCode writeValues(Vec vec, const std::array<PetscScalar, N> &values) {
34 PetscInt first, last;
35 PetscScalar *data;
36 CHKERR VecGetOwnershipRange(vec, &first, &last);
37 CHKERR VecGetArray(vec, &data);
38 for (auto i = first; i != last; ++i)
39 data[i - first] = values[i];
40 CHKERR VecRestoreArray(vec, &data);
42}
43
44struct Problem {
45 PetscReal scale;
46 PetscInt objectiveCalls = 0;
47 PetscInt gradientCalls = 0;
48 PetscInt constraintCalls = 0;
49 PetscInt jacobianCalls = 0;
50
51 MoFEMErrorCode evaluate(Vec x, PetscReal *value, Vec gradient) {
53 std::array<PetscScalar, 3> values;
54 CHKERR readValues(x, values);
55 const auto p1 = PetscRealPart(values[0]);
56 const auto p2 = PetscRealPart(values[1]);
57 if (value) {
58 ++objectiveCalls;
59 *value = 0.5 * ((p1 - 3) * (p1 - 3) + (p2 - 4) * (p2 - 4)) +
60 PetscRealPart(values[2]);
61 }
62 if (gradient) {
63 ++gradientCalls;
64 CHKERR writeValues(gradient,
65 std::array<PetscScalar, 3>{p1 - 3, p2 - 4, 1});
66 }
68 }
69
70 static PetscErrorCode constraints(Tao, Vec x, Vec c, void *ctx) {
72 auto *problem_ptr = static_cast<Problem *>(ctx);
73 ++problem_ptr->constraintCalls;
74 std::array<PetscScalar, 3> values;
75 CHKERR readValues(x, values);
76 const auto radius =
77 std::hypot(PetscRealPart(values[0]), PetscRealPart(values[1]));
78 CHKERR writeValues(
79 c, std::array<PetscScalar, 2>{problem_ptr->scale * (values[2] - radius),
80 6 - values[2]});
82 }
83
84 static PetscErrorCode jacobian(Tao, Vec x, Mat j, Mat j_pre, void *ctx) {
86 auto *problem_ptr = static_cast<Problem *>(ctx);
87 ++problem_ptr->jacobianCalls;
88 std::array<PetscScalar, 3> values;
89 CHKERR readValues(x, values);
90 const auto radius =
91 std::hypot(PetscRealPart(values[0]), PetscRealPart(values[1]));
92 const auto scale = problem_ptr->scale;
93 const PetscScalar rows[2][3] = {{radius ? -scale * values[0] / radius : 0,
94 radius ? -scale * values[1] / radius : 0,
95 scale},
96 {0, 0, -1}};
97 const PetscInt columns[] = {0, 1, 2};
98 for (auto matrix : {j, j_pre}) {
99 PetscInt first, last;
100 CHKERR MatGetOwnershipRange(matrix, &first, &last);
101 for (auto row = first; row != last; ++row)
102 CHKERR MatSetValues(matrix, 1, &row, 3, columns, rows[row],
103 INSERT_VALUES);
104 CHKERR MatAssemblyBegin(matrix, MAT_FINAL_ASSEMBLY);
105 CHKERR MatAssemblyEnd(matrix, MAT_FINAL_ASSEMBLY);
106 }
108 }
109};
110
111MoFEMErrorCode checkUnavailableSubproblem(Tao inner, Vec x, Vec gradient) {
113 PetscReal value;
114 CHKERR PetscPushErrorHandler(PetscReturnErrorHandler, nullptr);
115 const auto objective_error = TaoComputeObjective(inner, x, &value);
116 const auto gradient_error = TaoComputeGradient(inner, x, gradient);
117 CHKERR PetscPopErrorHandler();
118 // PETSc's compute routines leave the input read lock on error.
119 if (gradient_error)
120 CHKERR VecLockReadPop(x);
121 if (objective_error)
122 CHKERR VecLockReadPop(x);
123 if (objective_error != PETSC_ERR_ARG_WRONGSTATE ||
124 gradient_error != PETSC_ERR_ARG_WRONGSTATE)
125 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
126 "Inner callbacks require a live parent and a frozen outer iterate");
128}
129
130MoFEMErrorCode checkLineSearchRequests(Tao inner, Vec start, Problem &problem) {
132 auto trial = vectorDuplicate(start);
133 auto direction = vectorDuplicate(start);
134 auto gradient = vectorDuplicate(start);
135 auto expected_gradient = vectorDuplicate(start);
136 TaoLineSearch line_search;
137 CHKERR TaoGetLineSearch(inner, &line_search);
138 for (const auto type : {TAOLINESEARCHARMIJO, "nullspace"}) {
139 CHKERR TaoLineSearchSetType(line_search, type);
140 CHKERR TaoLineSearchReset(line_search);
141 CHKERR TaoLineSearchUseTaoRoutines(line_search, inner);
142 CHKERR TaoLineSearchSetInitialStepLength(line_search, 1);
143 CHKERR VecCopy(start, trial);
144 PetscReal value, step;
145 CHKERR TaoComputeObjectiveAndGradient(inner, trial, &value, gradient);
146 // Force backtracking so rejected values outnumber derivative evaluations.
147 CHKERR VecCopy(gradient, direction);
148 CHKERR VecScale(direction, -100);
149 const auto objective_calls = problem.objectiveCalls;
150 const auto gradient_calls = problem.gradientCalls;
151 const auto jacobian_calls = problem.jacobianCalls;
152 TaoLineSearchConvergedReason reason;
153 CHKERR TaoLineSearchApply(line_search, trial, &value, gradient, direction,
154 &step, &reason);
155 if (reason <= 0 || !(step < 1) ||
156 problem.objectiveCalls <= objective_calls + 1 ||
157 problem.gradientCalls != gradient_calls + 1 ||
158 problem.jacobianCalls != jacobian_calls + 1)
159 SETERRQ(PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
160 "%s backtracking must evaluate derivatives only at the accepted "
161 "trial: objective/gradient/Jacobian calls %d/%d/%d, step %g",
162 type, static_cast<int>(problem.objectiveCalls - objective_calls),
163 static_cast<int>(problem.gradientCalls - gradient_calls),
164 static_cast<int>(problem.jacobianCalls - jacobian_calls),
165 static_cast<double>(step));
166 PetscReal expected_value, gradient_error;
167 CHKERR TaoComputeObjectiveAndGradient(inner, trial, &expected_value,
168 expected_gradient);
169 CHKERR VecAXPY(expected_gradient, -1, gradient);
170 CHKERR VecNorm(expected_gradient, NORM_INFINITY, &gradient_error);
171 if (std::abs(value - expected_value) > 1e-12 || gradient_error > 1e-12)
172 SETERRQ(PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
173 "%s backtracking returned inconsistent value and gradient",
174 type);
175 }
177}
178
179MoFEMErrorCode checkFrozenMerit(Tao inner, Vec solution, Problem &problem,
180 const bool combined) {
182 auto trial = vectorDuplicate(solution);
183 auto direction = vectorDuplicate(solution);
184 auto gradient = vectorDuplicate(solution);
185 CHKERR VecCopy(solution, trial);
186 CHKERR writeValues(direction, std::array<PetscScalar, 3>{0.2, -0.3, 0.1});
187 CHKERR VecAXPY(trial, 0.01, direction);
188 const auto objective_calls = problem.objectiveCalls;
189 const auto gradient_calls = problem.gradientCalls;
190 const auto constraint_calls = problem.constraintCalls;
191 const auto jacobian_calls = problem.jacobianCalls;
192 PetscReal value;
193 CHKERR TaoComputeObjective(inner, trial, &value);
194 if (problem.objectiveCalls != objective_calls + 1 ||
195 problem.constraintCalls != constraint_calls + 1 ||
196 problem.jacobianCalls != jacobian_calls ||
197 (!combined && problem.gradientCalls != gradient_calls))
198 SETERRQ(PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
199 "Inner objective requested unnecessary derivatives");
200 CHKERR TaoComputeGradient(inner, trial, gradient);
201 if (problem.gradientCalls != gradient_calls + (combined ? 2 : 1) ||
202 problem.jacobianCalls != jacobian_calls + 1)
203 SETERRQ(PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
204 "Inner gradient did not evaluate the required derivatives");
205 auto combined_gradient = vectorDuplicate(gradient);
206 PetscReal combined_value, gradient_error;
207 CHKERR TaoComputeObjectiveAndGradient(inner, trial, &combined_value,
208 combined_gradient);
209 CHKERR VecAXPY(combined_gradient, -1, gradient);
210 CHKERR VecNorm(combined_gradient, NORM_INFINITY, &gradient_error);
211 if (std::abs(value - combined_value) > 1e-12 || gradient_error > 1e-12)
212 SETERRQ(PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
213 "Separate and combined frozen merit evaluations differ");
214 PetscScalar derivative;
215 CHKERR VecDot(gradient, direction, &derivative);
216 constexpr PetscReal epsilon = 1e-6;
217 PetscReal plus, minus;
218 CHKERR VecAXPY(trial, epsilon, direction);
219 CHKERR TaoComputeObjective(inner, trial, &plus);
220 CHKERR VecAXPY(trial, -2 * epsilon, direction);
221 CHKERR TaoComputeObjective(inner, trial, &minus);
222 if (std::abs((plus - minus) / (2 * epsilon) - PetscRealPart(derivative)) >
223 2e-6 * (1 + PetscAbsScalar(derivative)))
224 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
225 "Inner gradient is not the derivative of its frozen scalar merit");
226 if (!combined)
227 CHKERR checkLineSearchRequests(inner, trial, problem);
229}
230
231MoFEMErrorCode solveProblem(const std::array<PetscScalar, 3> &start,
232 const PetscReal scale, const bool combined) {
234 Problem problem{scale};
235 auto x = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 3);
236 auto g = vectorDuplicate(x);
237 auto c = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 2);
238 auto dm = createDM(PETSC_COMM_WORLD, DMSHELL);
239 CHKERR writeValues(x, start);
240 PetscInt local_rows, local_columns;
241 CHKERR VecGetLocalSize(c, &local_rows);
242 CHKERR VecGetLocalSize(x, &local_columns);
243 Mat matrix;
244 CHKERR MatCreateAIJ(PETSC_COMM_WORLD, local_rows, local_columns, 2, 3,
245 local_columns, nullptr, 3 - local_columns, nullptr,
246 &matrix);
248 CHKERR MatCreateAIJ(PETSC_COMM_WORLD, local_rows, local_columns, 2, 3,
249 local_columns, nullptr, 3 - local_columns, nullptr,
250 &matrix);
252 auto tao = createTao(PETSC_COMM_WORLD);
253 CHKERR TaoSetType(tao, combined ? TAOSHELL : TAOALMM);
254 CHKERR TaoSetOptionsPrefix(tao, "test_");
255 CHKERR TaoSetSolution(tao, x);
256 if (combined) {
257 CHKERR ::TaoSetObjectiveAndGradient(
258 tao, g,
259 [](Tao, Vec x, PetscReal *f, Vec g, void *ctx) {
260 return static_cast<Problem *>(ctx)->evaluate(x, f, g);
261 },
262 &problem);
263 } else {
264 CHKERR ::TaoSetObjective(
265 tao,
266 [](Tao, Vec x, PetscReal *f, void *ctx) {
267 return static_cast<Problem *>(ctx)->evaluate(x, f, nullptr);
268 },
269 &problem);
270 CHKERR ::TaoSetGradient(
271 tao, g,
272 [](Tao, Vec x, Vec g, void *ctx) {
273 return static_cast<Problem *>(ctx)->evaluate(x, nullptr, g);
274 },
275 &problem);
276 }
277 CHKERR TaoSetInequalityConstraintsRoutine(tao, c, Problem::constraints,
278 &problem);
279 CHKERR TaoSetJacobianInequalityRoutine(tao, jacobian, preconditioner,
280 Problem::jacobian, &problem);
281 if (combined)
282 CHKERR TaoSetType(tao, TAONULLSPACE);
283 else
284 CHKERR TaoSetFromOptions(tao);
286 CHKERR TaoSetMaximumIterations(tao, 300);
287 CHKERR TaoSetTolerances(tao, 1e-7, 0, 0);
288 CHKERR TaoSetConstraintTolerances(tao, 1e-7, 0);
289 CHKERR TaoSetFromOptions(tao);
290 PetscBool matches;
291 CHKERR PetscObjectTypeCompare(getPetscObject(tao.get()), TAONULLSPACE,
292 &matches);
293 if (!matches)
294 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY, "Incorrect TAO type");
295 Tao inner;
296 CHKERR TaoNullSpaceGetSubsolver(tao, &inner);
297 const char *prefix;
298 CHKERR TaoGetOptionsPrefix(inner, &prefix);
299 CHKERR PetscStrcmp(prefix, "test_tao_nullspace_", &matches);
300 if (!matches)
301 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
302 "Inner TAO did not inherit its options prefix");
303 CHKERR TaoSetUp(tao);
304 Vec sub_solution, sub_gradient, multipliers;
305 CHKERR TaoGetSolution(inner, &sub_solution);
306 CHKERR TaoGetGradient(inner, &sub_gradient, nullptr, nullptr);
308 CHKERR VecEqual(x, sub_solution, &matches);
309 if (!matches || sub_solution == x.get() || sub_gradient == g.get())
310 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
311 "Inner TAO needs an independent copy of the parent state");
312 CHKERR checkUnavailableSubproblem(inner, x, g);
313 CHKERR TaoSolve(tao);
314 TaoConvergedReason reason;
315 CHKERR TaoGetConvergedReason(tao, &reason);
316 if (reason <= 0)
317 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
318 "Epigraph solve did not converge (scale %g, reason %d)",
319 static_cast<double>(scale), static_cast<int>(reason));
320
321 PetscBool check_recycling = PETSC_FALSE;
322 CHKERR PetscOptionsHasName(nullptr, prefix, "-tao_lmvm_recycle",
323 &check_recycling);
324 if (check_recycling)
325 CHKERR TaoView(inner, PETSC_VIEWER_STDOUT_WORLD);
326
327 std::array<PetscScalar, 3> solution;
328 std::array<PetscScalar, 2> dual;
329 CHKERR readValues(x, solution);
330 CHKERR readValues(multipliers, dual);
331 constexpr PetscReal tolerance = 5e-6;
332 const PetscReal radius =
333 std::hypot(PetscRealPart(solution[0]), PetscRealPart(solution[1]));
334 if (PetscAbsScalar(solution[0] - 2.4) > tolerance ||
335 PetscAbsScalar(solution[1] - 3.2) > tolerance ||
336 PetscAbsScalar(solution[2] - 4) > tolerance ||
337 PetscAbsScalar(scale * dual[0] - 1) > tolerance ||
338 PetscAbsScalar(dual[1]) > tolerance ||
339 PetscRealPart(solution[2]) - radius < -tolerance ||
340 PetscRealPart(6 - solution[2]) < -tolerance)
341 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
342 "Incorrect epigraph optimum (scale %g): x=(%g,%g,%g), "
343 "scaled multipliers=(%g,%g)",
344 static_cast<double>(scale), static_cast<double>(solution[0]),
345 static_cast<double>(solution[1]), static_cast<double>(solution[2]),
346 static_cast<double>(scale * dual[0]), static_cast<double>(dual[1]));
347 PetscReal objective, stationarity;
348 CHKERR TaoComputeObjectiveAndGradient(tao, x, &objective, g);
349 CHKERR TaoComputeJacobianInequality(tao, x, jacobian, preconditioner);
350 auto residual = vectorDuplicate(x);
351 CHKERR MatMultTranspose(jacobian, multipliers, residual);
352 CHKERR VecAYPX(residual, -1, g);
353 CHKERR VecNorm(residual, NORM_INFINITY, &stationarity);
354 if (std::abs(objective - 4.5) > tolerance || stationarity > tolerance ||
355 !problem.objectiveCalls || !problem.gradientCalls ||
356 !problem.constraintCalls || !problem.jacobianCalls)
357 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
358 "Epigraph objective or constrained stationarity is incorrect");
359 CHKERR checkFrozenMerit(inner, x, problem, combined);
360 CHKERR PetscPrintf(
361 PETSC_COMM_WORLD,
362 "Epigraph scale %g start (%g,%g,%g): x=(%g,%g,%g), f=%g, KKT=%g\n",
363 static_cast<double>(scale), static_cast<double>(start[0]),
364 static_cast<double>(start[1]), static_cast<double>(start[2]),
365 static_cast<double>(solution[0]), static_cast<double>(solution[1]),
366 static_cast<double>(solution[2]), static_cast<double>(objective),
367 static_cast<double>(stationarity));
368
369 SmartPetscObj<Tao> retained_inner(inner, true);
370 SmartPetscObj<Vec> retained_multipliers(multipliers, true);
371 if (combined)
372 tao.reset();
373 else
374 CHKERR TaoSetType(tao, TAOLMVM);
375 if (retained_inner.use_count() != 1 ||
376 retained_multipliers.use_count() != 1 || dm.use_count() != 1)
377 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
378 "Parent destruction did not release null-space solver resources");
379 CHKERR checkUnavailableSubproblem(inner, x, g);
381}
382
383struct ActivationProblem {
384 PetscReal scale;
385
386 static PetscErrorCode objective(Tao, Vec x, PetscReal *f, Vec g, void *) {
388 std::array<PetscScalar, 2> values;
389 CHKERR readValues(x, values);
390 const auto k = PetscRealPart(values[0] * values[0] + values[1]);
391 *f = k + 0.5 * k * k;
392 CHKERR writeValues(
393 g, std::array<PetscScalar, 2>{2 * values[0] * (1 + k), 1 + k});
395 }
396
397 static PetscErrorCode constraints(Tao, Vec x, Vec c, void *ctx) {
399 const auto scale = static_cast<ActivationProblem *>(ctx)->scale;
400 std::array<PetscScalar, 2> values;
401 CHKERR readValues(x, values);
402 CHKERR VecSet(c, scale * (values[0] * values[0] + values[1]));
404 }
405
406 static PetscErrorCode jacobian(Tao, Vec x, Mat j, Mat, void *ctx) {
408 const auto scale = static_cast<ActivationProblem *>(ctx)->scale;
409 std::array<PetscScalar, 2> values;
410 CHKERR readValues(x, values);
411 const PetscInt columns[] = {0, 1};
412 const PetscScalar row_values[] = {2 * scale * values[0], scale};
413 PetscInt first, last;
414 CHKERR MatGetOwnershipRange(j, &first, &last);
415 for (auto row = first; row != last; ++row)
416 CHKERR MatSetValues(j, 1, &row, 2, columns, row_values, INSERT_VALUES);
417 CHKERR MatAssemblyBegin(j, MAT_FINAL_ASSEMBLY);
418 CHKERR MatAssemblyEnd(j, MAT_FINAL_ASSEMBLY);
420 }
421};
422
423MoFEMErrorCode checkCandidateActivation() {
425 PetscReal active_distance = 1e-6;
426 CHKERR PetscOptionsGetReal(nullptr, "activation_",
427 "-tao_nullspace_active_tol", &active_distance,
428 nullptr);
429 for (const auto scale : {0.1, 1.0, 10.0}) {
430 ActivationProblem problem{scale};
431 auto x = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 2);
432 auto g = vectorDuplicate(x);
433 auto sub_gradient = vectorDuplicate(x);
434 auto c = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 1);
435 PetscInt local_rows, local_columns;
436 CHKERR VecGetLocalSize(c, &local_rows);
437 CHKERR VecGetLocalSize(x, &local_columns);
438 Mat matrix;
439 CHKERR MatCreateAIJ(PETSC_COMM_WORLD, local_rows, local_columns, 1, 2,
440 local_columns, nullptr, 2 - local_columns, nullptr,
441 &matrix);
443 auto tao = createTao(PETSC_COMM_WORLD);
444 CHKERR TaoSetType(tao, TAONULLSPACE);
445 CHKERR TaoSetOptionsPrefix(tao, "activation_");
446 CHKERR TaoSetSolution(tao, x);
447 CHKERR ::TaoSetObjectiveAndGradient(tao, g, ActivationProblem::objective,
448 &problem);
449 CHKERR TaoSetInequalityConstraintsRoutine(
450 tao, c, ActivationProblem::constraints, &problem);
451 CHKERR TaoSetJacobianInequalityRoutine(
452 tao, jacobian, jacobian, ActivationProblem::jacobian, &problem);
454 CHKERR TaoSetFromOptions(tao);
455 // Freeze the current epoch without taking an optimization step.
456 CHKERR TaoSetConvergenceTest(
457 tao,
458 [](Tao tao, void *) {
459 return TaoSetConvergedReason(tao, TAO_CONVERGED_USER);
460 },
461 nullptr);
462 // Reusing the solver checks that nonlinear row norms refresh each epoch.
463 // On two ranks, the only row also contains an off-rank column.
464 for (const auto first_value : {0.5, 1.0}) {
465 const auto norm_squared = 4 * first_value * first_value + 1;
466 for (const auto distance_fraction : {0.95, 1.05}) {
467 const auto k =
468 distance_fraction * active_distance * std::sqrt(norm_squared);
469 CHKERR writeValues(x, std::array<PetscScalar, 2>{
470 first_value, k - first_value * first_value});
471 CHKERR TaoSolve(tao);
472 Vec multipliers;
473 Tao inner;
475 CHKERR TaoNullSpaceGetSubsolver(tao, &inner);
476 std::array<PetscScalar, 1> lambda;
477 std::array<PetscScalar, 2> merit_gradient;
478 CHKERR readValues(multipliers, lambda);
479 PetscReal merit;
480 CHKERR TaoComputeObjectiveAndGradient(inner, x, &merit, sub_gradient);
481 CHKERR readValues(sub_gradient, merit_gradient);
482 const bool active = distance_fraction < 1;
483 const auto expected_multiplier = active ? 1 + k : 0;
484 const auto expected_merit =
485 active ? -0.5 * k * k + 0.5 * k * k / norm_squared
486 : k + 0.5 * k * k;
487 const auto gradient_factor = active ? k / norm_squared : 1 + k;
488 constexpr PetscReal tolerance = 1e-9;
489 if (PetscAbsScalar(scale * lambda[0] - expected_multiplier) >
490 tolerance ||
491 std::abs(merit - expected_merit) > tolerance ||
492 PetscAbsScalar(merit_gradient[0] -
493 2 * first_value * gradient_factor) > tolerance ||
494 PetscAbsScalar(merit_gradient[1] - gradient_factor) > tolerance)
495 SETERRQ(PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
496 "Candidate activation or restoration changed with row "
497 "scaling: scale %g, x0 %g, normalized distance %g h",
498 static_cast<double>(scale), static_cast<double>(first_value),
499 static_cast<double>(distance_fraction));
500 }
501 }
502 }
504}
505
506struct LineSearchProblem {
507 bool objectiveDomain;
508 PetscReal domainUpper;
509 PetscInt rejectedObjectives = 0;
510 PetscReal objectivePoint = 0;
511 PetscReal constraintPoint = 0;
512 PetscReal jacobianPoint = 0;
513
514 static PetscErrorCode objective(Tao, Vec x, PetscReal *f, Vec g, void *ctx) {
516 auto &problem = *static_cast<LineSearchProblem *>(ctx);
517 std::array<PetscScalar, 1> values;
518 CHKERR readValues(x, values);
519 problem.objectivePoint = PetscRealPart(values[0]);
520 if (problem.objectiveDomain &&
521 problem.objectivePoint > problem.domainUpper) {
522 ++problem.rejectedObjectives;
523 *f = PETSC_INFINITY;
524 CHKERR VecSet(g, std::numeric_limits<PetscReal>::quiet_NaN());
526 }
527 *f = 0.5 * PetscSqr(problem.objectivePoint - 2);
528 CHKERR VecSet(g, problem.objectivePoint - 2);
530 }
531
532 static PetscErrorCode constraints(Tao, Vec x, Vec c, void *ctx) {
534 auto &problem = *static_cast<LineSearchProblem *>(ctx);
535 std::array<PetscScalar, 1> values;
536 CHKERR readValues(x, values);
537 problem.constraintPoint = PetscRealPart(values[0]);
538 if (problem.objectiveDomain &&
539 problem.constraintPoint > problem.domainUpper)
540 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
541 "Constraints evaluated after an invalid objective");
542 CHKERR VecSet(c, 1 - problem.constraintPoint);
544 }
545
546 static PetscErrorCode jacobian(Tao, Vec x, Mat j, Mat, void *ctx) {
548 auto &problem = *static_cast<LineSearchProblem *>(ctx);
549 std::array<PetscScalar, 1> values;
550 CHKERR readValues(x, values);
551 problem.jacobianPoint = PetscRealPart(values[0]);
552 if (problem.objectiveDomain && problem.jacobianPoint > problem.domainUpper)
553 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
554 "Jacobian evaluated after an invalid objective");
555 PetscInt first, last;
556 CHKERR MatGetOwnershipRange(j, &first, &last);
557 for (auto row = first; row != last; ++row)
558 CHKERR MatSetValue(j, row, 0, -1, INSERT_VALUES);
559 CHKERR MatAssemblyBegin(j, MAT_FINAL_ASSEMBLY);
560 CHKERR MatAssemblyEnd(j, MAT_FINAL_ASSEMBLY);
562 }
563};
564
565MoFEMErrorCode checkObjectiveDomainLineSearch(const bool exhaust_budget) {
567 // With gtol=0.1, no x <= 1 satisfies strong Wolfe for this descent ray.
568 // An objective-domain rejection needs an Armijo step with its true derivative.
569 LineSearchProblem problem{true, exhaust_budget ? 0.0 : 1.0};
570 auto x = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 1);
571 auto g = vectorDuplicate(x);
572 auto c = vectorDuplicate(x);
573 auto dm = createDM(PETSC_COMM_WORLD, DMSHELL);
574 CHKERR VecZeroEntries(x);
575 PetscInt local_size;
576 CHKERR VecGetLocalSize(x, &local_size);
577 Mat matrix;
578 CHKERR MatCreateAIJ(PETSC_COMM_WORLD, local_size, local_size, 1, 1,
579 local_size, nullptr, 1 - local_size, nullptr, &matrix);
581 auto tao = createTao(PETSC_COMM_WORLD);
582 CHKERR TaoSetType(tao, TAONULLSPACE);
583 CHKERR TaoSetOptionsPrefix(tao, "domain_");
584 CHKERR TaoSetSolution(tao, x);
585 CHKERR ::TaoSetObjectiveAndGradient(tao, g, LineSearchProblem::objective,
586 &problem);
587 CHKERR TaoSetInequalityConstraintsRoutine(
588 tao, c, LineSearchProblem::constraints, &problem);
589 CHKERR TaoSetJacobianInequalityRoutine(tao, jacobian, jacobian,
590 LineSearchProblem::jacobian, &problem);
592 CHKERR TaoSetMaximumIterations(tao, 1);
593 CHKERR TaoSetTolerances(tao, 1e-10, 0, 0);
594 CHKERR TaoSetConstraintTolerances(tao, 1e-10, 0);
595 CHKERR TaoSetFromOptions(tao);
596 CHKERR TaoSolve(tao);
597 TaoConvergedReason reason;
598 CHKERR TaoGetConvergedReason(tao, &reason);
599 std::array<PetscScalar, 1> solution, constraints;
600 CHKERR readValues(x, solution);
601 CHKERR readValues(c, constraints);
602 const auto accepted = PetscRealPart(solution[0]);
603 constexpr PetscReal tolerance = 1e-10;
604 if (!problem.rejectedObjectives ||
605 std::abs(problem.objectivePoint - accepted) > tolerance ||
606 std::abs(problem.constraintPoint - accepted) > tolerance ||
607 std::abs(problem.jacobianPoint - accepted) > tolerance ||
608 std::abs(PetscRealPart(constraints[0]) - (1 - accepted)) > tolerance)
609 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
610 "Objective-domain line search did not preserve valid application "
611 "state");
612 if (exhaust_budget) {
613 if (reason != TAO_DIVERGED_LS_FAILURE || accepted != 0)
614 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
615 "Exhausted line search must fail and restore the initial state");
616 } else {
617 if (!(accepted > 0) || accepted > 1 + tolerance ||
618 (reason <= 0 && reason != TAO_DIVERGED_MAXITS))
619 SETERRQ(
620 PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
621 "Objective-domain line search did not accept a valid descent step");
622 Tao inner;
623 TaoLineSearch line_search;
624 CHKERR TaoNullSpaceGetSubsolver(tao, &inner);
625 CHKERR TaoGetLineSearch(inner, &line_search);
626 Vec ls_x, ls_g;
627 CHKERR TaoGetSolution(inner, &ls_x);
628 CHKERR TaoGetGradient(inner, &ls_g, nullptr, nullptr);
629 PetscReal objective, step;
630 TaoLineSearchConvergedReason ls_reason;
631 CHKERR TaoLineSearchGetSolution(line_search, ls_x, &objective, ls_g, &step,
632 &ls_reason);
633 std::array<PetscScalar, 1> ls_solution, ls_gradient, direction;
634 CHKERR readValues(ls_x, ls_solution);
635 CHKERR readValues(ls_g, ls_gradient);
636 Vec step_direction;
637 CHKERR TaoLineSearchGetStepDirection(line_search, &step_direction);
638 CHKERR readValues(step_direction, direction);
639 if (ls_reason != TAOLINESEARCH_SUCCESS_USER || !(step > 0) ||
640 PetscAbsScalar(ls_solution[0] - accepted) > tolerance ||
641 PetscAbsScalar(step * direction[0] - accepted) > tolerance ||
642 PetscAbsScalar(ls_gradient[0] - (accepted - 2)) > tolerance ||
643 std::abs(objective - 0.5 * PetscSqr(accepted - 2)) > tolerance ||
644 objective > 2 - 1e-4 * 2 * accepted)
645 SETERRQ(
646 PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
647 "Domain fallback returned inconsistent objective, gradient or step");
648 }
649 CHKERR PetscPrintf(
650 PETSC_COMM_WORLD,
651 "Objective-domain line search budget exhaustion %d: "
652 "x=%g, rejected objectives=%d, reason=%d\n",
653 static_cast<int>(exhaust_budget), static_cast<double>(accepted),
654 static_cast<int>(problem.rejectedObjectives), static_cast<int>(reason));
656}
657
658MoFEMErrorCode checkAlgorithmOneStep(const bool exhaust_budget) {
660 LineSearchProblem problem{false, 0};
661 auto x = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 1);
662 auto g = vectorDuplicate(x);
663 auto c = vectorDuplicate(x);
664 CHKERR VecZeroEntries(x);
665 PetscInt local_size;
666 CHKERR VecGetLocalSize(x, &local_size);
667 Mat matrix;
668 CHKERR MatCreateAIJ(PETSC_COMM_WORLD, local_size, local_size, 1, 1,
669 local_size, nullptr, 1 - local_size, nullptr, &matrix);
671 auto tao = createTao(PETSC_COMM_WORLD);
672 CHKERR TaoSetType(tao, TAONULLSPACE);
673 CHKERR TaoSetOptionsPrefix(tao, "flow_");
674 CHKERR TaoSetSolution(tao, x);
675 CHKERR ::TaoSetObjectiveAndGradient(tao, g, LineSearchProblem::objective,
676 &problem);
677 CHKERR TaoSetInequalityConstraintsRoutine(
678 tao, c, LineSearchProblem::constraints, &problem);
679 CHKERR TaoSetJacobianInequalityRoutine(tao, jacobian, jacobian,
680 LineSearchProblem::jacobian, &problem);
682 CHKERR TaoSetTolerances(tao, 1e-10, 0, 0);
683 CHKERR TaoSetConstraintTolerances(tao, 1e-10, 0);
684 CHKERR TaoSetMaximumIterations(tao, exhaust_budget ? 1 : 2);
685 Tao inner;
686 CHKERR TaoNullSpaceGetSubsolver(tao, &inner);
687 // Returning to shell must recreate its line search and flow callback.
688 CHKERR TaoSetType(inner, TAOLMVM);
689 CHKERR TaoSetType(inner, TAOSHELL);
690 CHKERR TaoSetFromOptions(tao);
691 TaoLineSearch line_search;
692 CHKERR TaoGetLineSearch(inner, &line_search);
693 if (!line_search)
694 SETERRQ(
695 PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
696 "Returning the inner TAO to shell did not recreate its line search");
697 for (const auto type : {TAOLINESEARCHARMIJO, TAOLINESEARCHMT}) {
698 CHKERR TaoLineSearchSetType(line_search, type);
699 CHKERR TaoLineSearchSetType(line_search, "nullspace");
700 // The previous implementation's reset hook must not survive the switch.
701 CHKERR TaoLineSearchReset(line_search);
702 }
703 CHKERR TaoLineSearchSetFromOptions(line_search);
704 if (!exhaust_budget) {
705 CHKERR TaoMonitorSet(
706 tao,
707 [](Tao tao, void *) {
709 PetscInt iteration;
710 CHKERR TaoGetIterationNumber(tao, &iteration);
711 if (iteration == 1) {
712 Vec solution;
713 CHKERR TaoGetSolution(tao, &solution);
714 std::array<PetscScalar, 1> values;
715 CHKERR readValues(solution, values);
716 // The first -grad(Phi) step crosses the previously inactive row.
717 if (PetscAbsScalar(values[0] - 2) > 1e-12)
718 SETERRQ(PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
719 "The first null-space flow step was blocked by an "
720 "inactive inequality");
721 }
723 },
724 nullptr, nullptr);
725 }
726 CHKERR TaoSolve(tao);
727 TaoConvergedReason reason;
728 CHKERR TaoGetConvergedReason(tao, &reason);
729 std::array<PetscScalar, 1> solution, dual;
730 Vec multipliers;
732 CHKERR readValues(x, solution);
733 CHKERR readValues(multipliers, dual);
734 const auto expected_solution = exhaust_budget ? 6.0 : 1.5;
735 const auto expected_multiplier = exhaust_budget ? 0.0 : 0.5;
736 if (reason != TAO_DIVERGED_MAXITS ||
737 PetscAbsScalar(solution[0] - expected_solution) > 1e-10 ||
738 PetscAbsScalar(dual[0] - expected_multiplier) > 1e-10)
739 SETERRQ(PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
740 "Incorrect null-space flow step or refreshed active set: "
741 "budget %d, x=%g, lambda=%g, reason=%d",
742 static_cast<int>(exhaust_budget),
743 static_cast<double>(PetscRealPart(solution[0])),
744 static_cast<double>(PetscRealPart(dual[0])),
745 static_cast<int>(reason));
746
747 if (exhaust_budget) {
748 Vec inner_x, inner_g;
749 CHKERR TaoGetSolution(inner, &inner_x);
750 CHKERR TaoGetGradient(inner, &inner_g, nullptr, nullptr);
751 PetscReal objective, step;
752 TaoLineSearchConvergedReason ls_reason;
753 CHKERR TaoLineSearchGetSolution(line_search, inner_x, &objective, inner_g,
754 &step, &ls_reason);
755 std::array<PetscScalar, 1> gradient;
756 CHKERR readValues(inner_g, gradient);
757 // With one trial and stepinit=3, x=6 raises the finite merit from 2 to 8.
758 if (ls_reason != TAOLINESEARCH_SUCCESS_USER ||
759 std::abs(objective - 8) > 1e-12 || std::abs(step - 3) > 1e-12 ||
760 PetscAbsScalar(gradient[0] - 4) > 1e-12)
761 SETERRQ(PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
762 "Algorithm 1 did not accept the final finite trial at its "
763 "evaluation limit with its actual merit and gradient");
764 }
765 CHKERR PetscPrintf(PETSC_COMM_WORLD,
766 "Algorithm 1 budget fallback %d: x=%g, lambda=%g\n",
767 static_cast<int>(exhaust_budget),
768 static_cast<double>(PetscRealPart(solution[0])),
769 static_cast<double>(PetscRealPart(dual[0])));
771}
772
773} // namespace
774
775int main(int argc, char *argv[]) {
776 MoFEM::Core::Initialize(&argc, &argv, nullptr, help);
777 try {
779 PetscBool activation = PETSC_FALSE, domain = PETSC_FALSE,
780 domain_budget = PETSC_FALSE, flow = PETSC_FALSE,
781 flow_budget = PETSC_FALSE;
782 CHKERR PetscOptionsGetBool(nullptr, nullptr, "-test_nullspace_activation",
783 &activation, nullptr);
784 CHKERR PetscOptionsGetBool(nullptr, nullptr, "-test_nullspace_domain",
785 &domain, nullptr);
786 CHKERR PetscOptionsGetBool(nullptr, nullptr,
787 "-test_nullspace_domain_budget", &domain_budget,
788 nullptr);
789 CHKERR PetscOptionsGetBool(nullptr, nullptr, "-test_nullspace_flow", &flow,
790 nullptr);
791 CHKERR PetscOptionsGetBool(nullptr, nullptr, "-test_nullspace_flow_budget",
792 &flow_budget, nullptr);
793 if (activation) {
794 CHKERR checkCandidateActivation();
795 } else if (domain_budget) {
796 CHKERR checkObjectiveDomainLineSearch(true);
797 } else if (domain) {
798 CHKERR checkObjectiveDomainLineSearch(false);
799 } else if (flow || flow_budget) {
800 CHKERR checkAlgorithmOneStep(flow_budget);
801 } else {
802 const std::array<std::array<PetscScalar, 3>, 3> starts = {
803 {{1, 1, 3}, {3, 4, 2}, {1, 1, 6}}};
804 for (const auto scale : {1.0, 0.1, 10.0})
805 for (size_t i = 0; i != starts.size(); ++i)
806 CHKERR solveProblem(starts[i], scale, i != 1);
807 }
808 }
811 return 0;
812}
bool active
std::string type
SmartPetscObj< Vec > residual
SmartPetscObj< Vec > gradient
SmartPetscObj< Mat > jacobian
SmartPetscObj< Vec > values
SmartPetscObj< Mat > preconditioner
SmartPetscObj< Vec > constraints
SmartPetscObj< Vec > stationarity
SmartPetscObj< Vec > multipliers
SmartPetscObj< IS > rows
int main()
#define CATCH_ERRORS
Catch errors.
#define MoFEMFunctionReturnHot(a)
Last executable line of each PETSc function used for error handling. Replaces return()
#define MoFEMFunctionBegin
First executable line of each MoFEM function, used for error handling. Final line of MoFEM functions ...
@ MOFEM_DATA_INCONSISTENCY
Definition definitions.h:31
#define MoFEMFunctionReturn(a)
Last executable line of each PETSc function used for error handling. Replaces return()
#define CHKERR
Inline error check.
constexpr char TAONULLSPACE[]
FTensor::Index< 'i', SPACE_DIM > i
static double lambda
const double c
speed of light (cm/ns)
FTensor::Index< 'j', 3 > j
FTensor::Index< 'k', 3 > k
PetscErrorCode MoFEMErrorCode
MoFEM/PETSc error code.
implementation of Data Operators for Forces and Sources
Definition Common.hpp:10
MoFEMErrorCode TaoNullSpaceGetMultipliers(Tao tao, Vec *multipliers)
MoFEMErrorCode TaoNullSpaceSetConstraints(Tao tao, DM constraint_dm, Vec constraints, Mat jacobian, Mat preconditioner)
PetscErrorCode PetscOptionsGetReal(PetscOptions *, const char pre[], const char name[], PetscReal *dval, PetscBool *set)
PetscErrorCode PetscOptionsGetBool(PetscOptions *, const char pre[], const char name[], PetscBool *bval, PetscBool *set)
SmartPetscObj< Vec > vectorDuplicate(Vec vec)
Create duplicate vector of smart vector.
auto createVectorMPI(MPI_Comm comm, PetscInt n, PetscInt N)
Create MPI Vector.
MoFEMErrorCode MatSetValues(Mat M, const EntitiesFieldData::EntData &row_data, const EntitiesFieldData::EntData &col_data, const double *ptr, InsertMode iora)
Assemble PETSc matrix.
PetscObject getPetscObject(T obj)
MoFEMErrorCode TaoNullSpaceGetSubsolver(Tao tao, Tao *subsolver)
MoFEMErrorCode TaoRegisterNullSpace()
auto createDM(MPI_Comm comm, const std::string dm_type_name)
Creates smart DM object.
auto createTao(MPI_Comm comm)
double scale
Definition plastic.cpp:124
constexpr double g
const int N
Definition speed_test.cpp:3
static MoFEMErrorCode Initialize(int *argc, char ***args, const char file[], const char help[])
Initializes the MoFEM database PETSc, MOAB and MPI.
Definition Core.cpp:68
static MoFEMErrorCode Finalize()
Checks for options to be called at the conclusion of the program.
Definition Core.cpp:123
keeps basic data about problem
intrusive_ptr for managing petsc objects
static char help[]