MUQ  0.4.3
PolynomialChaosExpansion.cpp
Go to the documentation of this file.
2 
3 #include <fstream>
4 #include <iostream>
5 
8 
9 
10 using namespace muq::Utilities;
11 using namespace muq::Approximation;
12 
13 PolynomialChaosExpansion::PolynomialChaosExpansion(std::shared_ptr<OrthogonalPolynomial> const& basisCompsIn,
14  std::shared_ptr<muq::Utilities::MultiIndexSet> const& multisIn,
15  Eigen::MatrixXd const& coeffsIn) : PolynomialChaosExpansion(std::vector<std::shared_ptr<IndexedScalarBasis>>(multisIn->GetMultiLength(), basisCompsIn),
16  multisIn,
17  coeffsIn)
18 {}
19 
20 PolynomialChaosExpansion::PolynomialChaosExpansion(std::shared_ptr<OrthogonalPolynomial> const& basisCompsIn,
21  std::shared_ptr<muq::Utilities::MultiIndexSet> const& multisIn,
22  unsigned int outputDim) : PolynomialChaosExpansion(std::vector<std::shared_ptr<IndexedScalarBasis>>(multisIn->GetMultiLength(), basisCompsIn),
23  multisIn,
24  outputDim)
25 {}
26 
27 PolynomialChaosExpansion::PolynomialChaosExpansion(std::vector<std::shared_ptr<IndexedScalarBasis>> const& basisCompsIn,
28  std::shared_ptr<muq::Utilities::MultiIndexSet> const& multisIn,
29  Eigen::MatrixXd const& coeffsIn) : BasisExpansion(basisCompsIn, multisIn, coeffsIn)
30 {
31 }
32 
33 PolynomialChaosExpansion::PolynomialChaosExpansion(std::vector<std::shared_ptr<IndexedScalarBasis>> const& basisCompsIn,
34  std::shared_ptr<muq::Utilities::MultiIndexSet> const& multisIn,
35  unsigned int outputDim) : BasisExpansion(basisCompsIn, multisIn, Eigen::MatrixXd::Zero(outputDim, multisIn->Size()))
36 {}
37 
38 
39 
41 
42  Eigen::VectorXd result = Eigen::VectorXd::Zero(multis->Size());
43 
44  //compute each one
45  for (unsigned int i = 0; i < multis->Size(); i++) {
46  double norm = 1.0; //start the normalization at 1
47 
48  //loop over the dimensions and multiply the normalizations for each component polynomial
49  Eigen::RowVectorXi multi = multis->IndexToMulti(i)->GetVector();
50  for(int k=0; k<multi.size(); ++k){
51  norm *= std::dynamic_pointer_cast<OrthogonalPolynomial>(basisComps.at(k))->Normalization(multi(k));
52  }
53  result(i) = std::sqrt(norm);
54  }
55 
56  return result;
57 };
58 
59 
60 Eigen::VectorXd PolynomialChaosExpansion::Variance() const
61 {
62  Eigen::VectorXd normalVec = GetNormalizationVec();
63 
64  //if there's only the one constant term, the PCE has variance zero
65  if (normalVec.rows() <= 1) {
66  return Eigen::VectorXd::Zero(coeffs.rows());
67  }
68 
69  Eigen::VectorXd squareNorm = normalVec.tail(normalVec.rows() - 1).array().square();
70 
71  //for each output, the variance is the dot product of the squared coeffs with the squared norms of the PCE terms.
72  //Thus, grab all but the leftmost column.
73  //Have to normalize by what the constant integrates to.
74 
75  Eigen::VectorXd dimMeasures(inputSizes(0));
76  for (unsigned int i = 0; i < inputSizes(0); ++i)
77  dimMeasures(i) = std::dynamic_pointer_cast<OrthogonalPolynomial>(basisComps.at(i))->Normalization(0);
78 
79  //Since the variance is an expectation, we must normalize if the polynomials aren't quite set up
80  //to integrate to one.
81  return coeffs.rightCols(coeffs.cols() - 1).array().square().matrix() * squareNorm / dimMeasures.prod();
82 }
83 
84 Eigen::MatrixXd PolynomialChaosExpansion::Covariance() const
85 {
86  Eigen::VectorXd normalVec = GetNormalizationVec();
87 
88  //if there's only the one constant term, the PCE has variance zero
89  if (normalVec.rows() <= 1) {
90  return Eigen::MatrixXd::Zero(coeffs.rows(),coeffs.rows());
91  }
92 
93  Eigen::VectorXd squareNorm = normalVec.tail(normalVec.rows() - 1).array().square();
94 
95  //for each output, the variance is the dot product of the squared coeffs with the squared norms of the PCE terms.
96  //Thus, grab all but the leftmost column.
97  //Have to normalize by what the constant integrates to.
98 
99  Eigen::VectorXd dimMeasures(inputSizes(0));
100  for (unsigned int i = 0; i < inputSizes(0); ++i)
101  dimMeasures(i) = std::dynamic_pointer_cast<OrthogonalPolynomial>(basisComps.at(i))->Normalization(0);
102 
103  Eigen::VectorXd quadVec = squareNorm / dimMeasures.prod();
104 
105  // fill in upper portion of covariance matrix
106  Eigen::MatrixXd cov(outputSizes(0), outputSizes(0));
107  int npolys = coeffs.cols();
108  for (int j = 0; j < outputSizes(0); ++j) { // column index
109  for (int i = j; i < outputSizes(0); ++i) { // row index
110  cov(i,j) = coeffs.row(j).tail(npolys - 1) * quadVec.asDiagonal() * coeffs.row(i).tail(npolys - 1).transpose();
111  cov(j,i) = cov(i,j);
112  }
113  }
114 
115  return cov;
116 }
117 
118 Eigen::VectorXd PolynomialChaosExpansion::Mean() const
119 {
120  return coeffs.col(0);
121 }
122 
123 
124 Eigen::VectorXd PolynomialChaosExpansion::Magnitude() const
125 {
126  Eigen::VectorXd normalVec = GetNormalizationVec();
127 
128  //take the matrix product between the squared coeffs and squared norms, then sqrt each term
129  return (coeffs.array().square().matrix() * normalVec.array().square().matrix()).array().sqrt();
130 }
131 
132 
133 
134 std::shared_ptr<PolynomialChaosExpansion> PolynomialChaosExpansion::ComputeWeightedSum(std::vector<std::shared_ptr<PolynomialChaosExpansion>> expansions,
135  Eigen::VectorXd const& weights,
136  std::shared_ptr<MultiIndexSet> const& polynomials,
137  std::vector<std::vector<unsigned int>> const& locToGlob)
138 {
139  std::shared_ptr<PolynomialChaosExpansion> firstNonNull;
140  for(unsigned int i=0; i<expansions.size();++i){
141  if(expansions.at(i) != nullptr) {
142  firstNonNull = expansions.at(i);
143  break;
144  }
145  }
146  assert(firstNonNull);
147 
148  // Loop over each expansion and add the weighted coefficients
149  Eigen::MatrixXd newCoeffs = Eigen::MatrixXd::Zero(firstNonNull->outputSizes(0), polynomials->Size());
150 
151  for(unsigned int i=0; i<expansions.size(); ++i){
152  if(std::abs(weights(i))>10.0*std::numeric_limits<double>::epsilon()){
153  for(unsigned int k=0; k<expansions.at(i)->coeffs.cols(); ++k){
154  newCoeffs.col(locToGlob.at(i).at(k)) += weights(i)*expansions.at(i)->coeffs.col(k);
155  }
156  }
157  }
158 
159  return std::make_shared<PolynomialChaosExpansion>(firstNonNull->basisComps, polynomials, newCoeffs);
160 }
161 
162 std::shared_ptr<PolynomialChaosExpansion> PolynomialChaosExpansion::ComputeWeightedSum(std::vector<std::shared_ptr<PolynomialChaosExpansion>> expansions,
163  Eigen::VectorXd const& weights)
164 {
165  assert(weights.size()==expansions.size());
166 
167  std::shared_ptr<PolynomialChaosExpansion> firstNonNull;
168  for(unsigned int i=0; i<expansions.size();++i){
169  if(expansions.at(i) != nullptr) {
170  firstNonNull = expansions.at(i);
171  break;
172  }
173  }
174  assert(firstNonNull);
175 
176  unsigned int inputDim = firstNonNull->inputSizes(0);
177  unsigned int outputDim = firstNonNull->outputSizes(0);
178 
179  // the collection of all polynomials that will be in the result
180  auto allPolynomials = std::make_shared<MultiIndexSet>(inputDim);
181 
182  // Maps the local indices in each expansion to the global index of the sum
183  std::vector<std::vector<unsigned int>> locToGlob(weights.size());
184 
185  // Make a union of all the multiindex sets, and keep track of the coefficient mapping
186  for(int j=0; j<expansions.size(); ++j)
187  {
188  // if the weight is zero, skip this expansion
189  if(std::abs(weights(j))>10.0*std::numeric_limits<double>::epsilon()){
190  assert(expansions.at(j));
191 
192  //loop over all the polynomials in this expansion
193  unsigned int numTerms = expansions.at(j)->multis->Size();
194  locToGlob.at(j).resize(numTerms);
195  for (unsigned int i = 0; i < numTerms; ++i)
196  locToGlob.at(j).at(i) = allPolynomials->AddActive(expansions.at(j)->multis->IndexToMulti(i));
197  }
198  }
199 
200  return ComputeWeightedSum(expansions,weights,allPolynomials,locToGlob);
201 }
202 
203 
204 Eigen::VectorXd PolynomialChaosExpansion::TotalSensitivity(unsigned int targetDim) const
205 {
206 
207  //grab the total normalizations
208  Eigen::VectorXd normalVec = GetNormalizationVec();
209 
210  // Zero out the terms that do not depend on the targetDim
211  for (unsigned int i = 0; i < multis->Size(); ++i){
212  if(multis->IndexToMulti(i)->GetValue(targetDim)==0)
213  normalVec(i) = 0.0;
214  }
215 
216  Eigen::VectorXd dimMeasures(inputSizes(0));
217  for (unsigned int i = 0; i < inputSizes(0); ++i)
218  dimMeasures(i) = std::dynamic_pointer_cast<OrthogonalPolynomial>(basisComps.at(i))->Normalization(0);
219 
220  //we want the sum of normalized involved coeffs divided by the normalized sum of all of them.
221  //Don't forget the constant normalization the variance requires
222  return (coeffs.array().square().matrix() * normalVec.array().square().matrix()).array() / dimMeasures.prod() / Variance().array();
223 }
224 
226 {
227  Eigen::MatrixXd result(coeffs.rows(), inputSizes(0));
228 
229  for (unsigned int i = 0; i < inputSizes(0); ++i)
230  result.col(i) = TotalSensitivity(i);
231 
232  return result;
233 }
234 
235 Eigen::VectorXd PolynomialChaosExpansion::SobolSensitivity(unsigned int targetDim) const {
236  return SobolSensitivity(std::vector<unsigned int>(1,targetDim));
237 }
238 
239 
240 Eigen::VectorXd PolynomialChaosExpansion::SobolSensitivity(std::vector<unsigned int> const& targetDims) const {
241  //we're going to fill this index with either 1 or 0, depending on whether the term includes the target dimension
242  Eigen::ArrayXd contributesToIndex = Eigen::VectorXd::Zero(NumTerms());
243 
244  // each term contributes to the main effects iff all nonzero terms in the multiindex correspond to a target dimension
245  for(unsigned int i=0; i<multis->Size(); ++i){
246  auto currMulti = multis->IndexToMulti(i);
247  int miSum = currMulti->Sum();
248 
249  int partSum = 0;
250  for(auto& k : targetDims)
251  partSum += currMulti->GetValue(k);
252 
253  if((miSum>0)&&(partSum==miSum)){
254  contributesToIndex(i) = 1;
255  }
256  }
257 
258  //grab the total normalizations
259  Eigen::VectorXd normalVec = GetNormalizationVec();
260 
261  //filter the normalizations so that the ones that don't involve targetDim are zero
262  Eigen::VectorXd filterdVec = normalVec.array() * contributesToIndex;
263 
264  Eigen::VectorXd dimMeasures(inputSizes(0));
265  for (unsigned int i = 0; i < inputSizes(0); ++i)
266  dimMeasures(i) = std::dynamic_pointer_cast<OrthogonalPolynomial>(basisComps.at(i))->Normalization(0);
267 
268  //we want the sum of normalized involved coeffs divided by the normalized sum of all of them.
269  //Don't forget the constant normalization the variance requires
270  return (coeffs.array().square().matrix() * filterdVec.array().square().matrix()).array() / dimMeasures.prod() / Variance().array();
271 }
272 
274 {
275  Eigen::MatrixXd result(coeffs.rows(), inputSizes(0));
276 
277  for (unsigned int i = 0; i < inputSizes(0); ++i)
278  result.col(i) = SobolSensitivity(i);
279 
280  return result;
281 }
282 
283 
285 {
286  BasisExpansion::ToHDF5(group);
287  group.attrs["expansion_type"] = "PCE";
288 }
289 
290 std::shared_ptr<PolynomialChaosExpansion> PolynomialChaosExpansion::FromHDF5(std::string filename, std::string groupName)
291 {
292  std::shared_ptr<PolynomialChaosExpansion> output = std::dynamic_pointer_cast<PolynomialChaosExpansion>(BasisExpansion::FromHDF5(filename,groupName));
293  if(!output)
294  throw std::runtime_error("Could not read PolynomialChaosExpansion from group \"" + groupName + "\". HDF5 file does not seem to contain a PCE. Is the \"expansion_type\" attribute set to \"PCE\"?");
295  return output;
296 }
297 
298 std::shared_ptr<PolynomialChaosExpansion> PolynomialChaosExpansion::FromHDF5(muq::Utilities::H5Object &group)
299 {
300  std::shared_ptr<PolynomialChaosExpansion> output = std::dynamic_pointer_cast<PolynomialChaosExpansion>(BasisExpansion::FromHDF5(group));
301  if(!output)
302  throw std::runtime_error("Could not read PolynomialChaosExpansion from group \"" + group.Path() + "\". HDF5 file does not seem to contain a PCE. Is the \"expansion_type\" attribute set to \"PCE\"?");
303  return output;
304 }
305 
Class for defining expansions of basis functions defined by a MultiIndexSet and collection of IndexSc...
std::shared_ptr< muq::Utilities::MultiIndexSet > multis
static std::shared_ptr< BasisExpansion > FromHDF5(std::string filename, std::string groupName="/")
Loads an expansion from an HDF5 file.
virtual unsigned NumTerms() const
std::vector< std::shared_ptr< IndexedScalarBasis > > basisComps
virtual void ToHDF5(std::string filename, std::string groupName="/") const
Saves the expansion to group in an HDF5 file.
A class for representing and using expansions of orthogonal multivariate polynomials.
static std::shared_ptr< PolynomialChaosExpansion > FromHDF5(std::string filename, std::string groupName="/")
Loads an expansion from an HDF5 file.
Eigen::VectorXd Variance() const
compute the variance of the current expansion
Eigen::VectorXd Magnitude() const
Compute the L2 norm of each output.
static std::shared_ptr< PolynomialChaosExpansion > ComputeWeightedSum(std::vector< std::shared_ptr< PolynomialChaosExpansion >> expansions, Eigen::VectorXd const &weights)
Eigen::MatrixXd TotalSensitivity() const
Compute all Sobol total sensitivities. Rows are outputs, each column is an input.
PolynomialChaosExpansion(std::shared_ptr< OrthogonalPolynomial > const &basisCompsIn, std::shared_ptr< muq::Utilities::MultiIndexSet > const &multisIn, Eigen::MatrixXd const &coeffsIn)
virtual void ToHDF5(muq::Utilities::H5Object &group) const override
Write PCE to HDF5 file.
Eigen::VectorXd SobolSensitivity(unsigned int targetDim) const
Compute the main sensitivity index for the input dimension, for each output dimension.
const Eigen::VectorXi inputSizes
Definition: ModPiece.h:469
const Eigen::VectorXi outputSizes
Definition: ModPiece.h:472
AttributeList attrs
Definition: H5Object.h:205
std::string Path() const
Definition: H5Object.h:201