MUQ  0.4.3
Regression.cpp
Go to the documentation of this file.
2 
3 #include <Eigen/Cholesky>
4 #include <Eigen/Eigenvalues>
5 
7 
9 
11 
12 namespace pt = boost::property_tree;
13 using namespace muq::Utilities;
14 using namespace muq::Modeling;
15 using namespace muq::Optimization;
16 using namespace muq::Approximation;
17 
18 Regression::Regression(pt::ptree const& pt) : WorkPiece(), order(pt.get<unsigned int>("Order")), inputDim(pt.get<unsigned int>("InputSize")), alpha(std::fmin(pt.get<double>("MaxPoisednessRadius", 1.0), 1.0)) {
19  assert(alpha>0.0);
20  poly = IndexedScalarBasis::Construct(pt.get<std::string>("PolynomialBasis", "Legendre"));
21 
22  // initalize the multi-index
23  const std::string type = pt.get<std::string>("ExpansionType", "TotalOrder");
24  if( type=="TotalOrder" ) {
25  multi = MultiIndexFactory::CreateTotalOrder(inputDim, order);
26  } else if( type=="Hyperbolic" ) {
27  const double q = pt.get<double>("NormScale", 1.0);
28  multi = MultiIndexFactory::CreateHyperbolic(inputDim, order, q);
29  } else if( type=="Diagonal" ) {
30  multi = std::make_shared<MultiIndexSet>(inputDim);
31 
32  // add a constant term
33  std::shared_ptr<MultiIndex> constantIndex = std::make_shared<MultiIndex>(inputDim);
34  multi->AddActive(constantIndex);
35 
36  // add the higher order terms
37  for( unsigned int i=1; i<=order; ++i ) {
38  for( unsigned int d=0; d<inputDim; ++d ) {
39  auto singleIndex = MultiIndexFactory::CreateSingleTerm(inputDim, d, i);
40  multi->AddActive(singleIndex);
41  }
42  }
43  } else {
44  std::cerr << std::endl;
45  std::cerr << "ERROR: invalid polynomial expansion type in Regression.cpp" << std::endl;
46  std::cerr << "\tChoose from: 'TotalOrder', 'Hyperbolic', or 'Diagonal'" << std::endl;
47  std::cerr << std::endl;
48  assert(false);
49  }
50 
51  // set algorithm parameters for poisedness optimization with default values
52  optPt.put("Ftol.AbsoluteTolerance", pt.get<double>("PoisednessConstant.Ftol.AbsoluteTolerance", 1.0e-8));
53  optPt.put("Ftol.RelativeTolerance", pt.get<double>("PoisednessConstant.Ftol.RelativeTolerance", 1.0e-8));
54  optPt.put("Xtol.AbsoluteTolerance", pt.get<double>("PoisednessConstant.Xtol.AbsoluteTolerance", 1.0e-8));
55  optPt.put("Xtol.RelativeTolerance", pt.get<double>("PoisednessConstant.Xtol.RelativeTolerance", 1.0e-8));
56  optPt.put("ConstraintTolerance", pt.get<double>("PoisednessConstant.ConstraintTolerance", 1.0e-8));
57  optPt.put("MaxEvaluations", pt.get<unsigned int>("PoisednessConstant.MaxEvaluations", 1000));
58  optPt.put("Algorithm", pt.get<std::string>("PoisednessConstant.Algorithm", "MMA"));
59 }
60 
62  // if there are no points ... just return with an empty outputs
63  if(inputs.size()==0) { return; }
64 
65  // make sure we can compute the Vandermonde matrix
66  assert(multi);
67 
68  std::vector<Eigen::VectorXd> centered(inputs.size());
69  for( unsigned int i=0; i<inputs.size(); ++i ) {
70  // center and normalize
71  centered[i] = (boost::any_cast<Eigen::VectorXd const>(inputs[i])-currentCenter).array()*currentRadius.inverse();
72  }
73 
74  // get the Vandermonde matrix of the inputs
75  const Eigen::MatrixXd vand = VandermondeMatrix(centered);
76  assert(coeff.cols()==vand.cols());
77 
78  // compute the regression polynomial
79  outputs.resize(1);
80  outputs[0] = (Eigen::MatrixXd)(coeff*vand.transpose());
81 }
82 
84  if( multi ) {
85  return multi->Size();
86  }
87 
88  std::cerr << std::endl << std::endl << "ERROR: Not able to compute the number of points required for interpolation" <<
89  std::endl << "\tPolynomialRegressor.cpp NumInterpolationPoints()" << std::endl;
90  assert(false);
91 
92  return -1;
93 }
94 
95 void Regression::Fit(std::vector<Eigen::VectorXd> xs, std::vector<Eigen::VectorXd> const& ys, Eigen::VectorXd const& center) {
96  assert(xs.size()>0);
97  assert(xs.size()==ys.size());
98 
99  // set the current center
100  currentCenter = center;
101 
102  // center the input points
103  CenterPoints(xs);
104 
105  // compute basis coefficients
106  coeff = ComputeCoefficients(xs, ys);
107 }
108 
109 void Regression::Fit(std::vector<Eigen::VectorXd> const& xs, std::vector<Eigen::VectorXd> const& ys) {
110  assert(xs.size()>0);
111  assert(xs.size()==ys.size());
112 
113  // preform the fit with zero center
114  Fit(xs, ys, Eigen::VectorXd::Zero(xs[0].size()));
115 }
116 
117 Eigen::MatrixXd Regression::ComputeCoefficients(std::vector<Eigen::VectorXd> const& xs, std::vector<Eigen::VectorXd> const& ys) const {
118  assert(xs.size()==ys.size());
119 
120  // check to make sure we have more than the number of points required to interpolate
121  const unsigned int interp = NumInterpolationPoints();
122  if( xs.size()<interp ) {
123  std::cerr << std::endl << "ERROR: Regression requires " << interp << " points to interpolate but only " << xs.size() << " are given." << std::endl;
124  std::cerr << "\tTry fitting the regression with at least " << interp+1 << " points." << std::endl << std::endl;
125  assert(xs.size()>NumInterpolationPoints());
126  }
127 
128  // create the Vandermonde matrix and the rhs
129  Eigen::MatrixXd vand = VandermondeMatrix(xs);
130  const Eigen::MatrixXd rhs = ComputeCoefficientsRHS(vand, ys);
131  vand = vand.transpose()*vand;
132 
133  // make the solver to do the regression
134  auto solver = vand.colPivHouseholderQr();
135 
136  // comptue the coefficients
137  return solver.solve(rhs).transpose();
138 }
139 
140 Eigen::MatrixXd Regression::ComputeCoefficientsRHS(Eigen::MatrixXd const& vand, std::vector<Eigen::VectorXd> const& ys_data) const {
141  // the dimension
142  assert(ys_data.size()>0);
143  const unsigned int dim = ys_data[0].size();
144 
145  // initialize space for the data
146  Eigen::MatrixXd ys = Eigen::MatrixXd::Constant(ys_data.size(), dim, std::numeric_limits<double>::quiet_NaN());
147 
148  // copy the data into an Eigen type
149  for( unsigned int i=0; i<ys_data.size(); ++i ) {
150  ys.row(i) = ys_data[i];
151  }
152 
153  // apply the Vandermonde matrix
154  return vand.transpose()*ys;
155 }
156 
157 Eigen::MatrixXd Regression::VandermondeMatrix(std::vector<Eigen::VectorXd> const& xs) const {
158  assert(multi);
159 
160  // the number of points and the number of terms
161  const unsigned int N = xs.size();
162  const unsigned int M = multi->Size();
163  assert(N>0);
164 
165  // initialize the matrix
166  Eigen::MatrixXd vand = Eigen::MatrixXd::Ones(N, M);
167 
168  // each term is built by evaluating the polynomial basis
169  for( unsigned int i=0; i<M; ++i ) { // loop through the terms
170  // get the multi-index
171  const Eigen::RowVectorXi alpha = multi->at(i)->GetVector();
172 
173  for( unsigned int pt=0; pt<N; ++pt ) { // loop through the points
174  // get the point
175  const Eigen::VectorXd& pnt = xs[pt];
176  assert(alpha.size()==pnt.size());
177 
178  // each term is a product of 1D variables
179  for( unsigned int v=0; v<alpha.size(); ++v ) {
180  // evaluate the polynomial
181  vand(pt, i) *= boost::any_cast<double const>(poly->Evaluate((unsigned int)alpha(v), pnt[v]) [0]);
182  }
183  }
184  }
185 
186  return vand;
187 }
188 
189 Eigen::ArrayXd Regression::CenterPoints(std::vector<Eigen::VectorXd>& xs, Eigen::VectorXd const& center) const {
190  return CenterPoints(xs, center, xs.size());
191 }
192 
193 Eigen::ArrayXd Regression::CenterPoints(std::vector<Eigen::VectorXd>& xs, Eigen::VectorXd const& center, unsigned int const kn) const {
194 
195  // is the center zero?
196  const bool zeroCenter = center.norm()<std::numeric_limits<double>::epsilon();
197 
198  // loop through all of the input points and recenter them
199  for( auto it=xs.begin(); it!=xs.end(); ++it ) {
200  if( !zeroCenter ) {
201  // recenter the the point
202  *it -= center;
203  }
204  }
205 
206  // reset the radius
207  Eigen::ArrayXd radius = Eigen::ArrayXd::Zero(center.size());
208  for( auto it : xs ) { radius = radius.max(it.array().abs()); }
209 
210  for( auto it=xs.begin(); it!=xs.end(); ++it ) { (*it).array() *= radius.inverse(); }
211 
212  // return the radius
213  return radius;
214 }
215 
216 Eigen::ArrayXd Regression::CenterPoints(std::vector<Eigen::VectorXd>& xs) {
217  // reset the current radius
218  currentRadius = CenterPoints(xs, currentCenter, xs.size());
219  return currentRadius;
220 }
221 
222 std::pair<Eigen::VectorXd, double> Regression::PoisednessConstant(std::vector<Eigen::VectorXd> xs, Eigen::VectorXd const& center, int kn) const {
223  // recenter so the points are on the unit ball (unit ball contains the first kn neighbors)
224  assert(kn!=0);
225  const unsigned int N = xs.size(); // the number of points
226  if( kn<0 ) { kn = N; }
227  assert(xs.size()>=kn);
228 
229  const Eigen::ArrayXd radius = CenterPoints(xs, center, kn);
230 
231  // the data for the lagrange polynomial are the Euclidean vectors (w.l.o.g. assume one dimensional output space)
232  std::vector<Eigen::VectorXd> euclidean(N, Eigen::VectorXd::Zero(1));
233 
234  // a place to store the Lagrange coefficients
235  std::vector<Eigen::RowVectorXd> lagrangeCoeff(N);
236 
237  // compute the coefficients for each Lagrange polynomial
238  for( unsigned int i=0; i<N; ++i ) {
239  if( i>0 ) { euclidean[i-1] = Eigen::VectorXd::Zero(1); }
240  euclidean[i] = Eigen::VectorXd::Ones(1);
241 
242  // compute the coefficients
243  lagrangeCoeff[i] = ComputeCoefficients(xs, euclidean);
244  }
245 
246  auto cost = std::make_shared<PoisednessCost>(shared_from_this(), lagrangeCoeff, inputDim);
247  auto constraint = std::make_shared<PoisednessConstraint>(inputDim, alpha);
248 
249  auto opt =
250  std::make_shared<muq::Optimization::NLoptOptimizer>(cost, optPt);
251  opt->AddInequalityConstraint(constraint);
252 
253  std::vector<Eigen::VectorXd> inputs;
254  inputs.push_back((Eigen::VectorXd)Eigen::VectorXd::Zero(inputDim));
255 
256  const std::pair<Eigen::VectorXd, double>& soln =
257  opt->Solve(inputs);
258  assert(soln.second<=0.0); // we are minimizing a negative inner product so the optimal solution should be negative
259 
260  return std::pair<Eigen::VectorXd, double>((radius*soln.first.array()).matrix()+center, -soln.second);
261 }
262 
263 void Regression::ComputeBasisDerivatives(Eigen::VectorXd const& point, std::vector<Eigen::VectorXd>& gradient) const {
264  // the number of terms
265  const unsigned int numCoeff = multi->Size();
266  gradient.resize(numCoeff);
267 
268  // compute the gradient of the basis functions
269  for( unsigned int i=0; i<numCoeff; ++i ) { // loop through the terms
270  // initialize the gradient of basis function i
271  gradient.at(i) = Eigen::VectorXd::Constant(inputDim, std::numeric_limits<double>::quiet_NaN());
272 
273  // the multiindex
274  const Eigen::VectorXi multiIndex = multi->at(i)->GetVector();
275 
276  for( unsigned int d1=0; d1<inputDim; ++d1 ) { // loop through the dimensions of statespace
277  // each term is a product of the 1D variables
278  double result = 1.0;
279  for( unsigned int v=0; v<inputDim; ++v ) {
280  if( v==d1 ) { // the derivative we are differentiating w.r.t.
281  result *= poly->DerivativeEvaluate(multiIndex(v), 1, point(v));
282  } else {
283  result *= boost::any_cast<double const>(poly->Evaluate((unsigned int)multiIndex(v), point(v)) [0]);
284  }
285  }
286 
287  // insert each entry into the vector
288  gradient.at(i) (d1) = result;
289  }
290  }
291 }
292 
293 Regression::PoisednessCost::PoisednessCost(std::shared_ptr<Regression const> parent, std::vector<Eigen::RowVectorXd> const& lagrangeCoeff, unsigned int const inDim) : CostFunction(inDim), parent(parent), lagrangeCoeff(lagrangeCoeff) {}
294 
296 
297  const Eigen::RowVectorXd phi = parent->VandermondeMatrix(std::vector<Eigen::VectorXd>(1, x));
298 
299  Eigen::VectorXd lambda(lagrangeCoeff.size());
300  for( unsigned int i=0; i<lagrangeCoeff.size(); ++i ) { lambda(i) = phi.dot(lagrangeCoeff[i]); }
301 
302  return -1.0*lambda.norm();
303 }
304 
305 void Regression::PoisednessCost::GradientImpl(unsigned int outWrt, unsigned inWrt, muq::Modeling::ref_vector<Eigen::VectorXd> const& input, Eigen::VectorXd const& sensitivity) {
306  assert(inWrt==0);
307  const Eigen::VectorXd& x = input[0];
308 
309  const Eigen::RowVectorXd phi = parent->VandermondeMatrix(std::vector<Eigen::VectorXd>(1, x));
310 
311  // compute the gradient of the basis functions; each element is the gradient of a basis function
312  std::vector<Eigen::VectorXd> gradBasis;
313  parent->ComputeBasisDerivatives(x, gradBasis);
314  assert(gradBasis.size()==lagrangeCoeff[0].size());
315  assert(gradBasis[0].size()==inputSizes(0));
316 
317  gradient = Eigen::VectorXd::Zero(inputSizes(0));
318 
319  Eigen::VectorXd lambda(lagrangeCoeff.size());
320  for( unsigned int i=0; i<lagrangeCoeff.size(); ++i ) {
321  lambda(i) = phi.dot(lagrangeCoeff[i]);
322  for( unsigned int j=0; j<gradBasis.size(); ++j ) {
323  gradient += lambda(i)*lagrangeCoeff[i](j)*gradBasis[j];
324  }
325  }
326 
327  gradient *= -1.0*sensitivity(0)/lambda.norm();
328 
329  /*{ // sanity check ...
330  std::cout << std::endl;
331  Eigen::VectorXd gradFD = GradientByFD(0, 0, input, sensitivity);
332  std::cout << "point (in GradientImpl): " << x.transpose() << std::endl;
333  std::cout << "(cost) FD gradient: " << gradFD.transpose() << std::endl;
334  std::cout << "(cost) gradient: " << gradient.transpose() << std::endl;
335  }*/
336 }
337 
338 Regression::PoisednessConstraint::PoisednessConstraint(unsigned int const inDim, double const alpha) :
339  ModPiece(Eigen::VectorXi::Constant(1, inDim), Eigen::VectorXi::Ones(1)), alpha(alpha) {}
340 
342  const Eigen::VectorXd& x = input[0];
343  outputs.resize(outputSizes[0]);
344  outputs[0] = (x.dot(x)-alpha)*Eigen::VectorXd::Ones(1);
345 }
346 
347 void Regression::PoisednessConstraint::JacobianImpl(unsigned int const outputDimWrt,
348  unsigned int const inputDimWrt,
350 
351  assert(inputDimWrt==0);
352  const Eigen::VectorXd& x = input[0];
353 
354  jacobian.resize(inputSizes[0], outputSizes[0]);
355  jacobian = 2.0*x;
356 
357  /*{ // sanity check ...
358  std::cout << std::endl;
359  Eigen::VectorXd gradFD = GradientByFD(0, 0, input, sensitivity);
360  std::cout << "point (in GradientImpl): " << x.transpose() << std::endl;
361  std::cout << "(constraint) FD gradient: " << gradFD.transpose() << std::endl;
362  std::cout << "(constraint) gradient: " << gradient.transpose() << std::endl;
363  }*/
364 }
static std::shared_ptr< IndexedScalarBasis > Construct(std::string const &polyName)
virtual void JacobianImpl(unsigned int const outputDimWrt, unsigned int const inputDimWrt, muq::Modeling::ref_vector< Eigen::VectorXd > const &input) override
Definition: Regression.cpp:347
PoisednessConstraint(unsigned int const inDim, double const alpha)
Definition: Regression.cpp:338
virtual void EvaluateImpl(muq::Modeling::ref_vector< Eigen::VectorXd > const &input) override
Definition: Regression.cpp:341
virtual void GradientImpl(unsigned int outWrt, unsigned int inWrt, muq::Modeling::ref_vector< Eigen::VectorXd > const &input, Eigen::VectorXd const &sensitivity) override
Compute the gradient of the cost function.
Definition: Regression.cpp:305
PoisednessCost(std::shared_ptr< Regression const > parent, std::vector< Eigen::RowVectorXd > const &lagrangeCoeff, unsigned int const inDim)
Definition: Regression.cpp:293
Eigen::ArrayXd currentRadius
Current radius of inputs.
Definition: Regression.h:166
boost::property_tree::ptree optPt
Parameters for the poisedness cosntant optimization.
Definition: Regression.h:172
const double alpha
The radius of the poisedness constraint.
Definition: Regression.h:154
void ComputeBasisDerivatives(Eigen::VectorXd const &point, std::vector< Eigen::VectorXd > &gradient) const
Definition: Regression.cpp:263
virtual void EvaluateImpl(muq::Modeling::ref_vector< boost::any > const &inputs) override
User-implemented function that determines the behavior of this muq::Modeling::WorkPiece.
Definition: Regression.cpp:61
const unsigned int order
The order of the regression.
Definition: Regression.h:57
std::shared_ptr< IndexedScalarBasis > poly
The polynomial basis (in one variable) used to compute the Vandermonde matrix.
Definition: Regression.h:160
Eigen::MatrixXd ComputeCoefficients(std::vector< Eigen::VectorXd > const &xs, std::vector< Eigen::VectorXd > const &ys) const
Compute the coefficients for the basis functions.
Definition: Regression.cpp:117
Eigen::ArrayXd CenterPoints(std::vector< Eigen::VectorXd > &xs)
Center the input points.
Definition: Regression.cpp:216
const unsigned int inputDim
The input dimension.
Definition: Regression.h:148
Eigen::MatrixXd ComputeCoefficientsRHS(Eigen::MatrixXd const &vand, std::vector< Eigen::VectorXd > const &ys_data) const
Compute the right hand side given data to compute the polynomial coefficients.
Definition: Regression.cpp:140
void Fit(std::vector< Eigen::VectorXd > xs, std::vector< Eigen::VectorXd > const &ys, Eigen::VectorXd const &center)
Compute the coeffiecents of the polynomial given data.
Definition: Regression.cpp:95
Eigen::MatrixXd coeff
Coeffients for the polynomial basis.
Definition: Regression.h:169
std::pair< Eigen::VectorXd, double > PoisednessConstant(std::vector< Eigen::VectorXd > xs, Eigen::VectorXd const &center, int kn=-1) const
Definition: Regression.cpp:222
Eigen::MatrixXd VandermondeMatrix(std::vector< Eigen::VectorXd > const &xs) const
Create the Vandermonde matrix.
Definition: Regression.cpp:157
Eigen::VectorXd currentCenter
Current center of the inputs.
Definition: Regression.h:163
std::shared_ptr< muq::Utilities::MultiIndexSet > multi
The multi-index to so we know the order of each term.
Definition: Regression.h:157
Provides an abstract interface for defining vector-valued model components.
Definition: ModPiece.h:148
Base class for MUQ's modelling envronment.
Definition: WorkPiece.h:40
std::map< unsigned int, int > inputSizes
Definition: WorkPiece.h:560
std::vector< boost::any > outputs
The outputs.
Definition: WorkPiece.h:546
The cost function for an optimization routine.
Definition: CostFunction.h:74
std::vector< std::reference_wrapper< const T > > ref_vector
A vector of references to something ...
Definition: WorkPiece.h:37
int int diyfp diyfp v
Definition: json.h:15163
auto get(const nlohmann::detail::iteration_proxy_value< IteratorType > &i) -> decltype(i.key())
Definition: json.h:3956