v0.16.3
Loading...
Searching...
No Matches
tao_diagonal_nnls.cpp
Go to the documentation of this file.
1/** \file tao_diagonal_nnls.cpp
2 * \brief Test diagonal NNLS and its use for null-space multipliers.
3 */
4
5#include <MoFEM.hpp>
6
7using namespace MoFEM;
8
9static char help[] = "Test diagonal nonnegative least squares.\n\n";
10
11namespace {
12
13template <size_t N>
14MoFEMErrorCode readValues(Vec vec, std::array<PetscScalar, N> &values) {
16 values.fill(0);
17 PetscInt first, last;
18 const PetscScalar *data;
19 CHKERR VecGetOwnershipRange(vec, &first, &last);
20 CHKERR VecGetArrayRead(vec, &data);
21 for (auto i = first; i != last; ++i)
22 values[i] = data[i - first];
23 CHKERR VecRestoreArrayRead(vec, &data);
24 CHKERR MPIU_Allreduce(MPI_IN_PLACE, values.data(), N, MPIU_SCALAR, MPI_SUM,
25 PetscObjectComm(getPetscObject(vec)));
27}
28
29template <size_t N>
30MoFEMErrorCode writeValues(Vec vec, const std::array<PetscScalar, N> &values) {
32 PetscInt first, last;
33 PetscScalar *data;
34 CHKERR VecGetOwnershipRange(vec, &first, &last);
35 CHKERR VecGetArray(vec, &data);
36 for (auto i = first; i != last; ++i)
37 data[i - first] = values[i];
38 CHKERR VecRestoreArray(vec, &data);
40}
41
42SmartPetscObj<Mat> createJacobian(Vec rows, Vec columns) {
43 PetscInt local_rows, local_columns, global_rows, global_columns;
44 CHK_THROW_MESSAGE(VecGetLocalSize(rows, &local_rows), "Get local row size");
45 CHK_THROW_MESSAGE(VecGetLocalSize(columns, &local_columns),
46 "Get local column size");
47 CHK_THROW_MESSAGE(VecGetSize(rows, &global_rows), "Get row size");
48 CHK_THROW_MESSAGE(VecGetSize(columns, &global_columns), "Get column size");
49 Mat matrix;
50 CHK_THROW_MESSAGE(MatCreateAIJ(PETSC_COMM_WORLD, local_rows, local_columns,
51 global_rows, global_columns, local_columns,
52 nullptr, global_columns - local_columns,
53 nullptr, &matrix),
54 "Create Jacobian");
55 return SmartPetscObj<Mat>(matrix);
56}
57
58template <size_t N>
60writeRows(Mat matrix, const std::array<std::array<PetscScalar, 3>, N> &rows) {
62 PetscInt first, last;
63 const PetscInt columns[] = {0, 1, 2};
64 CHKERR MatGetOwnershipRange(matrix, &first, &last);
65 for (auto row = first; row != last; ++row)
66 CHKERR MatSetValues(matrix, 1, &row, 3, columns, rows[row].data(),
67 INSERT_VALUES);
68 CHKERR MatAssemblyBegin(matrix, MAT_FINAL_ASSEMBLY);
69 CHKERR MatAssemblyEnd(matrix, MAT_FINAL_ASSEMBLY);
71}
72
73struct LeastSquares {
74 Mat jacobian;
75 Vec target;
77
78 static PetscErrorCode evaluate(Tao, Vec x, PetscReal *f, Vec g, void *ctx) {
80 auto &problem = *static_cast<LeastSquares *>(ctx);
81 CHKERR MatMultTranspose(problem.jacobian, x, problem.residual);
82 CHKERR VecAXPY(problem.residual, -1, problem.target);
83 PetscScalar squared;
84 CHKERR VecDot(problem.residual, problem.residual, &squared);
85 *f = 0.5 * PetscRealPart(squared);
86 CHKERR MatMult(problem.jacobian, problem.residual, g);
88 }
89};
90
91MoFEMErrorCode checkDirectSolve() {
93 auto x = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 2);
94 auto nonnegative = vectorDuplicate(x);
95 CHKERR VecZeroEntries(nonnegative);
96 auto target = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 3);
97 auto jacobian = createJacobian(x, target);
98 auto tao = createTao(PETSC_COMM_WORLD);
99 CHKERR TaoSetType(tao, TAOSHELL);
100 CHKERR TaoSetType(tao, TAODIAGONALNNLS);
101 CHKERR TaoSetOptionsPrefix(tao, "direct_");
102 CHKERR TaoSetSolution(tao, x);
103 CHKERR TaoSetFromOptions(tao);
104
105 struct Case {
106 std::array<PetscScalar, 3> target;
107 std::array<PetscScalar, 2> scales, expected;
108 PetscReal objective;
109 };
110 const std::array<Case, 4> cases = {{{{3, 4, 1}, {1, 1}, {1, 0}, 0.5},
111 {{7, 1, 2}, {1, 1}, {1, 0}, 14.5},
112 {{0, 0, 0}, {1, 1}, {0, 0}, 0},
113 {{-1, 7, 2}, {0.1, 10}, {10, 0.1}, 2}}};
114 for (size_t i = 0; i != cases.size(); ++i) {
115 const auto &test = cases[i];
116 // Orthogonal rows deliberately overlap in their control-vector support.
117 CHKERR writeRows(jacobian,
118 std::array<std::array<PetscScalar, 3>, 2>{
119 {{3 * test.scales[0], 4 * test.scales[0], 0},
120 {-4 * test.scales[1], 3 * test.scales[1], 0}}});
121 SmartPetscObj<Vec> previous_target;
122 if (i == 2) {
123 previous_target = target;
125 }
126 CHKERR writeValues(target, test.target);
127 if (i == 0 || i == 2)
129 if (previous_target && previous_target.use_count() != 1)
130 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
131 "Rebinding did not release the previous NNLS target");
132 if (i == 1)
133 CHKERR TaoSetVariableBounds(tao, nonnegative, nullptr);
134 CHKERR VecSet(x, 7);
135 CHKERR TaoSolve(tao);
136 PetscReal objective, projected_gradient;
137 TaoConvergedReason reason;
138 CHKERR TaoGetSolutionStatus(tao, nullptr, &objective, &projected_gradient,
139 nullptr, nullptr, &reason);
140 Vec stored_gradient;
141 PetscReal stored_norm;
142 CHKERR TaoGetGradient(tao, &stored_gradient, nullptr, nullptr);
143 CHKERR VecNorm(stored_gradient, NORM_2, &stored_norm);
144 std::array<PetscScalar, 2> values;
145 CHKERR readValues(x, values);
146 if (reason <= 0 || !std::isfinite(stored_norm) ||
147 std::abs(stored_norm - projected_gradient) > 1e-10 ||
148 std::abs(objective - test.objective) > 1e-10 ||
149 projected_gradient > 1e-10 ||
150 PetscAbsScalar(values[0] - test.expected[0]) > 1e-10 ||
151 PetscAbsScalar(values[1] - test.expected[1]) > 1e-10)
152 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
153 "Incorrect diagonal NNLS solution or convergence status");
154
155 auto reference = createTao(PETSC_COMM_WORLD);
156 auto reference_x = vectorDuplicate(x);
157 auto gradient = vectorDuplicate(x);
158 auto lower = vectorDuplicate(x);
159 auto upper = vectorDuplicate(x);
160 CHKERR VecZeroEntries(reference_x);
161 CHKERR VecZeroEntries(lower);
162 CHKERR VecSet(upper, PETSC_INFINITY);
163 LeastSquares problem{jacobian, target, vectorDuplicate(target)};
164 CHKERR TaoSetType(reference, TAOBLMVM);
165 CHKERR TaoSetSolution(reference, reference_x);
166 CHKERR TaoSetVariableBounds(reference, lower, upper);
167 CHKERR ::TaoSetObjectiveAndGradient(reference, gradient,
168 LeastSquares::evaluate, &problem);
169 CHKERR TaoSetTolerances(reference, 1e-10, 0, 0);
170 CHKERR TaoSetMaximumIterations(reference, 1000);
171 CHKERR TaoSolve(reference);
172 CHKERR TaoGetConvergedReason(reference, &reason);
173 CHKERR VecAXPY(reference_x, -1, x);
174 PetscReal difference;
175 CHKERR VecNorm(reference_x, NORM_INFINITY, &difference);
176 if (reason <= 0 || difference > 1e-7)
177 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
178 "Diagonal NNLS disagrees with BLMVM");
179 }
180 auto short_bound = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 1);
181 CHKERR VecZeroEntries(short_bound);
182 for (const auto wrong_size : {true, false}) {
183 if (!wrong_size)
184 CHKERR VecSet(nonnegative, 1);
185 CHKERR PetscPushErrorHandler(PetscReturnErrorHandler, nullptr);
186 auto error = TaoSetVariableBounds(
187 tao, wrong_size ? short_bound.get() : nonnegative.get(), nullptr);
188 if (!error)
189 error = TaoSolve(tao);
190 CHKERR PetscPopErrorHandler();
191 if (error != (wrong_size ? PETSC_ERR_ARG_SIZ : PETSC_ERR_SUP))
192 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
193 "Diagonal NNLS did not reject unsupported bounds");
194 }
195 CHKERR VecZeroEntries(nonnegative);
196 CHKERR TaoSetVariableBounds(tao, nonnegative, nullptr);
197 if (jacobian.use_count() < 2 || target.use_count() < 2)
198 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
199 "NNLS did not retain its problem data");
200 CHKERR TaoSetType(tao, TAOLMVM);
201 if (jacobian.use_count() != 1 || target.use_count() != 1)
202 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
203 "Changing TAO type did not release NNLS problem data");
204 CHKERR PetscPrintf(PETSC_COMM_WORLD,
205 "Diagonal NNLS: four analytic cases agree with BLMVM\n");
207}
208
209MoFEMErrorCode checkInvalidRows(const bool zero_row) {
211 auto x = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 2);
212 auto target = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 3);
213 auto jacobian = createJacobian(x, target);
214 CHKERR VecZeroEntries(x);
215 CHKERR VecSet(target, 1);
216 CHKERR writeRows(jacobian,
217 std::array<std::array<PetscScalar, 3>, 2>{
218 {zero_row ? std::array<PetscScalar, 3>{0, 0, 0}
219 : std::array<PetscScalar, 3>{1, 1, 0},
220 {0, 1, 1}}});
221 auto tao = createTao(PETSC_COMM_WORLD);
222 CHKERR TaoSetType(tao, TAODIAGONALNNLS);
223 CHKERR TaoSetSolution(tao, x);
224 CHKERR PetscPushErrorHandler(PetscReturnErrorHandler, nullptr);
225 auto error = TaoDiagonalNNLSSetProblem(tao, jacobian, target);
226 if (!error)
227 error = TaoSolve(tao);
228 CHKERR PetscPopErrorHandler();
229 if (error != (zero_row ? PETSC_ERR_ARG_OUTOFRANGE : PETSC_ERR_ARG_INCOMP))
230 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
231 "Diagonal NNLS did not reject unsupported Jacobian rows");
233}
234
235MoFEMErrorCode checkNullSpaceIntegration() {
237 auto x = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 3);
238 auto gradient = vectorDuplicate(x);
239 auto c = createVectorMPI(PETSC_COMM_WORLD, PETSC_DECIDE, 1);
240 auto jacobian = createJacobian(c, x);
241 auto dm = createDM(PETSC_COMM_WORLD, DMSHELL);
242 auto tao = createTao(PETSC_COMM_WORLD);
243 CHKERR writeValues(x, std::array<PetscScalar, 3>{1, 1, 3});
244 CHKERR TaoSetType(tao, TAONULLSPACE);
245 CHKERR TaoSetOptionsPrefix(tao, "cell_");
246 CHKERR TaoSetSolution(tao, x);
247 CHKERR ::TaoSetObjectiveAndGradient(
248 tao, gradient,
249 [](Tao, Vec x, PetscReal *f, Vec g, void *) {
251 std::array<PetscScalar, 3> p;
252 CHKERR readValues(x, p);
253 *f = 0.5 * (PetscSqr(p[0] - 3) + PetscSqr(p[1] - 4)) + p[2];
254 CHKERR writeValues(g,
255 std::array<PetscScalar, 3>{p[0] - 3, p[1] - 4, 1});
257 },
258 nullptr);
259 CHKERR TaoSetInequalityConstraintsRoutine(
260 tao, c,
261 [](Tao, Vec x, Vec c, void *) {
263 std::array<PetscScalar, 3> p;
264 CHKERR readValues(x, p);
265 CHKERR VecSet(c, p[2] - std::hypot(p[0], p[1]));
267 },
268 nullptr);
269 CHKERR TaoSetJacobianInequalityRoutine(
270 tao, jacobian, jacobian,
271 [](Tao, Vec x, Mat j, Mat, void *) {
273 std::array<PetscScalar, 3> p;
274 CHKERR readValues(x, p);
275 const auto radius = std::hypot(p[0], p[1]);
276 CHKERR writeRows(j, std::array<std::array<PetscScalar, 3>, 1>{
277 {{-p[0] / radius, -p[1] / radius, 1}}});
279 },
280 nullptr);
282 CHKERR TaoSetMaximumIterations(tao, 300);
283 CHKERR TaoSetTolerances(tao, 1e-7, 0, 0);
284 CHKERR TaoSetConstraintTolerances(tao, 1e-7, 0);
285 CHKERR TaoSetFromOptions(tao);
286 CHKERR TaoSolve(tao);
287 PetscReal objective, residual;
288 TaoConvergedReason reason;
289 CHKERR TaoGetSolutionStatus(tao, nullptr, &objective, &residual, nullptr,
290 nullptr, &reason);
291 std::array<PetscScalar, 3> values;
292 CHKERR readValues(x, values);
293 Vec multipliers;
295 std::array<PetscScalar, 1> dual, constraint;
296 CHKERR readValues(multipliers, dual);
297 CHKERR readValues(c, constraint);
298 if (reason <= 0 || residual > 1e-7 || std::abs(objective - 4.5) > 5e-6 ||
299 PetscAbsScalar(values[0] - 2.4) > 5e-6 ||
300 PetscAbsScalar(values[1] - 3.2) > 5e-6 ||
301 PetscAbsScalar(values[2] - 4) > 5e-6 ||
302 PetscAbsScalar(dual[0] - 1) > 5e-6 ||
303 PetscRealPart(constraint[0]) < -1e-7)
304 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
305 "Incorrect null-space optimum with diagonal multiplier solver");
306 CHKERR PetscPrintf(
307 PETSC_COMM_WORLD,
308 "Null-space diagonal multipliers: x=(%g,%g,%g), KKT=%g\n",
309 static_cast<double>(values[0]), static_cast<double>(values[1]),
310 static_cast<double>(values[2]), static_cast<double>(residual));
312}
313
314} // namespace
315
316int main(int argc, char *argv[]) {
317 MoFEM::Core::Initialize(&argc, &argv, nullptr, help);
318 try {
321 CHKERR checkDirectSolve();
322 CHKERR checkInvalidRows(false);
323 CHKERR checkInvalidRows(true);
324 CHKERR checkNullSpaceIntegration();
325 }
328 return 0;
329}
SmartPetscObj< Vec > target
SmartPetscObj< Vec > residual
SmartPetscObj< Vec > gradient
SmartPetscObj< Mat > jacobian
SmartPetscObj< Vec > values
SmartPetscObj< Vec > multipliers
SmartPetscObj< IS > rows
int main()
#define CATCH_ERRORS
Catch errors.
#define CHK_THROW_MESSAGE(err, msg)
Check and throw MoFEM exception.
#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
const double c
speed of light (cm/ns)
FTensor::Index< 'j', 3 > j
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 TaoDiagonalNNLSSetProblem(Tao tao, Mat jacobian, Vec target)
MoFEMErrorCode TaoNullSpaceSetConstraints(Tao tao, DM constraint_dm, Vec constraints, Mat jacobian, Mat preconditioner)
MoFEMErrorCode TaoRegisterDiagonalNNLS()
SmartPetscObj< Vec > vectorDuplicate(Vec vec)
Create duplicate vector of smart vector.
auto createVectorMPI(MPI_Comm comm, PetscInt n, PetscInt N)
Create MPI Vector.
constexpr char TAODIAGONALNNLS[]
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 TaoRegisterNullSpace()
auto createDM(MPI_Comm comm, const std::string dm_type_name)
Creates smart DM object.
auto createTao(MPI_Comm comm)
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
intrusive_ptr for managing petsc objects
static char help[]