1 //                                               -*- C++ -*-
2 /**
3  *  @brief Karhunen-Loeve decomposition and by-products
4  *
5  *  Copyright 2005-2021 Airbus-EDF-IMACS-ONERA-Phimeca
6  *
7  *  This library is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU Lesser General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public License
18  *  along with this library.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include "openturns/PersistentObjectFactory.hxx"
23 #include "openturns/KarhunenLoeveResultImplementation.hxx"
24 #include "openturns/LinearFunction.hxx"
25 #include "openturns/Cloud.hxx"
26 #include "openturns/IdentityMatrix.hxx"
27 #include "openturns/ComposedFunction.hxx"
28 #include "openturns/LinearCombinationFunction.hxx"
29 #include "openturns/P1LagrangeEvaluation.hxx"
30 
31 BEGIN_NAMESPACE_OPENTURNS
32 
33 CLASSNAMEINIT(KarhunenLoeveResultImplementation)
34 
35 static const Factory<KarhunenLoeveResultImplementation> Factory_KarhunenLoeveResultImplementation;
36 
37 /* Default constructor */
KarhunenLoeveResultImplementation()38 KarhunenLoeveResultImplementation::KarhunenLoeveResultImplementation()
39   : PersistentObject()
40   , covariance_()
41   , threshold_(0.0)
42   , eigenvalues_()
43   , modes_()
44   , modesAsProcessSample_()
45   , projection_()
46 {
47   // Nothing to do
48 }
49 
50 /* Default constructor */
KarhunenLoeveResultImplementation(const CovarianceModel & covariance,const Scalar threshold,const Point & eigenvalues,const FunctionCollection & modes,const ProcessSample & modesAsProcessSample,const Matrix & projection)51 KarhunenLoeveResultImplementation::KarhunenLoeveResultImplementation(const CovarianceModel & covariance,
52     const Scalar threshold,
53     const Point & eigenvalues,
54     const FunctionCollection & modes,
55     const ProcessSample & modesAsProcessSample,
56     const Matrix & projection)
57   : PersistentObject()
58   , covariance_(covariance)
59   , threshold_(threshold)
60   , eigenvalues_(eigenvalues)
61   , modes_(modes)
62   , modesAsProcessSample_(modesAsProcessSample)
63   , projection_(projection)
64 {
65   // Nothing to do
66 }
67 
68 /* Virtual constructor */
clone() const69 KarhunenLoeveResultImplementation * KarhunenLoeveResultImplementation::clone() const
70 {
71   return new KarhunenLoeveResultImplementation(*this);
72 }
73 
74 /* Threshold accessor */
getThreshold() const75 Scalar KarhunenLoeveResultImplementation::getThreshold() const
76 {
77   return threshold_;
78 }
79 
80 /* Covariance model accessor */
getCovarianceModel() const81 CovarianceModel KarhunenLoeveResultImplementation::getCovarianceModel() const
82 {
83   return covariance_;
84 }
85 
86 /* Eigenvalues accessor */
getEigenvalues() const87 Point KarhunenLoeveResultImplementation::getEigenvalues() const
88 {
89   return eigenvalues_;
90 }
91 
drawEigenvalues() const92 Graph KarhunenLoeveResultImplementation::drawEigenvalues() const
93 {
94   Graph graph("Karhunen-Loeve eigenvalues", "Index", "Eigenvalue", true, "topright");
95   const UnsignedInteger K = eigenvalues_.getSize();
96   Point indices(K);
97   for (UnsignedInteger i = 0; i < K; ++ i)
98     indices[i] = i;
99   Cloud cloud(indices, eigenvalues_);
100   graph.add(cloud);
101   graph.setGrid(true);
102   return graph;
103 }
104 
drawCumulatedEigenvaluesRemainder() const105 Graph KarhunenLoeveResultImplementation::drawCumulatedEigenvaluesRemainder() const
106 {
107   Graph graph("Karhunen-Loeve eigenvalues", "Index", "Cumulated eigenvalue normalized remainder", true, "topright");
108   const UnsignedInteger K = eigenvalues_.getSize();
109   Point indices(K);
110   for (UnsignedInteger i = 0; i < K; ++ i)
111     indices[i] = i;
112   Point eigenCumSum(eigenvalues_);
113   for (UnsignedInteger i = 1; i < K; ++ i)
114     eigenCumSum[i] += eigenCumSum[i - 1];
115   if (K > 1)
116   {
117     eigenCumSum /= eigenCumSum[K - 1];
118     eigenCumSum[K - 1] = eigenCumSum[K - 2];// avoids log(0) in log scale
119   }
120   Cloud cloud(indices, Point(K, 1) - eigenCumSum);
121   graph.add(cloud);
122   graph.setGrid(true);
123   return graph;
124 }
125 
126 /* Modes accessors */
getModes() const127 KarhunenLoeveResultImplementation::FunctionCollection KarhunenLoeveResultImplementation::getModes() const
128 {
129   return modes_;
130 }
131 
getModesAsProcessSample() const132 ProcessSample KarhunenLoeveResultImplementation::getModesAsProcessSample() const
133 {
134   return modesAsProcessSample_;
135 }
136 
137 /* Mesh accessor */
getMesh() const138 Mesh KarhunenLoeveResultImplementation::getMesh() const
139 {
140   return modesAsProcessSample_.getMesh();
141 }
142 
143 /* Scaled modes accessors */
getScaledModes() const144 KarhunenLoeveResultImplementation::FunctionCollection KarhunenLoeveResultImplementation::getScaledModes() const
145 {
146   Collection<Function> scaledModes(modes_.getSize());
147   if (modes_.getSize() == 0) return scaledModes;
148   const UnsignedInteger dimension = modes_[0].getOutputDimension();
149   const Point zero(dimension);
150   const IdentityMatrix id(dimension);
151   for (UnsignedInteger i = 0; i < scaledModes.getSize(); ++i)
152   {
153     const Function modeI(modes_[i]);
154     LinearFunction scaling(zero, zero, id * std::sqrt(eigenvalues_[i]));
155     scaledModes[i] = ComposedFunction(scaling, modeI);
156   }
157   return scaledModes;
158 }
159 
getScaledModesAsProcessSample() const160 ProcessSample KarhunenLoeveResultImplementation::getScaledModesAsProcessSample() const
161 {
162   ProcessSample scaledModesAsProcessSample(modesAsProcessSample_.getMesh(), modesAsProcessSample_.getSize(), modesAsProcessSample_.getDimension());
163   for (UnsignedInteger i = 0; i < modesAsProcessSample_.getSize(); ++i)
164     scaledModesAsProcessSample[i] = modesAsProcessSample_[i] * std::sqrt(eigenvalues_[i]);
165   return scaledModesAsProcessSample;
166 }
167 
168 /* Projection matrix accessor */
getProjectionMatrix() const169 Matrix KarhunenLoeveResultImplementation::getProjectionMatrix() const
170 {
171   return projection_;
172 }
173 
174 /* Projection method */
project(const FunctionCollection & functionCollection) const175 Sample KarhunenLoeveResultImplementation::project(const FunctionCollection & functionCollection) const
176 {
177   const UnsignedInteger size = functionCollection.getSize();
178   const Sample vertices(modesAsProcessSample_.getMesh().getVertices());
179   Sample functionValues(size, projection_.getNbColumns());
180   for(UnsignedInteger i = 0; i < size; ++i)
181     functionValues[i] = (functionCollection[i](vertices)).getImplementation()->getData();
182   return projection_.getImplementation()->genSampleProd(functionValues, true, false, 'R');
183 }
184 
project(const Function & function) const185 Point KarhunenLoeveResultImplementation::project(const Function & function) const
186 {
187   // Evaluate the function over the vertices of the mesh and cast it into a Point
188   const Point functionValues(function(modesAsProcessSample_.getMesh().getVertices()).getImplementation()->getData());
189   return projection_ * functionValues;
190 }
191 
project(const Sample & values) const192 Point KarhunenLoeveResultImplementation::project(const Sample & values) const
193 {
194   if (values.getDimension() != modesAsProcessSample_.getDimension())
195     throw InvalidDimensionException(HERE) << "Expected values of dimension " << modesAsProcessSample_.getDimension() << " got " << values.getDimension();
196   return projection_ * values.getImplementation()->getData();
197 }
198 
project(const ProcessSample & sample) const199 Sample KarhunenLoeveResultImplementation::project(const ProcessSample & sample) const
200 {
201   if (sample.getDimension() != modesAsProcessSample_.getDimension())
202     throw InvalidDimensionException(HERE) << "Expected values of dimension " << modesAsProcessSample_.getDimension() << " got " << sample.getDimension();
203   const UnsignedInteger size = sample.getSize();
204   if (!(size != 0)) return Sample();
205   const Mesh mesh(modesAsProcessSample_.getMesh());
206   const UnsignedInteger dimension = sample.getDimension();
207   const UnsignedInteger length = mesh.getVerticesNumber();
208   if (sample.getMesh() == mesh)
209   {
210     // result[i] = projection_ * sample.data_[i] if sample.data_ is viewed as a
211     //   Sample(size, length * dimension) and result[i] as a Point of dimension rows(projection_)
212     // This can be rewritten with matrix multiplication:
213     //   transposed(result) = transposed(sample.data_) * transposed(projection_)
214     // As result and sample.data_ are stored as Sample and not Matrix, we have
215     //   result = sample.data_ * transposed(projection_)
216     Sample values(size, length * dimension);
217     for(UnsignedInteger i = 0; i < size; ++i)
218       std::copy(&sample[i](0, 0), &sample[i](0, 0) + length * dimension, &values(i, 0));
219     return projection_.getImplementation()->genSampleProd(values, true, false, 'R');
220   }
221   else
222   {
223     // We build a P1LagrangeEvaluation as if ProcessSample was an aggregated Field.
224     P1LagrangeEvaluation evaluation(sample);
225     // values is Sample(length, size * dimension)
226     const Sample values(evaluation(mesh.getVertices()));
227     // Dispatch values so that it can be multiplied by projection_ like above
228     Sample dispatched(size, length * dimension);
229     for(UnsignedInteger i = 0; i < size; ++i)
230       for(UnsignedInteger j = 0; j < length; ++j)
231         std::copy(&values(j, i * dimension), &values(j, i * dimension) + dimension, &dispatched(i, j * dimension));
232     return projection_.getImplementation()->genSampleProd(dispatched, true, false, 'R');
233   }
234 }
235 
236 /* Lift method */
lift(const Point & coefficients) const237 Function KarhunenLoeveResultImplementation::lift(const Point & coefficients) const
238 {
239   const UnsignedInteger dimension = eigenvalues_.getDimension();
240   if (coefficients.getDimension() != dimension)
241     throw InvalidDimensionException(HERE) << "Expected coefficients of dimension " << dimension << " got " << coefficients.getDimension();
242   Point scaledCoefficients(dimension);
243   Collection<Function> functions(dimension);
244   for (UnsignedInteger i = 0; i < dimension; ++i)
245   {
246     scaledCoefficients[i] = std::sqrt(eigenvalues_[i]) * coefficients[i];
247     functions[i] = modes_[i];
248   }
249   return LinearCombinationFunction(functions, scaledCoefficients);
250 }
251 
liftAsSample(const Point & coefficients) const252 Sample KarhunenLoeveResultImplementation::liftAsSample(const Point & coefficients) const
253 {
254   const UnsignedInteger dimension = eigenvalues_.getDimension();
255   if (coefficients.getDimension() != dimension)
256     throw InvalidDimensionException(HERE) << "Expected coefficients of dimension " << dimension << " got " << coefficients.getDimension();
257   Sample values(modesAsProcessSample_.getMesh().getVerticesNumber(), modesAsProcessSample_.getDimension());
258   for (UnsignedInteger i = 0; i < dimension; ++i)
259     values += modesAsProcessSample_[i] * (std::sqrt(eigenvalues_[i]) * coefficients[i]);
260   return values;
261 }
262 
liftAsField(const Point & coefficients) const263 Field KarhunenLoeveResultImplementation::liftAsField(const Point & coefficients) const
264 {
265   return Field(modesAsProcessSample_.getMesh(), liftAsSample(coefficients));
266 }
267 
268 /* String converter */
__repr__() const269 String KarhunenLoeveResultImplementation::__repr__() const
270 {
271   return OSS(true) << "class=" << getClassName()
272          << " covariance model=" << covariance_
273          << " threshold=" << threshold_
274          << " eigenvalues=" << eigenvalues_
275          << " modes=" << modes_
276          << " modesAsProcessSample=" << modesAsProcessSample_
277          << " projection=" << projection_;
278 }
279 
__str__(const String &) const280 String KarhunenLoeveResultImplementation::__str__(const String & ) const
281 {
282   return __repr__();
283 }
284 
285 /* Method save() stores the object through the StorageManager */
save(Advocate & adv) const286 void KarhunenLoeveResultImplementation::save(Advocate & adv) const
287 {
288   PersistentObject::save(adv);
289   adv.saveAttribute("covariance_", covariance_);
290   adv.saveAttribute("threshold_", threshold_);
291   adv.saveAttribute("eigenvalues_", eigenvalues_);
292   adv.saveAttribute("modes_", modes_);
293   adv.saveAttribute("modesAsProcessSample_", modesAsProcessSample_);
294   adv.saveAttribute("projection_", projection_);
295 }
296 
297 
298 /* Method load() reloads the object from the StorageManager */
load(Advocate & adv)299 void KarhunenLoeveResultImplementation::load(Advocate & adv)
300 {
301   PersistentObject::load(adv);
302   adv.loadAttribute("covariance_", covariance_);
303   adv.loadAttribute("threshold_", threshold_);
304   adv.loadAttribute("eigenvalues_", eigenvalues_);
305   adv.loadAttribute("modes_", modes_);
306   adv.loadAttribute("modesAsProcessSample_", modesAsProcessSample_);
307   adv.loadAttribute("projection_", projection_);
308 }
309 
310 
311 END_NAMESPACE_OPENTURNS
312 
313