User Guide
newton.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2023 Blue Brain Project, EPFL.
3  * See the top-level LICENSE file for details.
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #pragma once
9 
10 /**
11  * \dir
12  * \brief Newton solver implementations
13  *
14  * \file
15  * \brief Implementation of Newton method for solving system of non-linear equations
16  */
17 
18 #include <crout/crout.hpp>
19 
20 #include <Eigen/Dense>
21 #include <Eigen/LU>
22 
23 namespace nmodl {
24 /// newton solver implementations
25 namespace newton {
26 
27 /**
28  * @defgroup solver Solver Implementation
29  * @brief Solver implementation details
30  *
31  * Implementation of Newton method for solving system of non-linear equations using Eigen
32  * - newton::newton_solver with user, e.g. SymPy, provided Jacobian
33  *
34  * @{
35  */
36 
37 static constexpr int MAX_ITER = 50;
38 static constexpr double EPS = 1e-13;
39 
40 template <int N>
41 EIGEN_DEVICE_FUNC bool is_converged(const Eigen::Matrix<double, N, 1>& X,
42  const Eigen::Matrix<double, N, N>& J,
43  const Eigen::Matrix<double, N, 1>& F,
44  double eps) {
45  bool converged = true;
46  double square_eps = eps * eps;
47  for (Eigen::Index i = 0; i < N; ++i) {
48  double square_error = 0.0;
49  for (Eigen::Index j = 0; j < N; ++j) {
50  double JX = J(i, j) * X(j);
51  square_error += JX * JX;
52  }
53 
54  if (F(i) * F(i) > square_eps * square_error) {
55  converged = false;
56 // The NVHPC is buggy and wont allow us to short-circuit.
57 #ifndef __NVCOMPILER
58  return converged;
59 #endif
60  }
61  }
62  return converged;
63 }
64 
65 /**
66  * \brief Newton method with user-provided Jacobian
67  *
68  * Newton method with user-provided Jacobian: given initial vector X and a
69  * functor that calculates `F(X)`, `J(X)` where `J(X)` is the Jacobian of `F(X)`,
70  * solves for \f$F(X) = 0\f$, starting with initial value of `X` by iterating:
71  *
72  * \f[
73  * X_{n+1} = X_n - J(X_n)^{-1} F(X_n)
74  * \f]
75  * when \f$|F|^2 < eps^2\f$, solution has converged.
76  *
77  * @return number of iterations (-1 if failed to converge)
78  */
79 template <int N, typename FUNC>
80 EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, N, 1>& X,
81  FUNC functor,
82  double eps = EPS,
83  int max_iter = MAX_ITER) {
84  // If finite differences are needed, this is stores the stepwidth.
85  Eigen::Matrix<double, N, 1> dX;
86  // Vector to store result of function F(X):
87  Eigen::Matrix<double, N, 1> F;
88  // Matrix to store Jacobian of F(X):
89  Eigen::Matrix<double, N, N> J;
90  // Solver iteration count:
91  int iter = -1;
92  while (++iter < max_iter) {
93  // calculate F, J from X using user-supplied functor
94  functor(X, dX, F, J);
95  if (is_converged(X, J, F, eps)) {
96  return iter;
97  }
98  // In Eigen the default storage order is ColMajor.
99  // Crout's implementation requires matrices stored in RowMajor order (C-style arrays).
100  // Therefore, the transposeInPlace is critical such that the data() method to give the rows
101  // instead of the columns.
102  if (!J.IsRowMajor) {
103  J.transposeInPlace();
104  }
105  Eigen::Matrix<int, N, 1> pivot;
106  Eigen::Matrix<double, N, 1> rowmax;
107  // Check if J is singular
108  if (nmodl::crout::Crout<double>(N, J.data(), pivot.data(), rowmax.data()) < 0) {
109  return -1;
110  }
111  Eigen::Matrix<double, N, 1> X_solve;
112  nmodl::crout::solveCrout<double>(N, J.data(), F.data(), X_solve.data(), pivot.data());
113  X -= X_solve;
114  }
115  // If we fail to converge after max_iter iterations, return -1
116  return -1;
117 }
118 
119 /**
120  * Newton method template specializations for \f$N <= 4\f$ Use explicit inverse
121  * of `F` instead of LU decomposition. This is faster, as there is no pivoting
122  * and therefore no branches, but it is not numerically safe for \f$N > 4\f$.
123  */
124 
125 template <typename FUNC, int N>
126 EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix<double, N, 1>& X,
127  FUNC functor,
128  double eps,
129  int max_iter) {
130  bool invertible;
131  Eigen::Matrix<double, N, 1> F;
132  Eigen::Matrix<double, N, 1> dX;
133  Eigen::Matrix<double, N, N> J, J_inv;
134  int iter = -1;
135  while (++iter < max_iter) {
136  functor(X, dX, F, J);
137  if (is_converged(X, J, F, eps)) {
138  return iter;
139  }
140  // The inverse can be called from within OpenACC regions without any issue, as opposed to
141  // Eigen::PartialPivLU.
142  J.computeInverseWithCheck(J_inv, invertible);
143  if (invertible) {
144  X -= J_inv * F;
145  } else {
146  return -1;
147  }
148  }
149  return -1;
150 }
151 
152 template <typename FUNC>
153 EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 1, 1>& X,
154  FUNC functor,
155  double eps = EPS,
156  int max_iter = MAX_ITER) {
157  return newton_solver_small_N<FUNC, 1>(X, functor, eps, max_iter);
158 }
159 
160 template <typename FUNC>
161 EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 2, 1>& X,
162  FUNC functor,
163  double eps = EPS,
164  int max_iter = MAX_ITER) {
165  return newton_solver_small_N<FUNC, 2>(X, functor, eps, max_iter);
166 }
167 
168 template <typename FUNC>
169 EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 3, 1>& X,
170  FUNC functor,
171  double eps = EPS,
172  int max_iter = MAX_ITER) {
173  return newton_solver_small_N<FUNC, 3>(X, functor, eps, max_iter);
174 }
175 
176 template <typename FUNC>
177 EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 4, 1>& X,
178  FUNC functor,
179  double eps = EPS,
180  int max_iter = MAX_ITER) {
181  return newton_solver_small_N<FUNC, 4>(X, functor, eps, max_iter);
182 }
183 
184 /** @} */ // end of solver
185 
186 } // namespace newton
187 } // namespace nmodl
nmodl::newton::newton_solver_small_N
EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix< double, N, 1 > &X, FUNC functor, double eps, int max_iter)
Newton method template specializations for Use explicit inverse of F instead of LU decomposition.
Definition: newton.hpp:126
nmodl::newton::is_converged
EIGEN_DEVICE_FUNC bool is_converged(const Eigen::Matrix< double, N, 1 > &X, const Eigen::Matrix< double, N, N > &J, const Eigen::Matrix< double, N, 1 > &F, double eps)
Definition: newton.hpp:41
nmodl::newton::MAX_ITER
static constexpr int MAX_ITER
Definition: newton.hpp:37
nmodl
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
nmodl::newton::EPS
static constexpr double EPS
Definition: newton.hpp:38
crout.hpp
Implementation of Crout matrix decomposition (LU decomposition) followed by Forward/Backward substitu...
nmodl::newton::newton_solver
EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix< double, N, 1 > &X, FUNC functor, double eps=EPS, int max_iter=MAX_ITER)
Newton method with user-provided Jacobian.
Definition: newton.hpp:80