CMP++: Uncertainty Quantification & Bayesian Calibration
Loading...
Searching...
No Matches
model_cluster_poly.h
Go to the documentation of this file.
1#ifndef MODEL_CLUSTER_H
2#define MODEL_CLUSTER_H
3
4#include <iostream>
5#include <stdexcept>
6#include <Eigen/Dense>
7#include <distribution.h>
8#include <cmp_defines.h>
9#include <grid.h>
10#include <cluster.h>
11#include <classifier.h>
12#include <poly.h>
13#include <svm.h>
14#include <set>
15
16
21namespace cmp {
43
44 private:
45 std::default_random_engine rng_;
46
47 Eigen::MatrixXd xObs_;
48 Eigen::VectorXd yObs_;
49
50 size_t nClusters_;
51 size_t nObs_;
52 size_t dimX_;
53
54 // The labels of the points
56
57 // Local index of each global point inside its current cluster
59
60 // The GPs for each cluster, along with their centroids and fit status
61 std::vector<bool> fit_;
62 std::vector<Eigen::VectorXd> centroids_;
63 std::vector<cmp::PolynomialExpansion> *polynomials_;
64
65 // The cluster sizes
66 std::vector<size_t> clusterSize_;
67
68 // The Gamma parameter
69 double gamma_{1.0};
70
71 public:
72
73 ModelClusterPoly() = default;
74 ~ModelClusterPoly() = default;
75
76 void set(std::vector<cmp::PolynomialExpansion> *polynomials, const double &gamma = 0, const double &seed = 42) {
77 polynomials_ = polynomials;
78 nClusters_ = polynomials->size();
79 fit_ = std::vector<bool>(nClusters_, false);
80 clusterSize_ = std::vector<size_t>(nClusters_, 0);
81 gamma_ = gamma;
82 rng_.seed(seed);
83 }
84
85 void condition(const Eigen::Ref<const Eigen::MatrixXd> &xObs, const Eigen::Ref<const Eigen::VectorXd> &yObs, const Eigen::Ref<const Eigen::VectorXs> &labels) {
86
87 // Get only the unique labels
88 std::set<int> uniqueLabels;
89 for(int i = 0; i < labels.size(); i++) {
90 uniqueLabels.insert(labels(i));
91 }
92 if(uniqueLabels.size() != nClusters_) {
93 throw std::runtime_error("The number of unique labels in the labels vector does not match the number of clusters.");
94 }
95 if(xObs.rows() != yObs.size() || xObs.rows() != labels.size()) {
96 throw std::runtime_error("The number of observations in xObs, yObs and labels must be the same.");
97 }
98
99 // Remap the labels to be in the range [0, nClusters_-1]
100 if(*uniqueLabels.begin() != 0 || *uniqueLabels.rbegin() != nClusters_ - 1) {
101 std::cout << "Remapping labels to be in the range [0, " << nClusters_ - 1 << "]" << std::endl;
102 Eigen::VectorXs remappedLabels = Eigen::VectorXs::Zero(labels.size());
103 std::map<int, int> labelMap;
104 int newLabel = 0;
105 for(const auto &label : uniqueLabels) {
107 }
108 for(int i = 0; i < labels.size(); i++) {
110 }
112 return;
113 }
114
115
116 // Initialize the members
117 nObs_ = xObs.rows();
118 dimX_ = xObs.cols();
119 xObs_ = xObs;
120 yObs_ = yObs;
121
122 // Initialize the containers
123 labels_ = labels;
124 localIndexTable_ = Eigen::VectorXs::Zero(nObs_);
125 centroids_ = std::vector<Eigen::VectorXd>(nClusters_, Eigen::VectorXd::Zero(dimX_));
126
127 // Call the update model function
128 updateModel(std::vector<bool>(nClusters_, true));
129 }
130
131 size_t nClusters() const {
132 return nClusters_;
133 }
134
135 void fit() {
136 #pragma omp parallel for
137 for(size_t i = 0; i < nClusters(); i++) {
138 if(!fit_[i]) {
139 auto index = getIndices(i);
140 auto xObsi = xObs_(index, Eigen::all);
141 auto yObsi = yObs_(index);
142
143 (*polynomials_)[i].fit(xObsi, yObsi);
144 fit_[i] = true;
145 }
146 }
147 }
148
149 size_t nPoints() const {
150 return nObs_;
151 }
152
153 size_t dim() const {
154 return dimX_;
155 }
156
157 size_t getMembership(size_t i) const {
158 return labels_[i];
159 }
160
161 const Eigen::VectorXs &getLabels() const {
162 return labels_;
163 }
164
165 size_t getClusterSize(size_t i) const {
166 return clusterSize_[i];
167 }
168
170 return (*polynomials_)[i];
171 }
172
173 const Eigen::VectorXd &centroid(size_t i) const {
174 return centroids_[i];
175 }
176
178 Eigen::VectorXs indices = Eigen::VectorXs::Zero(clusterSize_[clusterIndex]);
179 size_t counter = 0;
180 for(size_t j = 0; j < nObs_; j++) {
181 if(labels_[j] == clusterIndex) {
182 indices[counter] = j;
183 counter++;
184 }
185 }
186 return indices;
187 }
188
189 void updateModel(const std::vector<bool> &affectedClusters) {
190
191 // Set the observations
192 for(size_t i = 0; i < nClusters_; i++) {
193
194 // Check if the cluster is affected
195 if(!affectedClusters[i]) {
196 continue;
197 }
198
199 // Centroids
200 centroids_[i] = Eigen::VectorXd::Zero(dimX_);
201
202 // Counter for the cluster size
203 size_t counter = 0;
204
205 // Set the observations
206 for(size_t j = 0; j < nObs_; j++) {
207 if(labels_[j] >= 0 && static_cast<size_t>(labels_[j]) == i) {
208
209 // Update the centroid
210 centroids_[i] += xObs_.row(j);
211
212 // Set the membership
213 labels_[j] = i;
214
215 // Set local index for LOO bookkeeping
217
218 // Update the counter
219 counter++;
220 }
221 }
222
223 // Compute the centroid
225
226 // Set the cluster size
228
229 // GP is not fit
230 fit_[i] = false;
231 }
232 }
233
238 void performSwitches(const std::vector<std::pair<size_t, size_t>> &newOwners) {
239
240 // Affected clusters
241 std::vector<bool> affectedClusters(nClusters_, false);
242
243 // Iterate through the points
244 for(size_t i = 0; i < newOwners.size(); i++) {
245
246 // Get the global index
247 size_t globalIndex = newOwners[i].first;
248
249 // Get the new and old owners
250 size_t newOwner = newOwners[i].second;
251 size_t oldOwner = labels_[globalIndex];
252
253 // The clusters are affected
256
257 // Update the membership
259 }
260
261 // Call the update model function
263 }
264
265
266
267 bool isFit(const size_t &clusterIndex) const {
268 return fit_[clusterIndex];
269 }
270
271 void setFit(const size_t &clusterIndex, const bool &fit) {
273 }
274
278 double computeScore(size_t globalIndex) const {
279 size_t owner = labels_[globalIndex];
280 auto [mean, var] = (*polynomials_)[owner].predict(xObs_.row(globalIndex));
281 return -((yObs_(globalIndex) - mean) * (yObs_(globalIndex) - mean) + gamma_ * (xObs_.row(globalIndex).transpose() - centroids_[owner]).squaredNorm());
282 }
283
287 double computeScore(size_t model, size_t globalIndex) const {
288
289 auto [mean, var] = (*polynomials_)[model].predict(xObs_.row(globalIndex));
290 return -((yObs_(globalIndex) - mean) * (yObs_(globalIndex) - mean) + gamma_ * (xObs_.row(globalIndex).transpose() - centroids_[model]).squaredNorm());
291 }
292
293 std::vector<std::pair<size_t, size_t>> switchStep(cmp::classifier::Classifier *classifier, const double &T = 1.0, const size_t &maxAllowedSwitches = 10, const double &minProb = 0.1) {
294
295 // Compute the probabilities for each point to switch to each cluster
296 std::vector<std::vector<double>> probabilities = computeProbabilities(T, classifier, minProb);
297
298 // Compute the probabilities of switching for each point
299 std::vector<double> switchingProbabilities(nObs_, 0.0);
300 for(size_t i = 0; i < nObs_; i++) {
301 // Find the owner of the point
302 size_t owner = labels_[i];
303
304 // Compute the switching probabilities
305 for(size_t j = 0; j < nClusters_; j++) {
306 if(j != owner) {
308 }
309 }
310 }
311
312 // Precompute active indices (non-zero switching mass).
313 std::vector<size_t> activeIndices;
314 activeIndices.reserve(nObs_);
315 for(size_t i = 0; i < nObs_; ++i) {
316 if(switchingProbabilities[i] > 0.0) {
317 activeIndices.push_back(i);
318 }
319 }
320
321 std::vector<std::pair<size_t, size_t>> switches;
323
324 // Iteratively sample points without replacement from active indices.
325 for(size_t s = 0; s < maxAllowedSwitches && !activeIndices.empty(); ++s) {
326 std::vector<double> weights;
327 weights.reserve(activeIndices.size());
328 for(auto idx : activeIndices) {
329 weights.push_back(switchingProbabilities[idx]);
330 }
331
332 std::discrete_distribution<size_t> dist(weights.begin(), weights.end());
333 size_t sampledPos = dist(rng_);
335
336 // Remove selected point from the active pool.
338 activeIndices.pop_back();
339
340 size_t owner = labels_[globalIndex];
341 const auto &probs = probabilities[globalIndex];
342 std::discrete_distribution<size_t> clusterDist(probs.begin(), probs.end());
343 size_t newOwner = clusterDist(rng_);
344
345 if(newOwner != owner) {
346 switches.emplace_back(globalIndex, newOwner);
347 }
348 }
349
350 return switches;
351 }
352
353 std::vector<std::pair<size_t, size_t>> deterministicSwitchStep(cmp::classifier::Classifier *cls, const size_t &maxAllowedSwitches, const double &minProb) {
354
355 std::vector<std::pair<double, std::pair<size_t, size_t>>> allSwitches;
356 allSwitches.reserve(nObs_);
357
358 // Precompute classifier probabilities once.
359 std::vector<std::vector<double>> classifierProbabilities(nObs_);
360 for(size_t i = 0; i < nObs_; i++) {
361 classifierProbabilities[i] = cls->predictProbabilities(xObs_.row(i));
362 }
363
364 for(size_t i = 0; i < nObs_; i++) {
365 const std::vector<double> &prob = classifierProbabilities[i];
366 size_t owner = labels_[i];
367
368 double looScore = computeScore(i);
369
370 double startingScore = 0.0;
371 if(prob[owner] < minProb) {
372 startingScore = -std::numeric_limits<double>::infinity();
373 }
374
375 std::pair<double, size_t> bestCluster{startingScore, owner};
376 for(size_t c = 0; c < nClusters_; c++) {
377 if(c == owner) {
378 continue;
379 } else if(prob[c] <= minProb) {
380 continue;
381 } else {
382 double predScore = computeScore(c, i);
383 double deltaScore = predScore - looScore;
384
385 if(deltaScore > bestCluster.first) {
386 bestCluster.first = deltaScore;
387 bestCluster.second = c;
388 }
389 }
390 }
391
392 if(bestCluster.second != owner) {
393 allSwitches.push_back({bestCluster.first, {i, bestCluster.second}});
394 }
395 }
396
397 std::sort(allSwitches.begin(), allSwitches.end(), [](std::pair<double, std::pair<size_t, size_t>> a, std::pair<double, std::pair<size_t, size_t>> b) {
398 return a.first > b.first;
399 });
400
401 size_t nSwitches = std::min(maxAllowedSwitches, allSwitches.size());
402 std::vector<std::pair<size_t, size_t>> switches(nSwitches);
403 for(size_t i = 0; i < nSwitches; i++) {
404 switches[i] = allSwitches[i].second;
405 }
406
407 return switches;
408 }
409
416 void purgeStep(const size_t &clusterIndex) {
417
418 std::vector<bool> affectedClusters(nClusters_, true);
419
420 // Cycle through the observations
421 for(size_t i = 0; i < nObs_; i++) {
422
423 // Check if the point is in the cluster to purge
424 if(labels_[i] == clusterIndex) {
425
426 // We need to redistribute the point
427 std::pair<size_t, double> chosenCluster = std::make_pair(-1, std::numeric_limits<double>::infinity());
428
429 for(size_t j = 0; j < nClusters_; j++) {
430
431 // Skip the current cluster
432 if(j == clusterIndex) {
433 continue;
434 }
435
436 // Compute the error
437 double error = computeScore(j, i);
438
439 // Check if we have a new minimum
440 if(error < chosenCluster.second) {
441 chosenCluster = std::make_pair(j, error);
442 }
443 }
444
445 // Perform the switch
446 labels_[i] = chosenCluster.first;
447 affectedClusters[chosenCluster.first] = true;
448 }
449 }
450
451 // Purge the cluster
452 polynomials_->erase(polynomials_->begin() + clusterIndex);
453 fit_.erase(fit_.begin() + clusterIndex);
454 clusterSize_.erase(clusterSize_.begin() + clusterIndex);
455 centroids_.erase(centroids_.begin() + clusterIndex);
457 nClusters_--;
458
459 // Now we need to decrease the cluster index of the points
460 for(size_t i = 0; i < nObs_; i++) {
461 if(labels_[i] > clusterIndex) {
462 labels_[i]--;
463 }
464 }
465
466 // Now we need to update the model
468 }
469
470 std::vector<std::vector<double>> computeProbabilities(const double &T, cmp::classifier::Classifier *classifier, const double &minProb = 0.1) const {
471
472 std::vector<std::vector<double>> switchingProbabilities(nObs_, std::vector<double>(nClusters_, 0.0));
473
474 // Precompute classifier probabilities and LOO scores once per point.
475 std::vector<std::vector<double>> classifierProbabilities(nObs_);
476 std::vector<double> looScores(nObs_, 0.0);
477 for(size_t i = 0; i < nObs_; i++) {
480 }
481
482 // Iterate through the points
483 for(size_t i = 0; i < nObs_; i++) {
484
485 // Find the owner of the point
486 size_t owner = labels_[i];
487
488 // Predict the SVM probabilities for the point
489 const std::vector<double> &svmProbabilities = classifierProbabilities[i];
490
491 // Compute the score for the current point
492 double looScore = looScores[i];
494
495 for(size_t j = 0; j < nClusters_; j++) {
496 if(j == owner) {
497 continue;
498 } else {
499
500 // Compute the predictive error
501 double predictiveScore = computeScore(j, i);
502
503 if(svmProbabilities[j] > minProb) {
505 } else {
507 }
508
509 }
510 }
511
512 // Compute the sum of the probabilities
513 double sum = 0.0;
514 for(size_t j = 0; j < nClusters_; j++) {
516 }
517
518 // Normalize safely; if scores are degenerate fall back to SVM probabilities.
519 if(sum < 1e-12 || std::isnan(sum) || std::isinf(sum)) {
520 for(size_t j = 0; j < nClusters_; j++) {
522 }
523 } else {
524 for(size_t j = 0; j < nClusters_; j++) {
526 }
527 }
528
529 }
530
531 // Now we return the probabilities
533 }
534
535 Eigen::MatrixXd confusionMatrix(std::vector<std::vector<double>> switchingProbability) const {
536 Eigen::MatrixXd confusion_num(nClusters_, nClusters_);
537 Eigen::MatrixXd confusion_den(nClusters_, nClusters_);
538 confusion_num.setZero();
539 confusion_den.setZero();
540
541 for(size_t i = 0; i < nObs_; i++) {
542 for(size_t j = 0; j < nClusters_; j++) {
543 for(size_t k = 0; k < nClusters_; k++) {
544
545 // Only consider points that are in cluster j
546 if(labels_[i] == j) {
549 }
550 }
551 }
552 }
553
554 // Divide the matrices elementwise
555 for(size_t i = 0; i < nClusters_; i++) {
556 for(size_t j = 0; j < nClusters_; j++) {
557 if(confusion_den(i, j) != 0) {
559 }
560 }
561 }
562
563 // Now we return the confusion matrix
564 return confusion_num;
565 }
566
567 // Merge two clusters
568 void mergeClusters(const size_t &clusterIndex1, const size_t &clusterIndex2, bool hparGuess = false) {
569
571
572 // Check if the clusters are the same
574 return;
575 }
576
577 std::vector<std::pair<size_t, size_t>> switches;
578
579 // Cycle through the observations
580 for(size_t i = 0; i < nObs_; i++) {
581
582 // Check if the point is in the cluster to purge
583 if(labels_[i] == clusterIndex2) {
584 switches.push_back(std::make_pair(i, clusterIndex1));
585 }
586 }
587
588 // Perform the switches
590
591 // Purge the cluster
593 }
594
595};
596
597} // namespace cmp
600#endif
Manages a clustered set of Polynomial Chaos Expansion (PCE) models for localized regression.
Definition model_cluster_poly.h:42
double computeScore(size_t globalIndex) const
Definition model_cluster_poly.h:278
std::vector< cmp::PolynomialExpansion > * polynomials_
Polynomial expansions representing local models.
Definition model_cluster_poly.h:63
double gamma_
Regularization blending parameter gamma.
Definition model_cluster_poly.h:69
void set(std::vector< cmp::PolynomialExpansion > *polynomials, const double &gamma=0, const double &seed=42)
Definition model_cluster_poly.h:76
~ModelClusterPoly()=default
size_t getMembership(size_t i) const
Definition model_cluster_poly.h:157
std::vector< std::pair< size_t, size_t > > deterministicSwitchStep(cmp::classifier::Classifier *cls, const size_t &maxAllowedSwitches, const double &minProb)
Definition model_cluster_poly.h:353
const Eigen::VectorXd & centroid(size_t i) const
Definition model_cluster_poly.h:173
double computeScore(size_t model, size_t globalIndex) const
Definition model_cluster_poly.h:287
size_t nObs_
Number of training observations.
Definition model_cluster_poly.h:51
const Eigen::VectorXs & getLabels() const
Definition model_cluster_poly.h:161
size_t dimX_
Dimension of input features.
Definition model_cluster_poly.h:52
void performSwitches(const std::vector< std::pair< size_t, size_t > > &newOwners)
Definition model_cluster_poly.h:238
std::vector< size_t > clusterSize_
Number of points assigned to each cluster.
Definition model_cluster_poly.h:66
void fit()
Definition model_cluster_poly.h:135
size_t nClusters() const
Definition model_cluster_poly.h:131
std::vector< std::pair< size_t, size_t > > switchStep(cmp::classifier::Classifier *classifier, const double &T=1.0, const size_t &maxAllowedSwitches=10, const double &minProb=0.1)
Definition model_cluster_poly.h:293
Eigen::VectorXs localIndexTable_
Local coordinate lookup index mapping.
Definition model_cluster_poly.h:58
size_t nClusters_
Number of active clusters.
Definition model_cluster_poly.h:50
Eigen::MatrixXd xObs_
Training input matrix of observations.
Definition model_cluster_poly.h:47
void condition(const Eigen::Ref< const Eigen::MatrixXd > &xObs, const Eigen::Ref< const Eigen::VectorXd > &yObs, const Eigen::Ref< const Eigen::VectorXs > &labels)
Definition model_cluster_poly.h:85
size_t nPoints() const
Definition model_cluster_poly.h:149
std::default_random_engine rng_
Pseudo-random number generator.
Definition model_cluster_poly.h:45
Eigen::VectorXd yObs_
Training target response vector.
Definition model_cluster_poly.h:48
void setFit(const size_t &clusterIndex, const bool &fit)
Definition model_cluster_poly.h:271
void purgeStep(const size_t &clusterIndex)
Definition model_cluster_poly.h:416
std::vector< bool > fit_
Cluster fit/convergence status flag vector.
Definition model_cluster_poly.h:61
std::vector< Eigen::VectorXd > centroids_
Coordinates for each cluster's centroid.
Definition model_cluster_poly.h:62
size_t dim() const
Definition model_cluster_poly.h:153
cmp::PolynomialExpansion & operator[](size_t i)
Definition model_cluster_poly.h:169
void mergeClusters(const size_t &clusterIndex1, const size_t &clusterIndex2, bool hparGuess=false)
Definition model_cluster_poly.h:568
void updateModel(const std::vector< bool > &affectedClusters)
Definition model_cluster_poly.h:189
std::vector< std::vector< double > > computeProbabilities(const double &T, cmp::classifier::Classifier *classifier, const double &minProb=0.1) const
Definition model_cluster_poly.h:470
size_t getClusterSize(size_t i) const
Definition model_cluster_poly.h:165
Eigen::VectorXs labels_
Cluster assignments label vector.
Definition model_cluster_poly.h:55
bool isFit(const size_t &clusterIndex) const
Definition model_cluster_poly.h:267
Eigen::VectorXs getIndices(size_t clusterIndex) const
Definition model_cluster_poly.h:177
Eigen::MatrixXd confusionMatrix(std::vector< std::vector< double > > switchingProbability) const
Definition model_cluster_poly.h:535
ModelClusterPoly()=default
Implements multi-dimensional Polynomial Chaos Expansion (PCE) for spectral surrogate modeling.
Definition poly.h:301
Abstract base class for all classifiers.
Definition classifier.h:41
virtual std::vector< double > predictProbabilities(const Eigen::Ref< const Eigen::VectorXd > &x) const =0
Matrix< size_t, Eigen::Dynamic, 1 > VectorXs
Definition cmp_defines.h:20
Definition classifier.h:17