MUQ  0.4.3
MonotoneRegression.cpp
Go to the documentation of this file.
1 /***
2 ## Overview:
3 The goal of this example is to fit a monotone function to stress-strain
4 data obtained during the 2017 Sandia Fracture Challenge. For our area of
5 interest, the stress should be a monotonically increasing function of the
6 strain.
7 
8 The MonotoneExpansion class provides a way of characterizing
9 monotone functions and can be fit to data in a least squares sense using
10 the Gauss-Newton algorithm, which is implemented in the `FitData` function
11 below.
12 */
13 
16 
19 
22 
23 #include <Eigen/Dense>
24 
25 using namespace muq::Approximation;
26 using namespace muq::Utilities;
27 
29 std::pair<Eigen::VectorXd, Eigen::VectorXd> ReadData()
30 {
31  auto f = muq::Utilities::OpenFile("data/LTA01.h5");
32 
33  std::cout << "Reading strain data..." << std::endl;
34  std::cout << " Units: " << std::string(f["/Strain"].attrs["Units"]) << std::endl;
35  std::cout << " Size: " << f["/Strain"].rows() << std::endl;
36 
37  Eigen::VectorXd strain = f["/Strain"];
38 
39  std::cout << "Reading stress data..." << std::endl;
40  std::cout << " Units: " << std::string(f["/Stress"].attrs["Units"]) << std::endl;
41  std::cout << " Size: " << f["/Stress"].rows() << std::endl;
42 
43  Eigen::VectorXd stress = f["/Stress"];
44 
45  return std::make_pair(strain, stress);
46 };
47 
48 
49 std::shared_ptr<MonotoneExpansion> SetupExpansion(unsigned order)
50 {
51  auto poly = std::make_shared<Legendre>();
52  std::vector<std::shared_ptr<IndexedScalarBasis>> bases(1, poly);
53  std::shared_ptr<MultiIndexSet> multis = MultiIndexFactory::CreateTotalOrder(1, order);
54 
55  Eigen::MatrixXd polyCoeffs = Eigen::MatrixXd::Zero(1,multis->Size());
56  polyCoeffs(0,1) = 500.0; // add an initial linear trend
57 
58  auto polyBase = std::make_shared<BasisExpansion>(bases, multis, polyCoeffs);
59  auto expansion = std::make_shared<MonotoneExpansion>(polyBase);
60 
61  return expansion;
62 }
63 
68 void FitData(Eigen::VectorXd const& x,
69  Eigen::VectorXd const& y,
70  std::shared_ptr<MonotoneExpansion> expansion)
71 {
72  Eigen::VectorXd coeffs = expansion->GetCoeffs();
73 
74  Eigen::VectorXd preds(x.size());
75  Eigen::VectorXd newPreds(x.size());
76  Eigen::VectorXd newCoeffs;
77 
78  Eigen::VectorXd resid, newResid, step, xslice;
79  Eigen::MatrixXd jac(x.size(), coeffs.size());
80 
81  const int maxLineIts = 10;
82  const int maxIts = 20;
83 
84  // Use the current monotone parameterization to make predictions at every point
85  for(int k=0; k<x.size(); ++k){
86  xslice = x.segment(k,1);
87  preds(k) = boost::any_cast<Eigen::VectorXd>(expansion->Evaluate(xslice,coeffs).at(0))(0);
88  }
89 
90  resid = y-preds;
91  double sse = resid.squaredNorm();
92 
93  for(int i=0; i<maxIts; ++i){
94 
95  // Compute the jacobian at the current point
96  for(int k=0; k<x.size(); ++k){
97  xslice = x.segment(k,1);
98  jac.row(k) = boost::any_cast<Eigen::MatrixXd>(expansion->Jacobian(1,0,xslice,coeffs));
99  }
100 
101  // Compute the Gauss-Newton step
102  step = jac.colPivHouseholderQr().solve(resid);
103  newCoeffs = coeffs + step;
104 
105  // Use the current monotone parameterization to make predictions at every point
106  for(int k=0; k<x.size(); ++k){
107  xslice = x.segment(k,1);
108  newPreds(k) = boost::any_cast<Eigen::VectorXd>(expansion->Evaluate(xslice,newCoeffs).at(0))(0);
109  }
110 
111  newResid = y-newPreds;
112  double newsse = newResid.squaredNorm();
113 
114  // Backtracing line search to guarantee a sufficient descent
115  int lineIt = 0;
116  while((newsse > sse - 1e-7)&&(lineIt<maxLineIts)){
117  step *= 0.5;
118  newCoeffs = coeffs + step;
119 
120  // Compute the residuals at the new point
121  for(int k=0; k<x.size(); ++k){
122  xslice = x.segment(k,1);
123  newPreds(k) = boost::any_cast<Eigen::VectorXd>(expansion->Evaluate(xslice,newCoeffs).at(0))(0);
124  }
125 
126  newResid = y-newPreds;
127  newsse = newResid.squaredNorm();
128  }
129 
130  if(lineIt == maxLineIts){
131  std::cout << "WARNING: Line search failed, terminating Gauss-Newton optimizer." << std::endl;
132  return;
133  }
134 
135  // The line search was successful, so update the coefficients and residuals
136  coeffs = newCoeffs;
137  preds = newPreds;
138  sse = newsse;
139  resid = newResid;
140 
141  std::cout << "Iteration " << i << ", SSE = "<< sse << std::endl;
142  }
143 }
144 
145 void WriteResults(Eigen::VectorXd const& x,
146  std::shared_ptr<MonotoneExpansion> expansion)
147 {
148  Eigen::VectorXd preds(x.size());
149  Eigen::VectorXd xslice;
150 
151  for(int k=0; k<x.size(); ++k){
152  xslice = x.segment(k,1);
153  preds(k) = boost::any_cast<Eigen::VectorXd>(expansion->Evaluate(xslice).at(0))(0);
154  }
155 
156  auto f = muq::Utilities::OpenFile("results/StressPredictions.h5");
157  f["/Strain"] = x;
158  f["/Stress"] = preds;
159 }
160 
161 
162 int main()
163 {
164 
165  Eigen::VectorXd strain, stress;
166  std::tie(strain, stress) = ReadData();
167 
168  unsigned polyOrder = 7;
169  auto expansion = SetupExpansion(polyOrder);
170 
171  FitData(strain, stress, expansion);
172 
173  WriteResults(strain, expansion);
174 
175  return 0;
176 };
void FitData(Eigen::VectorXd const &x, Eigen::VectorXd const &y, std::shared_ptr< MonotoneExpansion > expansion)
std::pair< Eigen::VectorXd, Eigen::VectorXd > ReadData()
std::shared_ptr< MonotoneExpansion > SetupExpansion(unsigned order)
void WriteResults(Eigen::VectorXd const &x, std::shared_ptr< MonotoneExpansion > expansion)
int main()
H5Object OpenFile(std::string const &filename)
Open an HDF5 file and return the root object.
Definition: H5Object.cpp:321