MiniTensor Version of the Day
Loading...
Searching...
No Matches
MiniTensor_Solvers.h
Go to the documentation of this file.
1// @HEADER
2// *****************************************************************************
3// MiniTensor Package
4//
5// Copyright 2016 NTESS and the MiniTensor contributors.
6// SPDX-License-Identifier: BSD-3-Clause
7// *****************************************************************************
8// @HEADER
9
10#if !defined(MiniTensor_Solvers_h)
11#define MiniTensor_Solvers_h
12
13#include <iomanip>
14#include <memory>
15#include <utility>
16
17#include "MiniTensor.h"
18
19namespace minitensor
20{
21
24
26template<typename T, int N>
27using FAD = Sacado::Fad::SLFad<T, N>;
28
35template<typename FunctionDerived, typename S, Index M>
37{
38public:
42 static constexpr
43 Index
45
47 {
48 }
49
54 template<typename T, Index N>
55 T
56 value(FunctionDerived & f, Vector<T, N> const & x);
57
61 template<typename T, Index N>
63 gradient(FunctionDerived & f, Vector<T, N> const & x);
64
68 template<typename T, Index N>
70 residual(FunctionDerived & f, Vector<T, N> const & x);
71
75 template<typename T, Index N>
77 hessian(FunctionDerived & f, Vector<T, N> const & x);
78
83 void
84 set_failed(char const * const msg = nullptr);
85
89 bool
91
95 void
97
101 void
102 set_failure_message(char const * const msg = nullptr);
103
107 char const *
109
110protected:
114 bool
115 failed{false};
116
119 char const *
121};
122
126template<typename ConstraintDerived, typename S, Index NC, Index NV>
128{
129public:
131 {
132 }
133
137 template<typename T, Index N>
139 value(ConstraintDerived & c, Vector<T, N> const & x);
140
144 template<typename T, Index N>
146 gradient(ConstraintDerived & c, Vector<T, N> const & x);
147
151 static constexpr
152 bool
154
158 bool
159 failed{false};
160
164 static constexpr
165 Index
167
171 static constexpr
172 Index
174};
175
179template<typename ConstraintDerived, typename S, Index NC, Index NV>
181 public Equality_Constraint<ConstraintDerived, S, NC, NV>
182{
186 static constexpr
187 bool
189};
190
194template<typename T, Index N>
195struct Bounds
196{
200 Bounds(Vector<T, N> const & l, Vector<T, N> const & u);
201
207
213};
214
218template<typename T, Index N>
220{
221public:
222 Minimizer();
223
228 template<typename STEP, typename FN>
229 void
230 solve(STEP & step_method, FN & fn, Vector<T, N> & x);
231
236 void
237 printReport(std::ostream & os);
238
239private:
244 void
246
251 void
252 updateDivergenceCriterion(T const fn_value);
253
257 bool
258 continueSolve() const;
259
264 template<typename FN>
265 void
266 recordFinals(FN & fn, Vector<T, N> const & x);
267
268public:
272 Index
274
278 Index
280
284 Index
286
290 Index
292
297 Index
299
303 T
305
309 T
310 rel_tol{1.0e-12};
311
315 T
317
321 T
322 abs_tol{1.0e-12};
323
328 T
329 acc_tol{1.0e-12};
330
335 T
337
341 T
343
348 T
350
354 T
356
361 T
363
367 T
369
373 bool
374 failed{false};
375
379 bool
380 warning{false};
381
385 bool
386 converged{false};
387
391 bool
393
397 bool
398 bounded{true};
399
403 bool
405
410 bool
412
417 bool
419
423 bool
425
431
437
443
449
453 char const *
455
459 char const *
461
465 char const *
466 failure_message{"No failure detected"};
467
471 char const *
472 warning_message{"No warning detected"};
473};
474
478template<typename T, Index N>
480{
485 template<typename FN>
487 step(FN & fn, Vector<T, N> const & direction, Vector<T, N> const & soln);
488
492 Index
494
498 T
499 tolerance{1.0e-6};
500};
501
505template<typename T, Index N>
507{
512 template<typename FN>
514 step(FN & fn, Vector<T, N> const & direction, Vector<T, N> const & soln);
515
519 Index
521
526 Index
528
532 T
534
539 T
541
545 T
546 alpha{1.0};
547
551 T
552 tolerance{1.0e-6};
553};
554
558template<typename T, Index N>
574
579template<typename T, Index N>
581{
587 step(Tensor<T, N> const & Hessian, Vector<T, N> const & gradient);
588
592 Index
594
598 T
600};
601
606template<typename T, Index N>
608{
614 step(Tensor<T, N> const & Hessian, Vector<T, N> const & gradient);
615
619 Index
621
625 T
627};
628
633template<typename T, Index N>
635{
641 step(Tensor<T, N> const & Hessian, Vector<T, N> const & gradient);
642
646 T
648};
649
654template<typename T, Index N>
656{
662 step(Tensor<T, N> const & Hessian, Vector<T, N> const & gradient);
663
667 T
669};
670
674template<typename FN, typename T, Index N>
676{
678 {
679 constexpr bool
680 is_fad = Sacado::IsADType<T>::value == true;
681
682 static_assert(is_fad == false, "AD types not allowed for type T");
683 }
684
688 virtual
689 char const *
690 name() = 0;
691
696 virtual
697 void
698 initialize(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r) = 0;
699
704 virtual
706 step(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r) = 0;
707
708 virtual
710
716
722 lin_solve(Tensor<T, N> const & A, Vector<T, N> const & b);
723};
724
728enum class StepType
729{
730 UNDEFINED = 0,
731 NEWTON = 1,
732 NEWTON_LS = 2,
733 TRUST_REGION = 3,
734 CG = 4,
736};
737
741template<typename FN, typename T, Index N>
742std::unique_ptr<StepBase<FN, T, N>>
743stepFactory(StepType step_type);
744
748template<typename FN, typename T, Index N>
749struct NewtonStep : public StepBase<FN, T, N>
750{
754 static constexpr
755 char const * const
756 NAME{"Newton"};
757
761 virtual
762 char const *
764 {
765 return NAME;
766 }
767
771 virtual
772 void
773 initialize(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r);
774
778 virtual
780 step(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r);
781
782 virtual
784};
785
786
790template<typename FN, typename T, Index N>
791struct NewtonWithLineSearchStep : public StepBase<FN, T, N>
792{
796 static constexpr
797 char const * const
798 NAME{"Newton with Line Search"};
799
803 virtual
804 char const *
806 {
807 return NAME;
808 }
809
813 virtual
814 void
815 initialize(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r);
816
821 virtual
823 step(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r);
824
825 virtual
827};
828
832template<typename FN, typename T, Index N>
833struct TrustRegionStep : public StepBase<FN, T, N>
834{
838 static constexpr
839 char const * const
840 NAME{"Trust Region"};
841
845 virtual
846 char const *
848 {
849 return NAME;
850 }
851
855 virtual
856 void
857 initialize(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r);
858
863 virtual
865 step(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r);
866
867 virtual
869
873 T
875
879 T
881
885 T
887
888private:
892 T
894};
895
899template<typename FN, typename T, Index N>
900struct ConjugateGradientStep : public StepBase<FN, T, N>
901{
905 static constexpr
906 char const * const
907 NAME{"Preconditioned Conjugate Gradient"};
908
912 virtual
913 char const *
915 {
916 return NAME;
917 }
918
923 virtual
924 void
925 initialize(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r);
926
931 virtual
933 step(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r);
934
935 virtual
937
942 Index
944
945private:
951
957
961 T
963
968 Index
970};
971
975template<typename FN, typename T, Index N>
976struct LineSearchRegularizedStep : public StepBase<FN, T, N>
977{
981 static constexpr
982 char const * const
983 NAME{"Line Search Regularized"};
984
988 virtual
989 char const *
991 {
992 return NAME;
993 }
994
998 virtual
999 void
1000 initialize(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r);
1001
1006 virtual
1008 step(FN & fn, Vector<T, N> const & x, Vector<T, N> const & r);
1009
1010 virtual
1012
1016 T
1018
1023 T
1025};
1026
1027} // namespace minitensor
1028
1029namespace minitensor
1030{
1031
1032//
1033//
1034//
1035template<typename FunctionDerived, typename S, Index M>
1036template<typename T, Index N>
1037T
1039value(FunctionDerived & f, Vector<T, N> const & x)
1040{
1041 assert(x.get_dimension() <= DIMENSION);
1042
1043 Vector<T, N> const
1044 r = residual(f, x);
1045
1046 return 0.5 * dot(r, r);
1047}
1048
1049//
1050//
1051//
1052template<typename FunctionDerived, typename S, Index M>
1053template<typename T, Index N>
1056gradient(FunctionDerived & f, Vector<T, N> const & x)
1057{
1058 using AD = FAD<T, N>;
1059
1060 Index const
1061 dimension = x.get_dimension();
1062
1063 assert(dimension <= DIMENSION);
1064
1066 x_ad(dimension);
1067
1068 for (Index i{0}; i < dimension; ++i) {
1069 x_ad(i) = AD(dimension, i, x(i));
1070 }
1071
1072 AD const
1073 f_ad = f.value(x_ad);
1074
1076 gradient(dimension);
1077
1078 for (Index i{0}; i < dimension; ++i) {
1079 gradient(i) = f_ad.dx(i);
1080 }
1081
1082 return gradient;
1083}
1084
1085//
1086//
1087//
1088template<typename FunctionDerived, typename S, Index M>
1089template<typename T, Index N>
1092residual(FunctionDerived & f, Vector<T, N> const & x)
1093{
1094 return f.gradient(x);
1095}
1096
1097//
1098//
1099//
1100template<typename FunctionDerived, typename S, Index M>
1101template<typename T, Index N>
1104hessian(FunctionDerived & f, Vector<T, N> const & x)
1105{
1106 using AD = FAD<T, N>;
1107
1108 Index const
1109 dimension = x.get_dimension();
1110
1111 assert(dimension <= DIMENSION);
1112
1114 x_ad(dimension);
1115
1116 for (Index i{0}; i < dimension; ++i) {
1117 x_ad(i) = AD(dimension, i, x(i));
1118 }
1119
1120 Vector<AD, N> const
1121 r_ad = f.gradient(x_ad);
1122
1124 Hessian(dimension);
1125
1126 for (Index i{0}; i < dimension; ++i) {
1127 for (Index j{0}; j < dimension; ++j) {
1128 Hessian(i, j) = r_ad(i).dx(j);
1129 }
1130 }
1131
1132 return Hessian;
1133}
1134
1135//
1136//
1137//
1138template<typename FunctionDerived, typename S, Index M>
1139void
1141set_failed(char const * const msg)
1142{
1143 failed = true;
1144 failure_message = msg;
1145 return;
1146}
1147
1148//
1149//
1150//
1151template<typename FunctionDerived, typename S, Index M>
1152bool
1154get_failed()
1155{
1156 return failed;
1157}
1158
1159//
1160//
1161//
1162template<typename FunctionDerived, typename S, Index M>
1163void
1166{
1167 failed = false;
1168 return;
1169}
1170
1171//
1172//
1173//
1174template<typename FunctionDerived, typename S, Index M>
1175void
1177set_failure_message(char const * const msg)
1178{
1179 failure_message = msg;
1180 return;
1181}
1182
1183//
1184//
1185//
1186template<typename FunctionDerived, typename S, Index M>
1187char const *
1190{
1191 return failure_message;
1192}
1193
1194//
1195//
1196//
1197template<typename ConstraintDerived, typename S, Index NC, Index NV>
1198template<typename T, Index N>
1201value(ConstraintDerived & c, Vector<T, N> const & x)
1202{
1203 assert(x.get_dimension() <= NUM_VAR);
1204 return c.value(x);
1205}
1206
1207//
1208//
1209//
1210template<typename ConstraintDerived, typename S, Index NC, Index NV>
1211template<typename T, Index N>
1214gradient(ConstraintDerived & c, Vector<T, N> const & x)
1215{
1216 using AD = FAD<T, N>;
1217
1218 Index const
1219 num_var = x.get_dimension();
1220
1221 assert(num_var <= NUM_VAR);
1222
1224 x_ad(num_var);
1225
1226 for (Index i{0}; i < num_var; ++i) {
1227 x_ad(i) = AD(num_var, i, x(i));
1228 }
1229
1230 Vector<AD, NC> const
1231 r_ad = c.value(x_ad);
1232
1233 Index const
1234 num_constr = r_ad.get_dimension();
1235
1237 Jacobian(num_constr, num_var);
1238
1239 for (Index i{0}; i < num_constr; ++i) {
1240 for (Index j{0}; j < num_var; ++j) {
1241 Jacobian(i, j) = r_ad(i).dx(j);
1242 }
1243 }
1244
1245 return Jacobian;
1246}
1247
1248//
1249//
1250//
1251template<typename T, Index N>
1253Bounds(Vector<T, N> const & l, Vector<T, N> const & u) : lower(l), upper(u)
1254{
1255 return;
1256}
1257
1258//
1259//
1260//
1261template<typename T, Index N>
1263Minimizer()
1264{
1265 constexpr bool
1266 is_fad = Sacado::IsADType<T>::value == true;
1267
1268 static_assert(is_fad == false, "AD types not allowed for type T");
1269
1270 return;
1271}
1272
1273//
1274//
1275//
1276template<typename T, Index N>
1277template<typename STEP, typename FN>
1278void
1280solve(STEP & step_method, FN & fn, Vector<T, N> & soln)
1281{
1282 step_method_name = step_method.name();
1283 function_name = FN::NAME;
1284 initial_guess = soln;
1285
1287 resi = fn.gradient(soln);
1288
1289 initial_value = fn.value(soln);
1290 previous_value = initial_value;
1291 failed = failed || fn.get_failed();
1292 if (fn.get_failed() == true) failure_message = fn.get_failure_message();
1293 initial_norm = norm(resi);
1294
1295 updateConvergenceCriterion(initial_norm);
1296
1297 step_method.initialize(fn, soln, resi);
1298
1299 while (continueSolve() == true) {
1300
1301 Vector<T, N> const
1302 step = step_method.step(fn, soln, resi);
1303
1304 soln += step;
1305
1306 resi = fn.gradient(soln);
1307
1308 failed = failed || fn.get_failed();
1309 if (fn.get_failed() == true) failure_message = fn.get_failure_message();
1310
1311 T const
1312 norm_resi = norm(resi);
1313
1314 updateConvergenceCriterion(norm_resi);
1315
1316 T const
1317 value = fn.value(soln);
1318
1319 failed = failed || fn.get_failed();
1320 if (fn.get_failed() == true) failure_message = fn.get_failure_message();
1321
1322 updateDivergenceCriterion(value);
1323
1324 ++num_iter;
1325 }
1326
1327 recordFinals(fn, soln);
1328 return;
1329}
1330
1331//
1332//
1333//
1334template<typename T, Index N>
1335void
1337printReport(std::ostream & os)
1338{
1339 char const * const
1340 converged_string = converged == true ? "YES" : "NO";
1341
1342 // Happy / frowny face
1343 //char const * const
1344 //converged_string = converged == true ? "\U0001F60A" : "\U0001F623";
1345
1346 os << "\n\n";
1347 os << "Method : " << step_method_name << '\n';
1348 os << "Function : " << function_name << '\n';
1349 os << "Converged : " << converged_string << '\n';
1350 os << "Max Iters : " << max_num_iter << '\n';
1351 os << "Iters Taken : " << num_iter << '\n';
1352
1353 os << std::scientific << std::setprecision(17);
1354
1355 os << "Initial |R| : " << std::setw(24) << initial_norm << '\n';
1356 os << "Abs Tol : " << std::setw(24) << abs_tol << '\n';
1357 os << "Abs Error : " << std::setw(24) << abs_error << '\n';
1358 os << "Rel Tol : " << std::setw(24) << rel_tol << '\n';
1359 os << "Rel Error : " << std::setw(24) << rel_error << '\n';
1360 os << "Initial X : " << initial_guess << '\n';
1361 os << "Initial f(X) : " << std::setw(24) << initial_value << '\n';
1362 os << "Final X : " << final_soln << '\n';
1363 os << "Final f(X) : " << std::setw(24) << final_value << '\n';
1364 os << "Final Df(X) : " << final_gradient << '\n';
1365 os << "Final DDf(X) : " << final_hessian << '\n';
1366 os << '\n';
1367
1368 return;
1369}
1370
1371//
1372//
1373//
1374template<typename T, Index N>
1375void
1378{
1379 abs_error = ae;
1380 rel_error = initial_norm > 0.0 ? abs_error / initial_norm : T(0.0);
1381
1382 bool const
1383 converged_absolute = abs_error <= abs_tol;
1384
1385 bool const
1386 converged_relative = rel_error <= rel_tol;
1387
1388 converged = converged_absolute || converged_relative;
1389
1390 bool const
1391 converged_acceptable = abs_error <= acc_tol && num_iter == max_num_iter - 1;
1392
1393 if (converged == false && converged_acceptable == true) {
1394 converged = true;
1395 warning = true;
1396 warning_message = "Reached acceptable tolerance";
1397 }
1398
1399 return;
1400}
1401
1402//
1403//
1404//
1405template<typename T, Index N>
1406void
1408updateDivergenceCriterion(T const fn_value)
1409{
1410 monotonic = fn_value <= previous_value;
1411
1412 if (enforce_monotonicity == true && monotonic == false) {
1413 failed = true;
1414 failure_message = "Non-monotonic";
1415 }
1416
1417 T reduction_ratio = previous_value > 0.0 ? (fn_value / previous_value) : 0.0;
1418
1419 if (reduction_ratio > stagnation_tol) {
1420 ++num_stagnation_iter;
1421 }
1422 else {
1423 num_stagnation_iter = 0;
1424 }
1425
1426 non_stagnant = num_stagnation_iter < max_stagnation_iter;
1427
1428 // only set warning for stagnant residual
1429 if (enforce_non_stagnation == true && non_stagnant == false) {
1430 warning = true;
1431 warning_message = "Stagnant residual";
1432 }
1433
1434 previous_value = fn_value;
1435
1436 bounded = fn_value <= growth_limit * initial_value;
1437
1438 if (enforce_boundedness == true && bounded == false) {
1439 failed = true;
1440 failure_message = "Growing unbounded";
1441 }
1442
1443 return;
1444}
1445
1446//
1447//
1448//
1449template<typename T, Index N>
1450bool
1452continueSolve() const
1453{
1454 // If failure has occurred, stop immediately.
1455 if (failed == true) return false;
1456
1457 // Regardless of other criteria, if the residual is zero stop solving.
1458 bool const
1459 zero_resi = ((abs_error > 0.0) == false);
1460
1461 if (zero_resi == true) return false;
1462
1463 // Minimum iterations takes precedence over maximum iterations and
1464 // convergence. Continue solving if not exceeded.
1465 bool const
1466 exceeds_min_iter = num_iter >= min_num_iter;
1467
1468 if (exceeds_min_iter == false) return true;
1469
1470 // Maximum iterations takes precedence over convergence.
1471 // Stop solving if exceeded.
1472 bool const
1473 exceeds_max_iter = num_iter >= max_num_iter;
1474
1475 if (exceeds_max_iter == true) return false;
1476
1477 // Lastly check for convergence.
1478 bool const
1479 continue_solve = (converged == false);
1480
1481 return continue_solve;
1482}
1483
1484//
1485//
1486//
1487template<typename T, Index N>
1488template<typename FN>
1489void
1491recordFinals(FN & fn, Vector<T, N> const & x)
1492{
1493 final_soln = x;
1494 final_value = fn.value(x);
1495 final_gradient = fn.gradient(x);
1496 final_hessian = fn.hessian(x);
1497}
1498
1499//
1500// Linear solver for step methods
1501//
1502template<typename FN, typename T, Index N>
1505lin_solve(Tensor<T, N> const & A, Vector<T, N> const & b)
1506{
1507 return solve(A, b, preconditioner_type);
1508}
1509
1510//
1511// Linear solve for trust region subproblems
1512//
1513template<typename T, Index N>
1516lin_solve(Tensor<T, N> const & A, Vector<T, N> const & b)
1517{
1518 return solve(A, b, preconditioner_type);
1519}
1520
1521//
1522// Trust region subproblem with given objective function.
1523// Exact algorithm, Nocedal 2nd Ed 4.3
1524//
1525template<typename T, Index N>
1528step(Tensor<T, N> const & Hessian, Vector<T, N> const & gradient)
1529{
1530 Index const
1531 dimension = gradient.get_dimension();
1532
1533 Tensor<T, N> const
1534 I = identity<T, N>(dimension);
1535
1537 step(dimension);
1538
1539 // set to Hessian norm to ensure that K is positive definite
1540 T
1541 lambda = norm(Hessian);
1542
1543 for (Index i{0}; i < max_num_iter; ++i) {
1544
1545 Tensor<T, N> const
1546 K = Hessian + lambda * I;
1547
1549 L(Filler::ZEROS);
1550
1551 bool
1552 is_posdef{false};
1553
1554 std::tie(L, is_posdef) = cholesky(K);
1555
1556 if (is_posdef == false) {
1557 MT_ERROR_EXIT("Trust region subproblem encountered singular Hessian.");
1558 }
1559
1560 step = - this->lin_solve(K, gradient);
1561
1562 Vector<T, N> const
1563 q = this->lin_solve(L, step);
1564
1565 T const
1566 np = norm(step);
1567
1568 T const
1569 nps = np * np;
1570
1571 T const
1572 nqs = norm_square(q);
1573
1574 T const
1575 lambda_incr = nps * (np - region_size) / nqs / region_size;
1576
1577 lambda += std::max(lambda_incr, 0.0);
1578
1579 }
1580
1581 return step;
1582}
1583
1584
1585//
1586// Trust region subproblem with given gradient/residual.
1587// Exact algorithm, Nocedal 2nd Ed 4.3
1588//
1589template<typename T, Index N>
1592step(Tensor<T, N> const & Hessian, Vector<T, N> const & gradient)
1593{
1594 Index const
1595 dimension = gradient.get_dimension();
1596
1597 Tensor<T, N> const
1598 I = identity<T, N>(dimension);
1599
1601 step(dimension);
1602
1603 // set to Hessian norm to ensure that K is positive definite
1604 T
1605 lambda = norm(Hessian);
1606
1607 for (Index i{0}; i < max_num_iter; ++i) {
1608
1609
1610 Tensor<T, N> const
1611 HTH = dot(transpose(Hessian), Hessian);
1612
1613 Vector<T, N> const
1614 HTr = dot(transpose(Hessian), gradient);
1615
1616 Tensor<T, N> const
1617 K = HTH + lambda * I;
1618
1620 L(Filler::ZEROS);
1621
1622 bool
1623 is_posdef{false};
1624
1625 std::tie(L, is_posdef) = cholesky(K);
1626
1627 if (is_posdef == false) {
1628 MT_ERROR_EXIT("Trust region subproblem encountered singular Hessian.");
1629 }
1630
1631 step = - this->lin_solve(K, HTr);
1632
1633 Vector<T, N> const
1634 q = this->lin_solve(L, step);
1635
1636 T const
1637 np = norm(step);
1638
1639 T const
1640 nps = np * np;
1641
1642 T const
1643 nqs = norm_square(q);
1644
1645 T const
1646 lambda_incr = nps * (np - region_size) / nqs / region_size;
1647
1648 lambda += std::max(lambda_incr, 0.0);
1649
1650 }
1651
1652 return step;
1653}
1654
1655
1656//
1657// Trust region subproblem. Dog-leg algorithm with given gradient/residual.
1658// See pp. 73 - 74, Nocedal 2nd Ed 4.3
1659//
1660template<typename T, Index N>
1663step(Tensor<T, N> const & Hessian, Vector<T, N> const & gradient)
1664{
1665 T const
1666 normg_H = dot(gradient, dot(Hessian, gradient));
1667
1668 if (normg_H < 0.0) {
1669 MT_ERROR_EXIT("Trust region subproblem encountered singular Hessian.");
1670 }
1671
1672 if (normg_H == 0.0) return Vector<T, N>(gradient.get_dimension(), Filler::ZEROS);
1673
1674 T const
1675 normg_squared = dot(gradient, gradient);
1676
1677 Vector<T, N> const
1678 step_minimizer = - normg_squared / normg_H * gradient;
1679
1680 Vector<T, N> const
1681 step_unconstrained = - this->lin_solve(Hessian, gradient);
1682
1683 T const
1684 normg_cubed = norm(gradient) * normg_squared;
1685
1686 T const
1687 tau = std::min(1.0, normg_cubed / (region_size * normg_H));
1688
1690 step{tau * step_minimizer};
1691
1692 if (tau > 1.0) {
1693 step = step_minimizer + (tau - 1.0) * (step_unconstrained - step_minimizer);
1694 }
1695
1696 return step;
1697}
1698
1699
1700//
1701// Trust region subproblem. Dog-leg algorithm with given gradient/residual.
1702// See pp. 73 - 74, Nocedal 2nd Ed 4.3
1703//
1704template<typename T, Index N>
1707step(Tensor<T, N> const & Hessian, Vector<T, N> const & gradient)
1708{
1709 Vector<T, N> const
1710 HTr = dot(transpose(Hessian), gradient);
1711
1712 T const
1713 normHTr_squared = dot(HTr, HTr);
1714
1715 Tensor<T, N> const
1716 HTH = dot(transpose(Hessian), Hessian);
1717
1718 T const
1719 normHTr_HTH = dot(HTr, dot(HTH, HTr));
1720
1721 if (normHTr_HTH < 0.0) {
1722 MT_ERROR_EXIT("Trust region subproblem encountered singular Hessian.");
1723 }
1724
1725 if (normHTr_HTH == 0.0) return Vector<T, N>(gradient.get_dimension(), Filler::ZEROS);
1726
1727 T const
1728 normHTr_cubed = norm(HTr) * normHTr_squared;
1729
1730 T const
1731 tau = std::min(1.0, normHTr_cubed / (region_size * normHTr_HTH));
1732
1733 Vector<T, N> const
1734 step_minimizer = - normHTr_squared / normHTr_HTH * HTr;
1735
1736 Vector<T, N> const
1737 step_unconstrained = - this->lin_solve(HTH, HTr);
1738
1740 step{tau * step_minimizer};
1741
1742 if (tau > 1.0) {
1743 step = step_minimizer + (tau - 1.0) * (step_unconstrained - step_minimizer);
1744 }
1745
1746 return step;
1747}
1748
1749//
1750// Newton line search
1751//
1752template<typename T, Index N>
1753template<typename FN>
1756step(FN & fn, Vector<T, N> const & direction, Vector<T, N> const & soln)
1757{
1758 Index const
1759 dimension = soln.get_dimension();
1760
1762 step(dimension, Filler::ZEROS);
1763
1764 T const
1765 projection_direction = dot(direction, direction);
1766
1767 for (Index i{0}; i < max_num_iter; ++i) {
1768
1769 Vector<T, N> const
1770 soln_next = soln + step;
1771
1772 Vector<T, N> const
1773 gradient_next = fn.gradient(soln_next);
1774
1775 Tensor<T, N> const
1776 Hessian_next = fn.hessian(soln_next);
1777
1778 T const
1779 projection = dot(gradient_next, direction);
1780
1781 T const
1782 contraction = dot(direction, dot(Hessian_next, direction));
1783
1784 T const
1785 step_length = - projection / contraction;
1786
1787 step += step_length * direction;
1788
1789 T const
1790 ls_length2 = step_length * step_length * projection_direction;
1791
1792 bool const
1793 line_search_converged = ls_length2 <= tolerance * tolerance;
1794
1795 if (line_search_converged == true) break;
1796
1797 }
1798
1799 return step;
1800}
1801
1802//
1803// Back-tracking line search
1804// Taken from box I.1 in the appendix of Armero and Perez-Foguet (Jan. 2000)
1805//
1806template<typename T, Index N>
1807template<typename FN>
1810step(FN & fn, Vector<T, N> const & direction, Vector<T, N> const & soln)
1811{
1812 Index const
1813 dimension = soln.get_dimension();
1814
1815 Vector <T, N>
1816 step = direction;
1817
1819 step_line_search(dimension, Filler::ZEROS);
1820
1821 Vector<T, N> const
1822 resid = fn.gradient(soln);
1823
1824 Vector<T, N> const
1825 resid_newton = fn.gradient(soln + step);
1826
1827 Index
1828 line_iter{0};
1829
1830 T const
1831 resid_norm = dot(resid, resid);
1832
1833 T const
1834 resid_newton_norm = dot(resid_newton, resid_newton);
1835
1836 T
1837 resid_line_norm = resid_newton_norm;
1838
1839 for (Index i{0}; i < max_num_iter; i++) {
1840
1841 if (line_iter == max_line_iter) {
1842
1843 alpha = 1.0;
1844 line_iter = 0;
1845 search_parameter += search_increment;
1846 }
1847
1848 if (search_parameter >= 1.0) break;
1849
1850 step_line_search = alpha * step;
1851
1852 Vector<T, N> const
1853 soln_line_search = soln + step_line_search;
1854
1855 Vector<T, N> const
1856 gradient_line_search = fn.gradient(soln_line_search);
1857
1858 T const
1859 resid_line_norm_old = resid_line_norm;
1860
1861 resid_line_norm = dot(gradient_line_search, gradient_line_search);
1862
1863 if (resid_line_norm <= (search_parameter * resid_norm)) {
1864 step = step_line_search;
1865 break;
1866 }
1867
1868 T const
1869 num = 0.25 * alpha * alpha * resid_line_norm_old;
1870
1871 T const
1872 den = resid_line_norm + resid_line_norm_old * (0.5 * alpha - 1.0);
1873
1874 T const
1875 den_tol = 1.0e-10;
1876
1877 bool const
1878 is_zero_den = (std::fabs(den) < den_tol);
1879
1880 alpha = is_zero_den == true ? 0.5 * alpha : max(0.5 * alpha, num / den);
1881
1882 line_iter++;
1883
1884 } //Index i
1885
1886 return step;
1887}
1888
1889//
1890//
1891//
1892template<typename FN, typename T, Index N>
1893void
1895initialize(FN &, Vector<T, N> const &, Vector<T, N> const &)
1896{
1897 return;
1898}
1899
1900//
1901// Plain Newton step.
1902//
1903template<typename FN, typename T, Index N>
1906step(FN & fn, Vector<T, N> const & soln, Vector<T, N> const & resi)
1907{
1908 Tensor<T, N> const
1909 Hessian = fn.hessian(soln);
1910
1911 Vector<T, N> const
1912 step = - this->lin_solve(Hessian, resi);
1913
1914 return step;
1915}
1916
1917
1918//
1919//
1920//
1921template<typename FN, typename T, Index N>
1922void
1924initialize(FN &, Vector<T, N> const &, Vector<T, N> const &)
1925{
1926 return;
1927}
1928
1929//
1930// Plain Newton step with line search
1931//
1932template<typename FN, typename T, Index N>
1935step(FN & fn, Vector<T, N> const & soln, Vector<T, N> const & resi)
1936{
1937 Tensor<T, N> const
1938 Hessian = fn.hessian(soln);
1939
1940 Vector<T, N> const
1941 step = - this->lin_solve(Hessian, resi);
1942
1943 // Newton back-tracking line search.
1945 newton_backtrack_ls;
1946
1947 Vector<T, N> const
1948 ls_step = newton_backtrack_ls.step(fn, step, soln);
1949
1950 return ls_step;
1951}
1952
1953//
1954//
1955//
1956template<typename FN, typename T, Index N>
1957void
1959initialize(FN &, Vector<T, N> const &, Vector<T, N> const &)
1960{
1961 region_size = initial_region_size;
1962
1963 return;
1964}
1965
1966//
1967// Trust Region method. See Nocedal's algorithm 11.5.
1968//
1969template<typename FN, typename T, Index N>
1972step(FN & fn, Vector<T, N> const & soln, Vector<T, N> const & resi)
1973{
1974 Tensor<T, N> const
1975 Hessian = fn.hessian(soln);
1976
1977 // Compute full Newton step first.
1979 step = - this->lin_solve(Hessian, resi);
1980
1981 T const
1982 norm_step = minitensor::norm(step);
1983
1984 // Take full Newton step if inside trust region.
1985 if (norm_step < region_size) return step;
1986
1987 // Trust region subproblem. Exact algorithm, Nocedal 2nd Ed 4.3
1989 tr_exact;
1990
1991 tr_exact.region_size = region_size;
1992
1993 step = tr_exact.step(Hessian, resi);
1994
1995 Vector<T, N> const
1996 soln_next = soln + step;
1997
1998 Vector<T, N> const
1999 resi_next = fn.gradient(soln_next);
2000
2001 // Compute reduction factor \rho_k in Nocedal's algorithm 11.5
2002 T const
2003 nr = norm_square(resi);
2004
2005 T const
2006 nrp = norm_square(resi_next);
2007
2008 T const
2009 nrKp = norm_square(resi + dot(Hessian, step));
2010
2011 T const
2012 reduction = (nr - nrp) / (nr - nrKp);
2013
2014 // Determine whether the trust region should be increased, decreased
2015 // or left the same.
2016 T const
2017 computed_size = norm(step);
2018
2019 if (reduction < 0.25) {
2020
2021 region_size = 0.25 * computed_size;
2022
2023 } else {
2024
2025 bool const
2026 increase_region_size = reduction > 0.75;
2027
2028 if (increase_region_size == true) {
2029 region_size = std::min(2.0 * region_size, max_region_size);
2030 }
2031
2032 }
2033
2034 if (reduction <= min_reduction) {
2035 step.fill(Filler::ZEROS);
2036 }
2037
2038 return step;
2039}
2040
2041//
2042//
2043//
2044template<typename FN, typename T, Index N>
2045void
2047initialize(FN & fn, Vector<T, N> const & soln, Vector<T, N> const & gradient)
2048{
2049 Tensor<T, N> const
2050 Hessian = fn.hessian(soln);
2051
2052 precon_resi = - this->lin_solve(Hessian, gradient);
2053
2054 search_direction = precon_resi;
2055
2056 projection_new = - dot(gradient, search_direction);
2057
2058 restart_directions_counter = 0;
2059
2060 return;
2061}
2062
2063//
2064// Conjugate Gradient Method step.
2065// For now the Gram-Schmidt method is fixed to Polak-Ribiere
2066// and preconditioning with the Hessian.
2067// This is taken from J.R. Shewchuck "painless" conjugate gradient
2068// manuscript that is all over the place on the net.
2069//
2070template<typename FN, typename T, Index N>
2073step(FN & fn, Vector<T, N> const & soln, Vector<T, N> const &)
2074{
2075 // Newton line search.
2077 newton_ls;
2078
2079 Vector<T, N> const
2080 step = newton_ls.step(fn, search_direction, soln);
2081
2082 Vector<T, N> const
2083 soln_next = soln + step;
2084
2085 Vector<T, N> const
2086 gradient_next = fn.gradient(soln_next);
2087
2088 T const
2089 projection_old = projection_new;
2090
2091 T const
2092 projection_mid = - dot(gradient_next, precon_resi);
2093
2094 Tensor<T, N> const
2095 Hessian = fn.hessian(soln_next);
2096
2097 precon_resi = - this->lin_solve(Hessian, gradient_next);
2098
2099 projection_new = - dot(gradient_next, precon_resi);
2100
2101 T const
2102 gram_schmidt_factor = (projection_new - projection_mid) / projection_old;
2103
2104 ++restart_directions_counter;
2105
2106 bool const
2107 rewind = restart_directions_counter == restart_directions_interval;
2108
2109 bool const
2110 bad_directions = gram_schmidt_factor <= 0.0;
2111
2112 bool const
2113 restart_directions = rewind || bad_directions;
2114
2115 if (restart_directions == true) {
2116
2117 search_direction = precon_resi;
2118 restart_directions_counter = 0;
2119
2120 } else {
2121
2122 search_direction = precon_resi + gram_schmidt_factor * search_direction;
2123
2124 }
2125
2126 return step;
2127}
2128
2129//
2130//
2131//
2132template<typename FN, typename T, Index N>
2133void
2135initialize(FN &, Vector<T, N> const &, Vector<T, N> const &)
2136{
2137 return;
2138}
2139
2140//
2141// Line Search Newton-like method. See Nocedal's algorithm 11.4.
2142//
2143template<typename FN, typename T, Index N>
2146step(FN & fn, Vector<T, N> const & soln, Vector<T, N> const & gradient)
2147{
2148 Index const
2149 dimension = soln.get_dimension();
2150
2151 Tensor<T, N> const
2152 Hessian = fn.hessian(soln);
2153
2155 step(dimension);
2156
2157 bool const
2158 bad_hessian = inv_cond(Hessian) * hessian_cond_tol < 1.0;
2159
2160 // Regularize Hessian if it is bad.
2161 if (bad_hessian == true) {
2162
2163 // Trust region subproblem. Exact algorithm, Nocedal 2nd Ed 4.3
2165 tr_exact;
2166
2167 tr_exact.region_size = step_length;
2168
2169 step = tr_exact.step(Hessian, gradient);
2170 } else {
2171
2172 // Standard Newton step
2173 step = - this->lin_solve(Hessian, gradient);
2174
2175 }
2176
2178 newton_ls;
2179
2180 Vector<T, N> const
2181 ls_step = newton_ls.step(fn, step, soln);
2182
2183 return ls_step;
2184}
2185
2186//
2187//
2188//
2192template<typename FN, typename T, Index N>
2193std::unique_ptr<StepBase<FN, T, N>>
2195{
2196 using STUP = std::unique_ptr<StepBase<FN, T, N>>;
2197
2198 switch (step_type) {
2199
2200 default:
2201 MT_ERROR_EXIT("ERROR: Unknown step method type.");
2202 break;
2203
2204 case StepType::NEWTON:
2205 return STUP(new NewtonStep<FN, T, N>());
2206 break;
2207
2209 return STUP(new TrustRegionStep<FN, T, N>());
2210 break;
2211
2212 case StepType::CG:
2213 return STUP(new ConjugateGradientStep<FN, T, N>());
2214 break;
2215
2217 return STUP(new LineSearchRegularizedStep<FN, T, N>());
2218 break;
2219
2221 return STUP(new NewtonWithLineSearchStep<FN, T, N>());
2222 break;
2223 }
2224
2225 return STUP(nullptr);
2226}
2227
2229} // namespace minitensor
2230
2231#endif // MiniTensor_Solvers_h
#define MT_ERROR_EXIT(...)
KOKKOS_INLINE_FUNCTION Index get_dimension() const
KOKKOS_INLINE_FUNCTION void fill(Filler const value)
KOKKOS_INLINE_FUNCTION Vector< typename Promote< S, T >::type, M > dot(Matrix< T, M, N > const &A, Vector< S, N > const &u)
KOKKOS_INLINE_FUNCTION Matrix< T, M, N > transpose(Matrix< T, N, M > const &A)
KOKKOS_INLINE_FUNCTION T norm_square(Vector< T, N > const &u)
T inv_cond(Tensor< T, N > const &A)
std::pair< Tensor< T, N >, bool > cholesky(Tensor< T, N > const &A)
RHS solve(Tensor< T, N > const &A, RHS const &b, PreconditionerType const pt=PreconditionerType::IDENTITY)
KOKKOS_INLINE_FUNCTION T norm(Tensor< T, N > const &A)
Vector< T, N > gradient(FunctionDerived &f, Vector< T, N > const &x)
PreconditionerType preconditioner_type
Tensor< T, N > hessian(FunctionDerived &f, Vector< T, N > const &x)
virtual Vector< T, N > step(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)
static constexpr char const *const NAME
Vector< T, N > step(Tensor< T, N > const &Hessian, Vector< T, N > const &gradient)
virtual Vector< T, N > step(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)
void set_failure_message(char const *const msg=nullptr)
T value(FunctionDerived &f, Vector< T, N > const &x)
virtual char const * name()=0
Vector< T, N > lin_solve(Tensor< T, N > const &A, Vector< T, N > const &b)
Vector< T, N > step(Tensor< T, N > const &Hessian, Vector< T, N > const &gradient)
virtual void initialize(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)
static constexpr char const *const NAME
Vector< T, N > step(FN &fn, Vector< T, N > const &direction, Vector< T, N > const &soln)
Bounds(Vector< T, N > const &l, Vector< T, N > const &u)
Vector< T, NC > value(ConstraintDerived &c, Vector< T, N > const &x)
virtual Vector< T, N > step(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)
void printReport(std::ostream &os)
void set_failed(char const *const msg=nullptr)
void recordFinals(FN &fn, Vector< T, N > const &x)
Vector< T, N > step(Tensor< T, N > const &Hessian, Vector< T, N > const &gradient)
Vector< T, N > residual(FunctionDerived &f, Vector< T, N > const &x)
virtual char const * name()
static constexpr char const *const NAME
static constexpr char const *const NAME
void updateDivergenceCriterion(T const fn_value)
std::unique_ptr< StepBase< FN, T, N > > stepFactory(StepType step_type)
virtual Vector< T, N > step(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)
virtual void initialize(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)
Vector< T, N > step(Tensor< T, N > const &Hessian, Vector< T, N > const &gradient)
Vector< T, N > lin_solve(Tensor< T, N > const &A, Vector< T, N > const &b)
Matrix< T, NC, NV > gradient(ConstraintDerived &c, Vector< T, N > const &x)
virtual Vector< T, N > step(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)
virtual void initialize(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)
void solve(STEP &step_method, FN &fn, Vector< T, N > &x)
virtual void initialize(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)
virtual void initialize(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)=0
virtual Vector< T, N > step(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)=0
Vector< T, N > step(FN &fn, Vector< T, N > const &direction, Vector< T, N > const &soln)
static constexpr Index DIMENSION
char const * failure_message
Keep a message to inform what went wrong above.
static constexpr char const *const NAME
virtual void initialize(FN &fn, Vector< T, N > const &x, Vector< T, N > const &r)
Sacado::Fad::SLFad< T, N > FAD
The Fad type to use.
virtual char const * name()
void updateConvergenceCriterion(T const abs_error)
uint32_t Index
Indexing type.
KOKKOS_INLINE_FUNCTION Sacado::ScalarType< T >::type tau()
KOKKOS_INLINE_FUNCTION T max(T const &a, T const &b)