v0.16.3
Loading...
Searching...
No Matches
TaoDiagonalNNLS.cpp
Go to the documentation of this file.
1/** \file TaoDiagonalNNLS.cpp
2 * \brief Direct TAO shell for NNLS with orthogonal constraint gradients.
3 */
4
5#include <petsc/private/taoimpl.h>
6#include <TaoDiagonalNNLS.hpp>
7
8#include <memory>
9
10namespace MoFEM {
11namespace {
12
13constexpr char diagonalNNLSContextKey[] = "_MoFEM_TaoDiagonalNNLS";
14
15struct TaoDiagonalNNLS {
16 Tao parentTao = nullptr;
17 SmartPetscObj<Mat> jacobian;
18 SmartPetscObj<Vec> target;
19 SmartPetscObj<Vec> residual;
20 SmartPetscObj<Vec> gradient;
21 SmartPetscObj<Vec> diagonal;
22 SmartPetscObj<Vec> work;
23 PetscErrorCode (*destroyShell)(Tao) = nullptr;
24
25 MoFEMErrorCode evaluate(Vec x, PetscReal *f, Vec g) {
27 CHKERR MatMultTranspose(jacobian, x, residual);
28 CHKERR VecAXPY(residual, -1, target);
29 if (f) {
30 PetscScalar squared;
31 CHKERR VecDot(residual, residual, &squared);
32 *f = 0.5 * PetscRealPart(squared);
33 }
34 if (g)
35 CHKERR MatMult(jacobian, residual, g);
37 }
38
39 /** Monitor the actual NNLS KKT residual, including active lower bounds. */
40 MoFEMErrorCode monitor(Vec x, PetscInt iteration, PetscReal step) {
42 PetscReal objective;
43 CHKERR TaoComputeObjectiveAndGradient(parentTao, x, &objective, gradient);
44 CHKERR VecCopy(gradient, work);
45 const PetscScalar *values;
46 PetscScalar *projected;
47 PetscInt size;
48 CHKERR VecGetLocalSize(x, &size);
49 CHKERR VecGetArrayRead(x, &values);
50 CHKERR VecGetArray(work, &projected);
51 for (PetscInt i = 0; i != size; ++i)
52 if (PetscRealPart(values[i]) == 0)
53 projected[i] = std::min(PetscRealPart(projected[i]), PetscReal(0));
54 CHKERR VecRestoreArray(work, &projected);
55 CHKERR VecRestoreArrayRead(x, &values);
56 PetscReal norm;
57 CHKERR VecNorm(work, NORM_2, &norm);
58 if (!std::isfinite(objective) || !std::isfinite(norm))
59 SETERRQ(PetscObjectComm(getPetscObject(parentTao)), PETSC_ERR_FP,
60 "Diagonal NNLS objective or projected gradient is not finite");
61 CHKERR TaoSetIterationNumber(parentTao, iteration);
62 CHKERR TaoLogConvergenceHistory(parentTao, objective, norm, 0, 0);
63 CHKERR TaoMonitor(parentTao, iteration, objective, norm, 0, step);
64 CHKERR (*parentTao->ops->convergencetest)(parentTao, parentTao->cnvP);
66 }
67
68 MoFEMErrorCode solve(Tao tao) {
70 const auto comm = PetscObjectComm(getPetscObject(tao));
71 if (!jacobian || !target)
72 SETERRQ(comm, PETSC_ERR_ARG_WRONGSTATE,
73 "Call TaoDiagonalNNLSSetProblem before TaoSolve");
74 if (tao->eq_constrained || tao->ineq_constrained ||
75 tao->ops->computeconstraints)
76 SETERRQ(comm, PETSC_ERR_SUP,
77 "Diagonal NNLS only supports nonnegativity constraints");
78 Vec x;
79 CHKERR TaoGetSolution(tao, &x);
80 PetscInt rows, columns, local_rows, local_columns;
81 PetscInt size, local_size, target_size, target_local_size;
82 CHKERR MatGetSize(jacobian, &rows, &columns);
83 CHKERR MatGetLocalSize(jacobian, &local_rows, &local_columns);
84 CHKERR VecGetSize(x, &size);
85 CHKERR VecGetLocalSize(x, &local_size);
86 CHKERR VecGetSize(target, &target_size);
87 CHKERR VecGetLocalSize(target, &target_local_size);
88 PetscInt compatible = rows == size && columns == target_size &&
89 local_rows == local_size &&
90 local_columns == target_local_size;
91 CHKERR MPIU_Allreduce(MPI_IN_PLACE, &compatible, 1, MPIU_INT, MPI_MIN,
92 comm);
93 if (!compatible)
94 SETERRQ(comm, PETSC_ERR_ARG_SIZ,
95 "Diagonal NNLS solution/target must use the Jacobian row/column "
96 "layouts");
97 if (tao->bounded || tao->ops->computebounds) {
98 CHKERR TaoComputeVariableBounds(tao);
99 compatible = 1;
100 for (const auto bound : {tao->XL, tao->XU}) {
101 if (!bound)
102 continue;
103 PetscInt bound_size, bound_local_size;
104 CHKERR VecGetSize(bound, &bound_size);
105 CHKERR VecGetLocalSize(bound, &bound_local_size);
106 if (bound_size != size || bound_local_size != local_size)
107 compatible = 0;
108 }
109 CHKERR MPIU_Allreduce(MPI_IN_PLACE, &compatible, 1, MPIU_INT, MPI_MIN,
110 comm);
111 if (!compatible)
112 SETERRQ(comm, PETSC_ERR_ARG_SIZ,
113 "Diagonal NNLS bounds must use the solution vector layout");
114 const PetscScalar *lower = nullptr, *upper = nullptr;
115 if (tao->XL)
116 CHKERR VecGetArrayRead(tao->XL, &lower);
117 if (tao->XU)
118 CHKERR VecGetArrayRead(tao->XU, &upper);
119 for (PetscInt i = 0; i != local_size; ++i)
120 if ((lower && PetscRealPart(lower[i]) != 0) ||
121 (upper && !(PetscRealPart(upper[i]) >= PETSC_INFINITY)))
122 compatible = 0;
123 if (tao->XU)
124 CHKERR VecRestoreArrayRead(tao->XU, &upper);
125 if (tao->XL)
126 CHKERR VecRestoreArrayRead(tao->XL, &lower);
127 CHKERR MPIU_Allreduce(MPI_IN_PLACE, &compatible, 1, MPIU_INT, MPI_MIN,
128 comm);
129 if (!compatible)
130 SETERRQ(comm, PETSC_ERR_SUP,
131 "Diagonal NNLS requires lower bounds zero and upper bounds "
132 "positive infinity");
133 }
134 if (rows) {
135 Mat matrix;
136 CHKERR MatMatTransposeMult(jacobian, jacobian, MAT_INITIAL_MATRIX,
137 PETSC_DEFAULT, &matrix);
138 SmartPetscObj<Mat> gram(matrix);
139 CHKERR MatGetDiagonal(gram, diagonal);
140 const PetscScalar *entries;
141 CHKERR VecGetArrayRead(diagonal, &entries);
142 compatible = 1;
143 for (PetscInt i = 0; i != local_size; ++i)
144 if (!std::isfinite(PetscRealPart(entries[i])) ||
145 !(PetscRealPart(entries[i]) > 0))
146 compatible = 0;
147 CHKERR VecRestoreArrayRead(diagonal, &entries);
148 CHKERR MPIU_Allreduce(MPI_IN_PLACE, &compatible, 1, MPIU_INT, MPI_MIN,
149 comm);
150 if (!compatible)
151 SETERRQ(comm, PETSC_ERR_ARG_OUTOFRANGE,
152 "Diagonal NNLS requires nonzero finite Jacobian rows");
153 // Normalize before checking coupling: small-volume rows must receive
154 // the same relative check as large-volume rows.
155 CHKERR VecCopy(diagonal, work);
156 CHKERR VecSqrtAbs(work);
157 CHKERR VecReciprocal(work);
158 CHKERR MatDiagonalScale(gram, work, work);
159 CHKERR MatShift(gram, -1);
160 PetscReal coupling;
161 CHKERR MatNorm(gram, NORM_INFINITY, &coupling);
162 if (!std::isfinite(coupling) || coupling > 100 * PETSC_MACHINE_EPSILON)
163 SETERRQ(comm, PETSC_ERR_ARG_INCOMP,
164 "Diagonal NNLS requires mutually orthogonal Jacobian rows; "
165 "normalized Gram coupling is %g. Use blmvm for coupled rows",
166 static_cast<double>(coupling));
167 }
168 CHKERR TaoSetConvergedReason(tao, TAO_CONTINUE_ITERATING);
169 CHKERR VecZeroEntries(work);
170 CHKERR VecPointwiseMax(x, x, work);
171 CHKERR monitor(x, 0, 1);
172 if (tao->reason != TAO_CONTINUE_ITERATING)
174 auto previous = vectorDuplicate(x);
175 CHKERR VecCopy(x, previous);
176 CHKERR MatMult(jacobian, target, x);
177 CHKERR VecPointwiseDivide(x, x, diagonal);
178 CHKERR VecZeroEntries(work);
179 CHKERR VecPointwiseMax(x, x, work);
180 CHKERR VecWAXPY(work, -1, previous, x);
181 PetscReal step;
182 CHKERR VecNorm(work, NORM_2, &step);
183 CHKERR monitor(x, 1, step);
184 if (tao->reason == TAO_CONTINUE_ITERATING ||
185 tao->reason == TAO_CONVERGED_STEPTOL) {
186 // Roundoff or tolerated Gram coupling can prevent a requested residual
187 // tolerance. Report that failure instead of declaring a direct success.
188 CHKERR TaoSetConvergedReason(tao, TAO_DIVERGED_USER);
189 CHKERR PetscInfo(tao, "Diagonal NNLS direct update did not satisfy the "
190 "requested optimality tolerance\n");
191 }
193 }
194};
195
196MoFEMErrorCode getDiagonalNNLSContext(Tao tao, TaoDiagonalNNLS *&ctx) {
198 PetscBool matches;
199 CHKERR PetscObjectTypeCompare(getPetscObject(tao), TAODIAGONALNNLS, &matches);
200 if (!matches)
201 SETERRQ(PetscObjectComm(getPetscObject(tao)), PETSC_ERR_ARG_WRONG,
202 "TAO type must be diagonal");
203 PetscContainer container = nullptr;
204 CHKERR PetscObjectQuery(getPetscObject(tao), diagonalNNLSContextKey,
205 reinterpret_cast<PetscObject *>(&container));
206 if (!container)
207 SETERRQ(PetscObjectComm(getPetscObject(tao)), PETSC_ERR_ARG_WRONGSTATE,
208 "Diagonal NNLS context is not attached");
209 void *ptr;
210 CHKERR PetscContainerGetPointer(container, &ptr);
211 ctx = static_cast<TaoDiagonalNNLS *>(ptr);
213}
214
215MoFEMErrorCode createTaoDiagonalNNLS(Tao tao) {
217 CHKERR PetscObjectChangeTypeName(getPetscObject(tao), nullptr);
218 CHKERR TaoSetType(tao, TAOSHELL);
219 const auto comm = PetscObjectComm(getPetscObject(tao));
220 auto ctx = std::make_unique<TaoDiagonalNNLS>();
221 ctx->parentTao = tao;
222 ctx->destroyShell = tao->ops->destroy;
223 PetscContainer container;
224 CHKERR PetscContainerCreate(comm, &container);
225 CHKERR PetscContainerSetPointer(container, ctx.get());
226 CHKERR PetscContainerSetUserDestroy(container, [](void *ptr) {
228 auto *ctx = static_cast<TaoDiagonalNNLS *>(ptr);
229 const auto tao = ctx->parentTao;
230 if (tao->user_objP == ctx) {
231 tao->ops->computeobjective = nullptr;
232 tao->user_objP = nullptr;
233 }
234 if (tao->user_gradP == ctx) {
235 tao->ops->computegradient = nullptr;
236 tao->user_gradP = nullptr;
237 }
238 if (tao->user_objgradP == ctx) {
239 tao->ops->computeobjectiveandgradient = nullptr;
240 tao->user_objgradP = nullptr;
241 }
242 delete ctx;
244 });
245 ctx.release();
246 const auto error = PetscObjectCompose(
247 getPetscObject(tao), diagonalNNLSContextKey, getPetscObject(container));
248 CHKERR PetscContainerDestroy(&container);
249 CHKERR error;
250 tao->ops->destroy = [](Tao tao) {
252 TaoDiagonalNNLS *ctx;
253 CHKERR getDiagonalNNLSContext(tao, ctx);
254 const auto destroy_shell = ctx->destroyShell;
255 CHKERR PetscObjectCompose(getPetscObject(tao), diagonalNNLSContextKey,
256 nullptr);
257 CHKERR destroy_shell(tao);
259 };
260 tao->ops->view = [](Tao tao, PetscViewer viewer) {
262 PetscBool ascii;
263 CHKERR PetscObjectTypeCompare(getPetscObject(viewer), PETSCVIEWERASCII,
264 &ascii);
265 if (ascii)
266 CHKERR PetscViewerASCIIPrintf(
267 viewer, " Direct nonnegative least squares with orthogonal rows\n"
268 " One diagonal update; no line search\n");
270 };
271 CHKERR TaoShellSetSolve(tao, [](Tao tao) {
273 TaoDiagonalNNLS *ctx;
274 CHKERR getDiagonalNNLSContext(tao, ctx);
275 CHKERR ctx->solve(tao);
277 });
279}
280
281} // namespace
282
285 CHKERR TaoRegister(TAODIAGONALNNLS, createTaoDiagonalNNLS);
287}
288
291 TaoDiagonalNNLS *ctx;
292 CHKERR getDiagonalNNLSContext(tao, ctx);
293 ctx->jacobian = SmartPetscObj<Mat>(jacobian, true);
294 ctx->target = SmartPetscObj<Vec>(target, true);
295 Vec row, column;
296 CHKERR MatCreateVecs(jacobian, &column, &row);
297 ctx->residual = SmartPetscObj<Vec>(column);
298 ctx->gradient = SmartPetscObj<Vec>(row);
299 ctx->diagonal = vectorDuplicate(ctx->gradient);
300 ctx->work = vectorDuplicate(ctx->gradient);
301 CHKERR ::TaoSetObjective(
302 tao,
303 [](Tao, Vec x, PetscReal *f, void *ptr) {
304 return static_cast<TaoDiagonalNNLS *>(ptr)->evaluate(x, f, nullptr);
305 },
306 ctx);
307 CHKERR ::TaoSetGradient(
308 tao, ctx->work,
309 [](Tao, Vec x, Vec g, void *ptr) {
310 return static_cast<TaoDiagonalNNLS *>(ptr)->evaluate(x, nullptr, g);
311 },
312 ctx);
313 CHKERR ::TaoSetObjectiveAndGradient(
314 tao, ctx->work,
315 [](Tao, Vec x, PetscReal *f, Vec g, void *ptr) {
316 return static_cast<TaoDiagonalNNLS *>(ptr)->evaluate(x, f, g);
317 },
318 ctx);
320}
321
322} // namespace MoFEM
MPI_Comm comm
SmartPetscObj< Vec > diagonal
SmartPetscObj< Vec > target
SmartPetscObj< Vec > work
SmartPetscObj< Vec > residual
Tao parentTao
SmartPetscObj< Vec > gradient
SmartPetscObj< Mat > jacobian
Direct nonnegative least squares for orthogonal constraint rows.
SmartPetscObj< Vec > values
PetscInt size
SmartPetscObj< IS > rows
#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 ...
#define MoFEMFunctionReturn(a)
Last executable line of each PETSc function used for error handling. Replaces return()
#define CHKERR
Inline error check.
FTensor::Index< 'i', SPACE_DIM > i
const FTensor::Tensor2< T, Dim, Dim > Vec
PetscErrorCode MoFEMErrorCode
MoFEM/PETSc error code.
implementation of Data Operators for Forces and Sources
Definition Common.hpp:10
MoFEMErrorCode TaoDiagonalNNLSSetProblem(Tao tao, Mat jacobian, Vec target)
MoFEMErrorCode TaoRegisterDiagonalNNLS()
SmartPetscObj< Vec > vectorDuplicate(Vec vec)
Create duplicate vector of smart vector.
static PetscErrorCode solve(Mat mat, Vec x, Vec y)
Definition Schur.cpp:2951
constexpr char TAODIAGONALNNLS[]
PetscObject getPetscObject(T obj)
constexpr double g
intrusive_ptr for managing petsc objects