sgsPy
structurally guided sampling
Loading...
Searching...
No Matches
clhs.h
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * Project: sgs
4 * Purpose: C++ implementation of CLHS
5 * Author: Joseph Meyer
6 * Date: November, 2025
7 *
8 ******************************************************************************/
9
14
15#include <iostream>
16#include <optional>
17#include <random>
18
19#include "utils/access.h"
20#include "utils/existing.h"
21#include "utils/helper.h"
22#include "utils/raster.h"
23#include "utils/vector.h"
24
25#include <boost/unordered/unordered_flat_set.hpp>
26#include <mkl.h>
27#include "oneapi/dal.hpp"
28#include <xoshiro.h>
29
30#define MILLION 1000000
31
32typedef oneapi::dal::homogen_table DALHomogenTable;
33
34namespace sgs {
35namespace clhs {
36
42template <typename T>
43struct Point {
44 T *p_features = nullptr;
45 int x = -1;
46 int y = -1;
47};
48
58template <typename T>
59inline size_t
60getQuantile(T val, std::vector<T>& quantiles) {
61 auto it = std::lower_bound(quantiles.begin(), quantiles.end(), val);
62 return (it == quantiles.end()) ?
63 quantiles.size() :
64 std::distance(quantiles.begin(), it);
65}
66
75template <typename T>
77 private:
78 std::vector<T> features;
79 std::vector<int> x;
80 std::vector<int> y;
81 size_t fi; //features index
82 size_t count;
83 int64_t size;
84 uint64_t ucount;
85
86 //for existing sample points
87 std::vector<T> efeatures;
88 std::vector<int> ex;
89 std::vector<int> ey;
90 size_t efi = 0;
91 size_t ecount = 0;
92
93 std::vector<std::vector<T>> corr;
94
95 int nFeat;
96 int nSamp;
97
98 xso::xoshiro_4x64_plus *p_rng = nullptr;
99 uint64_t mask = 0;
100
101 public:
114 CLHSDataManager(int nFeat, int nSamp, xso::xoshiro_4x64_plus *p_rng, size_t existingCount) {
115 this->nFeat = nFeat;
116 this->nSamp = nSamp;
117 this->count = 0;
118 this->fi = 0;
119 this->size = MILLION;
120 this->features.resize(MILLION * nFeat);
121 this->x.resize(MILLION);
122 this->y.resize(MILLION);
123
124 this->p_rng = p_rng;
125
126 if (existingCount != 0) {
127 this->efeatures.resize(existingCount * static_cast<size_t>(nFeat));
128 this->ex.resize(existingCount);
129 this->ey.resize(existingCount);
130 }
131 }
132
145 inline void
146 addPoint(T *p_features, int x, int y) {
147 for (int f = 0; f < nFeat; f++) {
148 features[this->fi] = p_features[f];
149 this->fi++;
150 }
151
152 this->x[this->count] = x;
153 this->y[this->count] = y;
154 this->count++;
155
156 if (this->count == this->size) {
157 this->features.resize(this->features.size() + MILLION * this->nFeat);
158 this->x.resize(this->x.size() + MILLION);
159 this->y.resize(this->y.size() + MILLION);
160 this->size += MILLION;
161 }
162 }
163
175 inline void
176 addExistingPoint(T *p_features, int x, int y) {
177 for (int f = 0; f < nFeat; f++) {
178 this->efeatures[this->efi] = p_features[f];
179 this->efi++;
180 }
181
182 this->ex[this->ecount] = x;
183 this->ey[this->ecount] = y;
184 this->ecount++;
185 }
186
207 inline void
208 finalize(std::vector<std::vector<T>>& corr) {
209 if (this->count < static_cast<size_t>(this->nSamp)) {
210 throw std::runtime_error("not enough points saved during raster iteration to conduct clhs sampling.");
211 }
212
213 this->corr = corr;
214
215 this->x.resize(this->count);
216 this->y.resize(this->count);
217 this->features.resize(this->count * nFeat);
218 this->ucount = static_cast<uint64_t>(this->count);
219
220 //use bit twiddling to fill the mask
221 this->mask = static_cast<uint64_t>(this->count);
222 this->mask |= this->mask >> 1;
223 this->mask |= this->mask >> 2;
224 this->mask |= this->mask >> 4;
225 this->mask |= this->mask >> 8;
226 this->mask |= this->mask >> 16;
227 this->mask |= this->mask >> 32;
228
229 //resize existing sample vectors
230 this->ex.resize(this->ecount);
231 this->ey.resize(this->ecount);
232 this->features.resize(this->ecount * nFeat);
233 }
234
249 inline uint64_t
251 uint64_t index = ((*p_rng)() >> 11) & mask;
252
253 while (index >= this->ucount) {
254 index = ((*p_rng)() >> 11) & mask;
255 }
256
257 return index;
258 }
259
266 inline void
268 uint64_t index = randomIndex();
269
270 point.p_features = this->features.data() + (index * nFeat);
271 point.x = x[index];
272 point.y = y[index];
273 }
274
289 inline T
290 quantileObjectiveFunc(std::vector<int>& sampleCountPerQuantile) {
291 int retval = 0;
292
293 for (const int& count : sampleCountPerQuantile) {
294 retval += std::abs(count - 1);
295 }
296
297 return static_cast<T>(retval);
298 }
299
313 inline T
314 correlationObjectiveFunc(std::vector<std::vector<T>>& corr) {
315 T retval = 0;
316
317 for (size_t i = 0; i < this->corr.size(); i++) {
318 for (size_t j = 0; j < this->corr[i].size(); j++) {
319 retval += std::abs(corr[i][j] - this->corr[i][j]);
320 }
321 }
322
323 return retval;
324 }
325
333 inline void
334 getExistingFeatures(std::vector<T>& features, std::vector<int>& x, std::vector<int>& y) {
335 features.resize(this->efeatures.size());
336 x.resize(this->ex.size());
337 y.resize(this->ey.size());
338
339 std::memcpy(features.data(), this->efeatures.data(), this->efeatures.size() * sizeof(T));
340 std::memcpy(x.data(), this->ex.data(), this->ex.size() * sizeof(int));
341 std::memcpy(y.data(), this->ey.data(), this->ey.size() * sizeof(int));
342 }
343};
344
408template <typename T>
409inline void
411 std::vector<helper::RasterBandMetaData>& bands,
416 GDALDataType type,
417 std::vector<std::vector<T>>& quantiles,
418 size_t size,
419 int width,
420 int height,
421 int count,
422 int nSamp)
423{
424 std::vector<std::vector<T>> probabilities(count);
425 quantiles.resize(count);
426
427 for (int i = 0; i < count; i++) {
428 probabilities[i].resize(nSamp - 1);
429 quantiles[i].resize(nSamp - 1);
430
431 for (int j = 0; j < nSamp - 1; j++) {
432 probabilities[i][j] = static_cast<T>(j + 1) / static_cast<T>(nSamp);
433 }
434 }
435
436 int xBlockSize = bands[0].xBlockSize;
437 int yBlockSize = bands[0].yBlockSize;
438
439 int xBlocks = (width + xBlockSize - 1) / xBlockSize;
440 int yBlocks = (height + yBlockSize - 1) / yBlockSize;
441
442 double deps = .001;
443 float seps = .001f;
444
445 std::vector<T> corrBuffer(count * xBlockSize * yBlockSize);
446 std::vector<std::vector<T>> quantileBuffers(count);
447 for (int i = 0; i < count; i++) {
448 quantileBuffers[i].resize(xBlockSize * yBlockSize);
449 }
450
451 if (access.used) {
452 access.band.p_buffer = VSIMalloc3(xBlockSize, yBlockSize, access.band.size);
453 }
454
455 //create descriptor for correlation matrix streaming calculation with oneDAL
456 const auto cor_desc = oneapi::dal::covariance::descriptor{}.set_result_options(oneapi::dal::covariance::result_options::cor_matrix);
457 oneapi::dal::covariance::partial_compute_result<> partial_result;
458
459 //create tasks for quantile streaming calculation with MKL
460 std::vector<VSLSSTaskPtr> quantileTasks(count);
461 int status;
462 MKL_INT quant_order_n = quantiles[0].size();
463 MKL_INT p = 1;
464 MKL_INT n = xBlockSize * yBlockSize;
465 MKL_INT nparams = VSL_SS_SQUANTS_ZW_PARAMS_N;
466 MKL_INT xstorage = VSL_SS_MATRIX_STORAGE_ROWS;
467
468 //MKL functions have different versions for single/double precision floating point data
469 if (type == GDT_Float64) {
470 for (int i = 0; i < count; i++) {
471 //reinterpret cast the pointer for compiler reasons
472 status = vsldSSNewTask(
473 &quantileTasks[i],
474 &p,
475 &n,
476 &xstorage,
477 reinterpret_cast<double *>(quantileBuffers[i].data()),
478 0,
479 0
480 );
481 }
482 }
483 else {
484 for (int i = 0; i < count; i++) {
485 //reinterpret cast the pointer for compiler reasons
486 status = vslsSSNewTask(
487 &quantileTasks[i],
488 &p,
489 &n,
490 &xstorage,
491 reinterpret_cast<float *>(quantileBuffers[i].data()),
492 0,
493 0
494 );
495 }
496 }
497
498 int8_t *p_access = reinterpret_cast<int8_t *>(access.band.p_buffer);
499
500 bool calledEditStreamQuantiles = false;
501 void *p_data = reinterpret_cast<void *>(corrBuffer.data());
502
503 for (int yBlock = 0; yBlock < yBlocks; yBlock++) {
504 for (int xBlock = 0; xBlock < xBlocks; xBlock++) {
505 //get block size
506 int xValid, yValid;
507 bands[0].p_band->GetActualBlockSize(xBlock, yBlock, &xValid, &yValid);
508
509 //read bands into memory
510 for (int i = 0; i < count ; i++) {
511 CPLErr err = bands[i].p_band->RasterIO(
512 GF_Read,
513 xBlock * xBlockSize,
514 yBlock * yBlockSize,
515 xValid,
516 yValid,
517 (void *)((size_t)p_data + i * size),
518 xValid,
519 yValid,
520 type,
521 size * static_cast<size_t>(count),
522 size * static_cast<size_t>(count) * static_cast<size_t>(xBlockSize)
523 );
524
525 if (err) {
526 throw std::runtime_error("Error reading data from raster band.");
527 }
528 }
529
530 //calculate rand vals
531 rand.calculateRandValues();
532
533 //read access band into memory if used
534 if (access.used) {
535 rasterBandIO(access.band, access.band.p_buffer, xBlockSize, yBlockSize, xBlock, yBlock, xValid, yValid, true, false);
536 }
537
538 //iterate through pixels
539 n = 0;
540 for (int y = 0; y < yValid; y++) {
541 int index = y * xBlockSize;
542 for (int x = 0; x < xValid; x++) {
543 bool isNan = false;
544 T *p_buff = corrBuffer.data() + (index * count);
545 for (int b = 0; b < count; b++) {
546 T val = p_buff[b];
547 isNan = std::isnan(val) || val == bands[b].nan;
548
549 if (isNan) {
550 break;
551 }
552
553 quantileBuffers[b][n] = val;
554 corrBuffer[n * count + b] = val;
555 }
556
557 if (!isNan) {
558 n++;
559
560 bool accessible = !access.used || p_access[index] != 1;
561
562 if (existing.used && existing.containsIndex(x + xBlock * xBlockSize, y + yBlock * yBlockSize)) {
563 clhs.addExistingPoint(
564 p_buff,
565 xBlock * xBlockSize + x,
566 yBlock * yBlockSize + y
567 );
568 }
569 else if (accessible && rand.next()) {
570 clhs.addPoint(
571 p_buff,
572 xBlock * xBlockSize + x,
573 yBlock * yBlockSize + y
574 );
575 }
576 }
577 index++;
578 }
579 }
580
581 if (n == 0) {
582 continue;
583 }
584
585 //MKL functions have different versions for single/double precision floating point data
586 if (type == GDT_Float64) {
587 if (!calledEditStreamQuantiles) {
588 for (int i = 0; i < count; i++) {
589 //reinterpret cast the pointers for compiler reasons
590 status = vsldSSEditStreamQuantiles(
591 quantileTasks[i],
592 &quant_order_n,
593 reinterpret_cast<double *>(probabilities[i].data()),
594 reinterpret_cast<double *>(quantiles[i].data()),
595 &nparams,
596 &deps
597 );
598 }
599 calledEditStreamQuantiles = true;
600 }
601 for (int i = 0; i < count; i++) {
602 status = vsldSSCompute(
603 quantileTasks[i],
604 VSL_SS_STREAM_QUANTS,
605 VSL_SS_METHOD_SQUANTS_ZW_FAST
606 );
607 }
608 }
609 else { //type == GDT_Float32
610 if (!calledEditStreamQuantiles) {
611 for (int i = 0; i < count; i++) {
612 //reinterpret cast the pointers for compiler reasons
613 status = vslsSSEditStreamQuantiles(
614 quantileTasks[i],
615 &quant_order_n,
616 reinterpret_cast<float *>(probabilities[i].data()),
617 reinterpret_cast<float *>(quantiles[i].data()),
618 &nparams,
619 &seps
620 );
621 }
622 calledEditStreamQuantiles = true;
623 }
624 for (int i = 0; i < count; i++) {
625 status = vslsSSCompute(
626 quantileTasks[i],
627 VSL_SS_STREAM_QUANTS,
628 VSL_SS_METHOD_SQUANTS_ZW_FAST
629 );
630 }
631
632 }
633
634 //update correlation matrix calculations
635 DALHomogenTable table = DALHomogenTable(corrBuffer.data(), n, count, [](const T *){}, oneapi::dal::data_layout::row_major);
636 partial_result = oneapi::dal::partial_compute(cor_desc, partial_result, table);
637 }
638 }
639
640 if (access.used) {
641 VSIFree(access.band.p_buffer);
642 }
643
644 //calculate and update clhs data manager with correlation matrix
645 auto result = oneapi::dal::finalize_compute(cor_desc, partial_result);
646 auto correlation = result.get_cor_matrix();
647
648 oneapi::dal::row_accessor<const T> acc {correlation};
649
650 n = 0;
651 std::vector<std::vector<T>> corr(count);
652 for (int i = 0; i < count; i++) {
653 corr[i].resize(count);
654 auto row = acc.pull({i, i + 1});
655
656 for (int j = 0; j < count; j++) {
657 corr[i][j] = row[j];
658 }
659
660 status = (type == GDT_Float64) ?
661 vsldSSCompute(quantileTasks[i], VSL_SS_STREAM_QUANTS, VSL_SS_METHOD_SQUANTS_ZW) :
662 vslsSSCompute(quantileTasks[i], VSL_SS_STREAM_QUANTS, VSL_SS_METHOD_SQUANTS_ZW);
663 status = vslSSDeleteTask(&quantileTasks[i]);
664 }
665
666 clhs.finalize(corr);
667}
668
704template <typename T>
705inline void
706selectSamples(std::vector<std::vector<T>>& quantiles,
708 xso::xoshiro_4x64_plus& rng,
710 size_t replace,
711 size_t iterations,
712 size_t nSamp,
713 size_t nFeat,
714 OGRLayer *p_layer,
715 double *GT,
716 bool plot,
717 std::vector<double>& xCoords,
718 std::vector<double>& yCoords)
719{
720 std::vector<T> features;
721 std::vector<int> x;
722 std::vector<int> y;
723
724 std::vector<int> sampleCountPerQuantile(nFeat * nSamp, 0); //nFeat x nSamp 2d array
725 std::vector<int> quantilesOfEachSample(nSamp * nFeat, 0); //nSamp x nFeat 2d array
726
727 //if there are existing samples, add all of them.
728 if (existing.used) {
729 clhs.getExistingFeatures(features, x, y);
730 }
731
732 //Then, remove up to 'replace' number of them which are redundant
733 //'redundant' samples are samples which cause over-representation in feature quantiles
734
735 size_t neSamples = x.size(); //number of existing samples
736 if (neSamples > 0 && replace != 0) {
737 for (size_t si = 0; si < neSamples; si++) {
738 for (size_t fi = 0; fi < nFeat; fi++) {
739 T val = features[(si * nFeat) + fi];
740 size_t q = getQuantile<T>(val, quantiles[fi]);
741 sampleCountPerQuantile[(fi * nSamp) + q]++;
742 quantilesOfEachSample[(si * nFeat) + fi] = q;
743 }
744 }
745
746 while (replace > 0 && neSamples > 0) {
747 size_t worstRedundancy = 0;
748 size_t worstRedundancyIndex = 0;
749
750 //get the sample with the worst redundancy. In other words, the one which is in the
751 //most over-represented quantile across all features
752 for (size_t si = 0; si < neSamples; si++) {
753 size_t curSampleRedundancy = 0;
754 for (size_t fi = 0; fi < nFeat; fi++) {
755 size_t q = quantilesOfEachSample[(si * nFeat) + fi];
756 curSampleRedundancy += sampleCountPerQuantile[(fi * nSamp) + q];
757 }
758 if (curSampleRedundancy > worstRedundancy) {
759 worstRedundancy = curSampleRedundancy;
760 worstRedundancyIndex = si;
761 }
762 }
763
764 //if the remaining samples are already forming a partial latin hypercube
765 //(no more than 1 sample per quantile of each feature)
766 //then don't remove any more indices!
767 if (worstRedundancy == nFeat) {
768 break;
769 }
770
771 //replace the most redundant sample with the last sample in the vector, and decrease
772 //the size of the vector by 1. But first, adjust the values of sampleCountPerQuantile
773 size_t si = worstRedundancyIndex;
774 for (size_t fi = 0; fi < nFeat; fi++) {
775 size_t q = quantilesOfEachSample[(si * nFeat) + fi];
776 sampleCountPerQuantile[(fi * nSamp) + q]--;
777 }
778
779 x[si] = x[neSamples - 1];
780 y[si] = y[neSamples - 1];
781 std::memcpy(features.data() + (si * nFeat),
782 features.data() + ((neSamples - 1) * nFeat),
783 sizeof(T) * nFeat);
784 std::memcpy(quantilesOfEachSample.data() + (si * nFeat),
785 quantilesOfEachSample.data() + ((neSamples - 1) * nFeat),
786 sizeof(int) * nFeat);
787
788 neSamples--;
789 replace--;
790 }
791 }
792
793 //NOW, the neSamples variable contains the number of existing samples which MUST be kept.
794 //This number also happens to be the first index of the remaining space in the vector which
795 //may be filled in with non-existing samples. If there were no existing samples this value
796 //is 0.
797 size_t starti = neSamples;
798 boost::unordered::unordered_flat_set<uint64_t> points;
799
800 //Add all of the existing samples to the output layer, and add to indices map
801 helper::Field fieldExistingTrue("existing", 1);
802 for (size_t si = 0; si < neSamples; si++) {
803 OGRPoint point = existing.getPoint(x[si], y[si]);
804 helper::addPoint(&point, p_layer, &fieldExistingTrue);
805
806 if (plot) {
807 xCoords.push_back(point.getX());
808 yCoords.push_back(point.getY());
809 }
810
811 points.insert((((uint64_t) x[si]) << 32) | ((uint64_t) y[si]));
812 }
813
814 //if there are already enough (existing) samples, return and don't add any more
815 if (starti >= nSamp) {
816 return;
817 }
818
819 std::uniform_real_distribution<T> dist(0.0, 1.0);
820 std::uniform_int_distribution<size_t> indexDist(starti, nSamp - 1);
821
822 std::vector<std::vector<T>> corr(nFeat);
823 for (int i = 0; i < nFeat; i++) {
824 corr[i].resize(nFeat);
825 }
826
827 features.resize(nSamp * nFeat);
828 x.resize(nSamp);
829 y.resize(nSamp);
830
831 //get first random samples
832 int i = starti;
833 Point<T> p;
834 while (i < nSamp) {
835 clhs.getRandomPoint(p);
836
837 if (points.contains((((uint64_t) p.x) << 32) | ((uint64_t) p.y))) {
838 continue;
839 }
840
841 x[i] = p.x;
842 y[i] = p.y;
843
844 for (int f = 0; f < nFeat; f++) {
845 T val = p.p_features[f];
846 features[(i * nFeat) + f] = val;
847
848 int q = getQuantile<T>(val, quantiles[f]);
849 sampleCountPerQuantile[(f * nSamp) + q]++;
850 quantilesOfEachSample[(i * nFeat) + f] = q;
851 }
852
853 points.insert((((uint64_t) p.x) << 32) | ((uint64_t) p.y));
854 i++;
855 }
856
857 //define covariance calculation
858 DALHomogenTable table = DALHomogenTable(features.data(), nSamp, nFeat, [](const T *){}, oneapi::dal::data_layout::row_major);
859 const auto cor_desc = oneapi::dal::covariance::descriptor{}.set_result_options(oneapi::dal::covariance::result_options::cor_matrix);
860 const auto result = oneapi::dal::compute(cor_desc, table);
861 oneapi::dal::row_accessor<const T> acc {result.get_cor_matrix()};
862 for (int i = 0; i < nFeat; i++) {
863 auto row = acc.pull({i, i + 1});
864
865 for (int j = 0; j < nFeat; j++) {
866 corr[i][j] = row[j];
867 }
868 }
869
870 double temp = 1;
871 double d = temp / static_cast<double>(iterations);
872
873 T obj = 0;
874 T objQ = clhs.quantileObjectiveFunc(sampleCountPerQuantile);
875 T objC = clhs.correlationObjectiveFunc(corr);
876
877 obj = objQ + objC;
878
879 //features of old (before random new index) index
880 std::vector<T> oldf(nFeat);
881
882 //begin annealing schedule. If we have a perfect latin hypercube -- or if we pass enough iterations -- stop iterating.
883 while (temp > 0 && objQ != 0) {
884 size_t i; //the index within the indices, x, y, and features vector so we know what to swap without searching
885 if (dist(rng) < 0.5) {
886 //50% of the time, choose a random sample to replace
887 i = indexDist(rng);
888 }
889 else {
890 //50% of the time, choose the worst sample to replace
891
892 //get the sample with the worst redundancy. In other words, the one which is in the
893 //most over-represented quantile across all features
894 size_t worstRedundancyIndex = 0;
895 size_t worstRedundancy = 0;
896 for (size_t si = starti; si < nSamp; si++) {
897 size_t curSampleRedundancy = 0;
898 for (size_t fi = 0; fi < nFeat; fi++) {
899 size_t q = quantilesOfEachSample[(si * nFeat) + fi];
900 curSampleRedundancy += sampleCountPerQuantile[(fi * nSamp) + q];
901 }
902 if (curSampleRedundancy > worstRedundancy) {
903 worstRedundancy = curSampleRedundancy;
904 worstRedundancyIndex = si;
905 }
906 }
907 i = worstRedundancyIndex;
908 }
909
910 //move selected replacement to 'oldf' vector to retain the old values in case we revert back
911 //to that state
912 std::memcpy(oldf.data(), features.data() + (i * nFeat), nFeat * sizeof(T));
913
914 //select a new index
915 Point<T> p;
916 clhs.getRandomPoint(p);
917 while (points.contains((((uint64_t) p.x) << 32) | ((uint64_t) p.y))) {
918 clhs.getRandomPoint(p);
919 }
920
921 //move new features into feature vector
922 std::memcpy(features.data() + (i * nFeat), p.p_features, nFeat * sizeof(T));
923
924 //recalculate sample count per quantile
925 std::vector<int> oldq(nFeat);
926 std::vector<int> newq(nFeat);
927 for (int f = 0; f < nFeat; f++) {
928 //decrement based removal of on old features
929 int q = getQuantile(oldf[f], quantiles[f]);
930 oldq[f] = q;
931 sampleCountPerQuantile[(f * nSamp) + q]--;
932
933 //increment based on inputof new features
934 q = getQuantile(p.p_features[f], quantiles[f]);
935 newq[f] = q;
936 sampleCountPerQuantile[(f * nSamp) + q]++;
937 }
938
939 //recalculate objective function from quantiles
940 T newObjQ = clhs.quantileObjectiveFunc(sampleCountPerQuantile);
941
942 //recalculate correlation matrix
943 const auto result = oneapi::dal::compute(cor_desc, table); // we update the table in place
944 oneapi::dal::row_accessor<const T> acc {result.get_cor_matrix()};
945 for (int j = 0; j < nFeat; j++) {
946 auto row = acc.pull({j, j + 1});
947
948 for (int k = 0; k < nFeat; k++) {
949 corr[j][k] = row[k];
950 }
951 }
952
953 //recalculate objective function from correlation matrix
954 T newObjC = clhs.correlationObjectiveFunc(corr);
955
956 T newObj = newObjQ + newObjC;
957 T delta = newObj - obj;
958
959 bool keep = dist(rng) < std::exp(-1 * delta / temp);
960
961 if (keep) {
962 //update the new changes
963 points.erase((((uint64_t) x[i]) << 32) | ((uint64_t) y[i]));
964 points.insert((((uint64_t) p.x) << 32) | ((uint64_t) p.y));
965
966 x[i] = p.x;
967 y[i] = p.y;
968
969 std::memcpy(quantilesOfEachSample.data() + (i * nFeat), newq.data(), sizeof(int) * nFeat);
970
971 objC = newObjC;
972 objQ = newObjQ;
973 obj = newObj;
974 }
975 else {
976 //revert back to old changes
977 for (int f = 0; f < nFeat; f++) {
978 sampleCountPerQuantile[(f * nSamp) + newq[f]]--;
979 sampleCountPerQuantile[(f * nSamp) + oldq[f]]++;
980 }
981
982 std::memcpy(
983 reinterpret_cast<void *>(features.data() + (i * nFeat)),
984 reinterpret_cast<void *>(oldf.data()),
985 nFeat * sizeof(T)
986 );
987 }
988
989 //update annealing temperature
990 temp -= d;
991 }
992
993 //add samples to output layer
994 helper::Field fieldExistingFalse("existing", 0);
995 for (int i = starti ; i < nSamp; i++) {
996 const auto [xCoord, yCoord] = helper::sample_to_point(GT, x[i], y[i]);
997 OGRPoint point = OGRPoint(xCoord, yCoord);
998 existing.used ?
999 helper::addPoint(&point, p_layer, &fieldExistingFalse) :
1000 helper::addPoint(&point, p_layer);
1001
1002 if (plot) {
1003 xCoords.push_back(xCoord);
1004 yCoords.push_back(yCoord);
1005 }
1006 }
1007}
1008
1038std::tuple<std::vector<std::vector<double>>, vector::GDALVectorWrapper *>
1040 raster::GDALRasterWrapper *p_raster,
1041 int nSamp,
1042 int iterations,
1043 vector::GDALVectorWrapper *p_access,
1044 std::string layerName,
1045 double buffInner,
1046 double buffOuter,
1047 vector::GDALVectorWrapper *p_existing,
1048 size_t replace,
1049 bool plot,
1050 std::optional<uint64_t> random_state,
1051 std::string tempFolder,
1052 std::string filename)
1053{
1054 GDALAllRegister();
1055
1056 int width = p_raster->getWidth();
1057 int height = p_raster->getHeight();
1058 int nFeat = p_raster->getBandCount();
1059 double *GT = p_raster->getGeotransform();
1060
1061 std::vector<double> xCoords, yCoords;
1062
1063 std::vector<helper::RasterBandMetaData> bands(p_raster->getBandCount());
1064 for (int i = 0; i < nFeat; i++) {
1065 bands[i].p_band = p_raster->getRasterBand(i);
1066 bands[i].type = p_raster->getRasterBandType(i);
1067 bands[i].size = p_raster->getRasterBandTypeSize(i);
1068 bands[i].nan = bands[i].p_band->GetNoDataValue();
1069 bands[i].p_band->GetBlockSize(&bands[i].xBlockSize, &bands[i].yBlockSize);
1070 }
1071
1072 //create output dataset before doing anything which will take a long time in case of failure.
1073 GDALDriver *p_driver = GetGDALDriverManager()->GetDriverByName("MEM");
1074 if (!p_driver) {
1075 throw std::runtime_error("unable to create output sample dataset driver.");
1076 }
1077 GDALDataset *p_samples = p_driver->Create("", 0, 0, 0, GDT_Unknown, nullptr);
1078 if (!p_samples) {
1079 throw std::runtime_error("unable to create output dataset with driver.");
1080 }
1081
1082 vector::GDALVectorWrapper *p_wrapper = new vector::GDALVectorWrapper(p_samples, std::string(p_raster->getDataset()->GetProjectionRef()));
1083 OGRLayer *p_layer = p_samples->CreateLayer("samples", p_wrapper->getSRS(), wkbPoint, nullptr);
1084 if (!p_layer) {
1085 throw std::runtime_error("unable to create output dataset layer.");
1086 }
1087
1089 p_access,
1090 p_raster,
1091 layerName,
1092 buffInner,
1093 buffOuter,
1094 true,
1095 tempFolder,
1096 bands[0].xBlockSize,
1097 bands[0].yBlockSize
1098 );
1099
1101 p_existing,
1102 p_raster,
1103 GT,
1104 width,
1105 p_layer,
1106 false,
1107 xCoords,
1108 yCoords,
1109 false
1110 );
1111
1112 //fast random number generator using xoshiro256++
1113 //https://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
1114 xso::xoshiro_4x64_plus rng = random_state ? xso::xoshiro_4x64_plus(*random_state) : xso::xoshiro_4x64_plus();
1115 uint64_t multiplier = helper::getProbabilityMultiplier(
1116 width,
1117 height,
1118 p_raster->getPixelWidth(),
1119 p_raster->getPixelHeight(),
1120 8,
1121 MILLION * 100,
1122 false,
1123 access.area
1124 );
1125 helper::RandValController rand(bands[0].xBlockSize, bands[0].yBlockSize, multiplier, &rng);
1126
1127 //get data type for all bands
1128 GDALDataType type = GDT_Float32;
1129 for (const helper::RasterBandMetaData& band : bands) {
1130 if (band.type == GDT_Float64) {
1131 type = GDT_Float64;
1132 break;
1133 }
1134 }
1135
1136 if (type == GDT_Float64) {
1137 std::vector<std::vector<double>> quantiles;
1138
1139 //create instance of data management class
1140 CLHSDataManager<double> clhs(nFeat, nSamp, &rng, existing.count());
1141
1142 //read raster, calculating quantiles, correlation matrix, and adding points to sample from.
1143 readRaster<double>(bands, clhs, access, existing, rand, type, quantiles, sizeof(double), width, height, nFeat, nSamp);
1144
1145 //select samples and add them to output layer
1146 selectSamples<double>(quantiles, clhs, rng, existing, replace, iterations, nSamp, nFeat, p_layer, GT, plot, xCoords, yCoords);
1147 }
1148 else { //type == GDT_Float32
1149 std::vector<std::vector<float>> quantiles;
1150
1151 //create instance of data management class
1152 CLHSDataManager<float> clhs(nFeat, nSamp, &rng, existing.count());
1153
1154 //read raster, calculating quantiles, correlation matrix, and adding points to sample from.
1155 readRaster<float>(bands, clhs, access, existing, rand, type, quantiles, sizeof(float), width, height, nFeat, nSamp);
1156
1157 //select samples and add them to output layer
1158 selectSamples<float>(quantiles, clhs, rng, existing, replace, iterations, nSamp, nFeat, p_layer, GT, plot, xCoords, yCoords);
1159 }
1160
1161 if (filename != "") {
1162 try {
1163 p_wrapper->write(filename);
1164 }
1165 catch (const std::exception& e) {
1166 std::cout << "Exception thrown trying to write file: " << e.what() << std::endl;
1167 }
1168 }
1169
1170 return {{xCoords, yCoords}, p_wrapper};
1171}
1172
1173} //namespace clhs
1174} //namespace sgs
Definition clhs.h:76
void finalize(std::vector< std::vector< T > > &corr)
Definition clhs.h:208
CLHSDataManager(int nFeat, int nSamp, xso::xoshiro_4x64_plus *p_rng, size_t existingCount)
Definition clhs.h:114
T quantileObjectiveFunc(std::vector< int > &sampleCountPerQuantile)
Definition clhs.h:290
void addExistingPoint(T *p_features, int x, int y)
Definition clhs.h:176
T correlationObjectiveFunc(std::vector< std::vector< T > > &corr)
Definition clhs.h:314
void getExistingFeatures(std::vector< T > &features, std::vector< int > &x, std::vector< int > &y)
Definition clhs.h:334
void getRandomPoint(Point< T > &point)
Definition clhs.h:267
uint64_t randomIndex()
Definition clhs.h:250
void addPoint(T *p_features, int x, int y)
Definition clhs.h:146
Definition helper.h:976
void calculateRandValues(void)
Definition helper.h:1019
bool next(void)
Definition helper.h:1034
Definition raster.h:57
GDALDataset * getDataset()
Definition raster.h:429
int getWidth()
Definition raster.h:467
size_t getRasterBandTypeSize(int band)
Definition raster.h:735
double * getGeotransform()
Definition raster.h:590
GDALDataType getRasterBandType(int band)
Definition raster.h:724
int getBandCount()
Definition raster.h:485
int getHeight()
Definition raster.h:476
GDALRasterBand * getRasterBand(int band)
Definition raster.h:696
double getPixelWidth()
Definition raster.h:555
double getPixelHeight()
Definition raster.h:565
Definition vector.h:46
OGRSpatialReference * getSRS(void)
Definition vector.h:470
void write(std::string filename)
Definition vector.h:410
#define MILLION
Definition clhs.h:30
size_t getQuantile(T val, std::vector< T > &quantiles)
Definition clhs.h:60
void selectSamples(std::vector< std::vector< T > > &quantiles, CLHSDataManager< T > &clhs, xso::xoshiro_4x64_plus &rng, existing::Existing &existing, size_t replace, size_t iterations, size_t nSamp, size_t nFeat, OGRLayer *p_layer, double *GT, bool plot, std::vector< double > &xCoords, std::vector< double > &yCoords)
Definition clhs.h:706
void readRaster(std::vector< helper::RasterBandMetaData > &bands, CLHSDataManager< T > &clhs, access::Access &access, existing::Existing &existing, helper::RandValController &rand, GDALDataType type, std::vector< std::vector< T > > &quantiles, size_t size, int width, int height, int count, int nSamp)
Definition clhs.h:410
std::tuple< std::vector< std::vector< double > >, vector::GDALVectorWrapper * > clhs(raster::GDALRasterWrapper *p_raster, int nSamp, int iterations, vector::GDALVectorWrapper *p_access, std::string layerName, double buffInner, double buffOuter, vector::GDALVectorWrapper *p_existing, size_t replace, bool plot, std::optional< uint64_t > random_state, std::string tempFolder, std::string filename)
Definition clhs.h:1039
uint64_t getProbabilityMultiplier(double width, double height, double pixelWidth, double pixelHeight, int startMult, int numSamples, bool useMindist, double accessibleArea)
Definition helper.h:940
std::pair< double, double > sample_to_point(double *GT, Index &index)
Definition helper.h:1108
void addPoint(OGRPoint *p_point, OGRLayer *p_layer)
Definition helper.h:700
Definition access.h:23
Definition dist.h:20
Definition existing.h:27
Definition quantiles.h:24
Definition pca.h:23
Definition access.h:30
Definition clhs.h:43
T * p_features
Definition clhs.h:44
int x
Definition clhs.h:45
int y
Definition clhs.h:46
Definition existing.h:38
Definition helper.h:46
Definition helper.h:87