Thermal-FIST  1.3
Package for hadron resonance gas model applications
Broyden.cpp
Go to the documentation of this file.
1 /*
2  * Thermal-FIST package
3  *
4  * Copyright (c) 2018-2019 Volodymyr Vovchenko
5  *
6  * GNU General Public License (GPLv3 or later)
7  */
8 #include "HRGBase/Broyden.h"
9 
10 #include <stdio.h>
11 #include <cmath>
12 
13 #include <Eigen/Dense>
14 
15 
16 using namespace Eigen;
17 
18 using namespace std;
19 
20 namespace thermalfist {
21 
22  const double BroydenJacobian::EPS = 1.0E-6;
23  const double Broyden::TOL = 1.0E-10;
24  const int Broyden::MAX_ITERS = 200;
25 
26 
27  std::vector<double> BroydenJacobian::Jacobian(const std::vector<double>& x)
28  {
29  if (m_Equations == NULL) {
30  printf("**ERROR** BroydenJacobian::Jacobian: Equations to solve not specified!\n");
31  exit(1);
32  }
33 
34  if (m_Equations->Dimension() != static_cast<int>(x.size())) {
35  printf("**ERROR** BroydenJacobian::Jacobian: Equations dimension does not match that of input!\n");
36  exit(1);
37  }
38 
39  int N = m_Equations->Dimension();
40 
41  std::vector<double> h = x;
42 
43  std::vector< std::vector<double> > xh(N);
44 
45  for (size_t i = 0; i < x.size(); ++i) {
46  h[i] = m_dx*abs(h[i]);
47  if (h[i] == 0.0) h[i] = m_dx;
48  if (h[i] < 1.e-10) h[i] = 1.e-10;
49  //h[i] = max(m_dx, h[i]);
50  }
51 
52  for (size_t i = 0; i < x.size(); ++i) {
53  xh[i].resize(N);
54  for (size_t j = 0; j < x.size(); ++j) {
55  if (i != j)
56  xh[i][j] = x[j];
57  else
58  xh[i][j] = x[j] + h[j];
59  }
60  }
61 
62  std::vector<double> Jac(N*N);
63 
64  std::vector<double> fx = m_Equations->Equations(x);
65 
66  for (int j = 0; j < N; ++j) {
67  std::vector<double> dfdxj = m_Equations->Equations(xh[j]);
68  for (size_t i = 0; i < dfdxj.size(); ++i) {
69  dfdxj[i] = (dfdxj[i] - fx[i]) / h[j];
70  Jac[i*N + j] = dfdxj[i];
71  }
72  }
73 
74  return Jac;
75  }
76 
77  std::vector<double> Broyden::Solve(const std::vector<double> &x0, BroydenSolutionCriterium *solcrit, int max_iterations)
78  {
79  if (m_Equations == NULL) {
80  printf("**ERROR** Broyden::Solve: Equations to solve not specified!\n");
81  exit(1);
82  }
83 
84  m_MaxIterations = max_iterations;
85 
86  BroydenSolutionCriterium *SolutionCriterium = solcrit;
87  bool UseDefaultSolutionCriterium = false;
88  if (SolutionCriterium == NULL) {
89  SolutionCriterium = new BroydenSolutionCriterium(TOL);
90  UseDefaultSolutionCriterium = true;
91  }
92 
93  BroydenJacobian *JacobianInUse = m_Jacobian;
94  bool UseDefaultJacobian = false;
95  if (JacobianInUse == NULL) {
96  JacobianInUse = new BroydenJacobian(m_Equations);
97  UseDefaultJacobian = true;
98  }
99  m_Iterations = 0;
100  double &maxdiff = m_MaxDifference;
101  int N = m_Equations->Dimension();
102  std::vector<double> xcur = x0, tmpvec, xdeltavec = x0;
103  VectorXd xold(N), xnew(N), xdelta(N);
104  VectorXd fold(N), fnew(N), fdelta(N);
105 
106  xold = VectorXd::Map(&xcur[0], xcur.size());
107 
108  MatrixXd Jac = Eigen::Map< Matrix<double, Dynamic, Dynamic, RowMajor> >(&JacobianInUse->Jacobian(xcur)[0], N, N);
109 
110  if (Jac.determinant() == 0.0)
111  {
112  printf("**WARNING** Singular Jacobian in Broyden::Solve\n");
113  return xcur;
114  }
115 
116  MatrixXd Jinv = Jac.inverse();
117  tmpvec = m_Equations->Equations(xcur);
118  fold = VectorXd::Map(&tmpvec[0], tmpvec.size());
119 
120  for (m_Iterations = 1; m_Iterations < max_iterations; ++m_Iterations) {
121  xnew = xold - Jinv * fold;
122 
123  VectorXd::Map(&xcur[0], xcur.size()) = xnew;
124 
125  tmpvec = m_Equations->Equations(xcur);
126  fnew = VectorXd::Map(&tmpvec[0], tmpvec.size());
127 
128 
129  maxdiff = 0.;
130  for (size_t i = 0; i < xcur.size(); ++i) {
131  maxdiff = std::max(maxdiff, fabs(fnew[i]));
132  }
133 
134  xdelta = xnew - xold;
135  fdelta = fnew - fold;
136 
137  VectorXd::Map(&xdeltavec[0], xdeltavec.size()) = xdelta;
138 
139  if (SolutionCriterium->IsSolved(xcur, tmpvec, xdeltavec))
140  break;
141  //if (maxdiff < relative_error) break;
142 
143  if (!m_UseNewton) // Use Broyden's method
144  {
145 
146  double norm = 0.;
147  for (int i = 0; i < N; ++i)
148  for (int j = 0; j < N; ++j)
149  norm += xdelta[i] * Jinv(i, j) * fdelta[j];
150  VectorXd p1(N);
151  p1 = (xdelta - Jinv * fdelta);
152  for (int i = 0; i < N; ++i) p1[i] *= 1. / norm;
153  Jinv = Jinv + (p1 * xdelta.transpose()) * Jinv;
154  }
155  else // Use Newton's method
156  {
157  Jac = Eigen::Map< Matrix<double, Dynamic, Dynamic, RowMajor> >(&JacobianInUse->Jacobian(xcur)[0], N, N);
158  Jinv = Jac.inverse();
159  }
160 
161  xold = xnew;
162  fold = fnew;
163  }
164 
165  if (UseDefaultSolutionCriterium) {
166  delete SolutionCriterium;
167  SolutionCriterium = NULL;
168  }
169  if (UseDefaultJacobian) {
170  delete JacobianInUse;
171  JacobianInUse = NULL;
172  }
173  return xcur;
174  }
175 
176  bool Broyden::BroydenSolutionCriterium::IsSolved(const std::vector<double>& x, const std::vector<double>& f, const std::vector<double>& xdelta) const
177  {
178  double maxdiff = 0., maxdiffxdelta = 0.;
179  for (size_t i = 0; i < x.size(); ++i) {
180  maxdiff = std::max(maxdiff, fabs(f[i]));
181  maxdiffxdelta = std::max(maxdiffxdelta, fabs(xdelta[i]));
182  }
183  return (maxdiff < m_MaximumError) || maxdiffxdelta == 0.;
184  }
185 
186 } // namespace thermalfist
virtual std::vector< double > Jacobian(const std::vector< double > &x)
Evaluates the Jacobian for given values of the variables.
Definition: Broyden.cpp:27
Sub-class where it is determined whether the required accuracy is achieved in the Broyden&#39;s method...
Definition: Broyden.h:144
Class which implements calculation of the Jacobian needed for the Broyden&#39;s method.
Definition: Broyden.h:76
Implementation of the generic Broyden&#39;s method routines.
The main namespace where all classes and functions of the Thermal-FIST library reside.
virtual bool IsSolved(const std::vector< double > &x, const std::vector< double > &f, const std::vector< double > &xdelta=std::vector< double >()) const
Definition: Broyden.cpp:176