In this method we perform a tesselation, then we assign [quantity] to vertices and walk the tesselation assigning the particles to Watershed basins Quantity is a vector of size NumPart.
When quantity is the density or inverse density of the particle then we get a Void or Cluster finder
If the buffer is too small the results will not be perfect, we give warnings when this is the case (instead of just throwing) as some times this is fine. To be sure of the result its a good idea to try with a smaller number of CPUs or large buffer and check that the results match
737 {
738
739 assert(quantity.size() == NumPart);
741
742
744 using Vertex_handle = typename decltype(dt)::Vertex_handle;
745
746
747 [[maybe_unused]] size_t count_left = 0;
748 [[maybe_unused]] size_t count_right = 0;
749 [[maybe_unused]] size_t recv_left = 0;
750 [[maybe_unused]] size_t recv_right = 0;
751 [[maybe_unused]] size_t nboundary = 0;
752
753
754
756 assert(vs.size() == NumPart);
757 for (size_t i = 0; i < NumPart; i++) {
758 vs[i]->info().quantity = quantity[i];
760 }
761
762
763#ifdef USE_MPI
767
768
769
770
771 count_left = 0;
772 count_right = 0;
773 for (size_t i = 0; i < NumPart; i++) {
776 count_left++;
778 count_right++;
779 }
780
781
782 MPI_Status status;
783 MPI_Sendrecv(&count_left,
784 sizeof(count_left),
785 MPI_CHAR,
786 LeftTask,
787 0,
788 &recv_right,
789 sizeof(recv_right),
790 MPI_CHAR,
791 RightTask,
792 0,
793 MPI_COMM_WORLD,
794 &status);
795 MPI_Sendrecv(&count_right,
796 sizeof(count_right),
797 MPI_CHAR,
798 RightTask,
799 0,
800 &recv_left,
801 sizeof(recv_left),
802 MPI_CHAR,
803 LeftTask,
804 0,
805 MPI_COMM_WORLD,
806 &status);
807
808
809 std::vector<QuantityType> quantity_to_send_left(count_left);
810 std::vector<QuantityType> quantity_to_send_right(count_right);
811 std::vector<QuantityType> quantity_to_recv_left(recv_left);
812 std::vector<QuantityType> quantity_to_recv_right(recv_right);
813 count_left = 0;
814 count_right = 0;
815 for (size_t i = 0; i < NumPart; i++) {
818 quantity_to_send_left[count_left] = quantity[i];
819 count_left++;
820 }
822 quantity_to_send_right[count_right] = quantity[i];
823 count_right++;
824 }
825 }
826
827
828 MPI_Sendrecv(quantity_to_send_left.data(),
830 MPI_CHAR,
831 LeftTask,
832 0,
833 quantity_to_recv_right.data(),
835 MPI_CHAR,
836 RightTask,
837 0,
838 MPI_COMM_WORLD,
839 &status);
840 MPI_Sendrecv(quantity_to_send_right.data(),
842 MPI_CHAR,
843 RightTask,
844 0,
845 quantity_to_recv_left.data(),
847 MPI_CHAR,
848 LeftTask,
849 0,
850 MPI_COMM_WORLD,
851 &status);
852
853
854
856 nboundary = recv_left + recv_right;
857 assert(vs_boundary.size() == nboundary);
858 for (size_t i = 0; i < recv_left; i++) {
859 vs_boundary[i]->info().quantity = quantity_to_recv_left[i];
861 }
862 for (size_t i = 0; i < recv_right; i++) {
863 vs_boundary[i + recv_left]->info().quantity = quantity_to_recv_right[i];
865 }
866
867
868 for (size_t i = 0; i < nboundary; i++) {
869 auto v = vs_boundary[i];
870 std::vector<Vertex_handle> vertices;
871 dt.adjacent_vertices(v, std::back_inserter(vertices));
873 for (auto & vnew : vertices) {
875 continue;
876 if (vnew->info().quantity < min_quantity)
877 min_quantity = vnew->info().quantity;
878 }
879 v->info().min_quantity_nbor = min_quantity;
880 if (v->info().min_quantity_nbor ==
Infinity) {
881 std::cout << "[WatershedGeneral] Warning boundary particle has no normal nbors " << v->point()
883 }
884 }
885 }
886#endif
887
888
889 std::vector<char> is_minimum(NumPart, 0);
890 for (size_t i = 0; i < NumPart; i++) {
891 std::vector<Vertex_handle> vertices;
892 dt.adjacent_vertices(vs[i], std::back_inserter(vertices));
893 auto quantity = vs[i]->info().quantity;
894 is_minimum[i] = 1;
895 for (auto & v : vertices) {
896 if (v == vs[i])
897 continue;
898 if (v->info().quantity < quantity) {
899 is_minimum[i] = 0;
900 break;
901 }
902 }
903 }
904
905
906 size_t nminima = 0;
907 for (size_t i = 0; i < NumPart; i++) {
908 if (is_minimum[i] == 1) {
909 nminima++;
910 }
911 }
912
913
916#ifdef USE_MPI
917 MPI_Allreduce(MPI_IN_PLACE, nminima_per_task.data(),
FML::NTasks, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
918#endif
919 int id_start = 0;
921 id_start += nminima_per_task[i];
922 size_t ntotal_minima = 0;
924 ntotal_minima += size_t(nminima_per_task[i]);
925
927 std::cout << "[WatershedGeneral] We found " << ntotal_minima << " minimum points\n";
928
929
930
931 decltype(vs) vminima(nminima);
932 nminima = 0;
933 for (size_t i = 0; i < NumPart; i++) {
934 if (is_minimum[i]) {
935 auto v = vs[i];
936 vminima[nminima] = v;
937 v->info().WatershedID = id_start + nminima;
938 nminima++;
939 }
940 }
941 is_minimum.clear();
942 is_minimum.shrink_to_fit();
943
944
945 for (size_t i = 0; i < NumPart; i++) {
946 auto v = vs[i];
947 std::vector<Vertex_handle> vertices;
948 dt.adjacent_vertices(v, std::back_inserter(vertices));
949
951 for (auto & vnew : vertices) {
953 continue;
954 if (vnew->info().quantity < min_quantity)
955 min_quantity = vnew->info().quantity;
956 }
958 v->info().min_quantity_nbor = min_quantity;
959 }
960
961
962
963 std::function<int(Vertex_handle,
IDType)> recursive_through_vertices = [&](Vertex_handle v,
IDType id) {
965 return 0;
966
967
968 int count = 0;
969 v->info().WatershedID = id;
971 count++;
972
973
974 std::vector<Vertex_handle> vertices;
975 dt.adjacent_vertices(v, std::back_inserter(vertices));
976 auto quantity = v->info().quantity;
977 for (auto & vnew : vertices) {
978 if (vnew == v)
979 continue;
980 if (vnew->info().WatershedID == id)
981 continue;
983 continue;
984 if (quantity > vnew->info().min_quantity_nbor)
985 continue;
986 if (vnew->info().quantity > quantity) {
988 count++;
989 }
990 vnew->info().WatershedID = id;
991 count += recursive_through_vertices(vnew, id);
992 }
993 }
994 return count;
995 };
996
997
998 size_t ntot = 0;
999 for (size_t i = 0; i < nminima; i++) {
1000 auto v = vminima[i];
1001 auto id = v->info().WatershedID;
1002 ntot += recursive_through_vertices(v, id);
1003 }
1004#ifdef DEBUG_TESSELATION
1005 std::cout << "We have " << nminima << " minima with " << ntot << " of " << NumPart
1007#endif
1008
1009
1010
1011
1012
1013
1014#ifdef USE_MPI
1016
1017 std::vector<IDType> watershed_id_to_send_left(count_left);
1018 std::vector<IDType> watershed_id_to_send_right(count_right);
1019 std::vector<IDType> watershed_id_to_recv_left(recv_left);
1020 std::vector<IDType> watershed_id_to_recv_right(recv_right);
1021
1022
1023
1024 for (int s = 0; s < 3; s++) {
1026 assert(vs.size() == NumPart);
1027 count_left = 0;
1028 count_right = 0;
1029 for (size_t i = 0; i < NumPart; i++) {
1030 auto id = vs[i]->info().WatershedID;
1033 watershed_id_to_send_left[count_left] = id;
1034 count_left++;
1035 }
1037 watershed_id_to_send_right[count_right] = id;
1038 count_right++;
1039 }
1040 }
1041 assert(watershed_id_to_send_left.size() == count_left);
1042 assert(watershed_id_to_send_right.size() == count_right);
1043
1044 MPI_Status status;
1045 MPI_Sendrecv(watershed_id_to_send_left.data(),
1046 sizeof(
IDType) * count_left,
1047 MPI_CHAR,
1048 LeftTask,
1049 0,
1050 watershed_id_to_recv_right.data(),
1051 sizeof(
IDType) * recv_right,
1052 MPI_CHAR,
1053 RightTask,
1054 0,
1055 MPI_COMM_WORLD,
1056 &status);
1057 MPI_Sendrecv(watershed_id_to_send_right.data(),
1058 sizeof(
IDType) * count_right,
1059 MPI_CHAR,
1060 RightTask,
1061 0,
1062 watershed_id_to_recv_left.data(),
1063 sizeof(
IDType) * recv_left,
1064 MPI_CHAR,
1065 LeftTask,
1066 0,
1067 MPI_COMM_WORLD,
1068 &status);
1069
1070
1072 for (size_t i = 0; i < recv_left; i++) {
1073 auto v = vs_boundary[i];
1074 auto id = watershed_id_to_recv_left[i];
1075 v->info().WatershedID = id;
1076 }
1077 for (size_t i = 0; i < recv_right; i++) {
1078 auto v = vs_boundary[i + recv_left];
1079 auto id = watershed_id_to_recv_right[i];
1080 v->info().WatershedID = id;
1081 }
1082
1083
1084 int ntot = 0;
1085 for (size_t i = 0; i < nboundary; i++) {
1086 auto v = vs_boundary[i];
1087 auto id = v->info().WatershedID;
1089 continue;
1090 ntot += recursive_through_vertices(v, id);
1091 }
1092#ifdef DEBUG_TESSELATION
1093 std::cout <<
"In merging on " <<
FML::ThisTask <<
" we assigned " << ntot <<
"\n";
1094#endif
1095
1097 if (ntot == 0)
1098 break;
1100 std::cout << "[WatershedGeneral] Warning we merged 3 times and still not done, buffer should "
1101 "be larger, cannot guarantee the results are perfect\n";
1102 }
1103 }
1104#endif
1105
1106
1107 long long int assigned = 0;
1108 for (size_t i = 0; i < NumPart; i++) {
1110 assigned++;
1111 } else {
1112 std::cout <<
"[WatershedGeneral] Warning: on task " <<
FML::ThisTask <<
" particle " << i
1113 << " pos: " << vs[i]->point() << " has not been assigned!\n";
1114 }
1115 }
1116#ifdef DEBUG_TESSELATION
1117 std::cout <<
"Processed on task " <<
FML::ThisTask <<
" is " << assigned <<
" / " << NumPart <<
"\n";
1118#endif
1119
1120 long long int npartotal = NumPart;
1123
1125 std::cout << "[WatershedGeneral] Total particles assigned " << assigned << " / " << npartotal << "\n";
1126 if (assigned < npartotal) {
1127 std::cout << "[WatershedGeneral] Warning we have not mange to assign all particles to their "
1128 "respective minima, cannot guarantee the results are perfect\n";
1129 }
1130 }
1131
1132
1133 std::vector<int> n_per_group(ntotal_minima, 0);
1134 for (size_t i = 0; i < NumPart; i++) {
1136 n_per_group[vs[i]->info().WatershedID]++;
1137 }
1138 }
1139#ifdef USE_MPI
1140 MPI_Allreduce(MPI_IN_PLACE, n_per_group.data(), ntotal_minima, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
1141#endif
1142
1143
1144 std::vector<double> x_minima(ntotal_minima, 0.0);
1145 std::vector<double> y_minima(ntotal_minima, 0.0);
1146 std::vector<double> z_minima(ntotal_minima, 0.0);
1147 for (size_t i = 0; i < nminima; i++) {
1148 auto v = vminima[i];
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158 auto pos = v->point();
1159 x_minima[v->info().WatershedID] = pos[0];
1160 y_minima[v->info().WatershedID] = pos[1];
1161#if CGAL_NDIM == 3
1162 z_minima[v->info().WatershedID] = pos[2];
1163#endif
1164 }
1165#ifdef USE_MPI
1166 MPI_Allreduce(MPI_IN_PLACE, x_minima.data(), ntotal_minima, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
1167 MPI_Allreduce(MPI_IN_PLACE, y_minima.data(), ntotal_minima, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
1168 MPI_Allreduce(MPI_IN_PLACE, z_minima.data(), ntotal_minima, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
1169#endif
1170
1171
1172 watershed_groups.resize(ntotal_minima);
1173
1174
1175 for (size_t i = 0; i < ntotal_minima; i++) {
1176#if CGAL_NDIM == 2
1177 double pos[] = {x_minima[i], y_minima[i]};
1178#elif CGAL_NDIM == 3
1179 double pos[] = {x_minima[i], y_minima[i], z_minima[i]};
1180#endif
1181 watershed_groups[i].init(pos);
1182 }
1183
1184
1185 for (size_t i = 0; i < NumPart; i++) {
1186 auto v = vs[i];
1187 auto id = v->info().WatershedID;
1189 watershed_groups[id].add_particle((T *)v->info().part_ptr, v->info().quantity);
1190 }
1191 }
1192
1193
1194
1195
1196#ifdef USE_MPI
1198 size_t bytes = sizeof(U) * ntotal_minima;
1200 std::vector<U> watershed_groups_from_other_task(ntotal_minima);
1201 MPI_Status status;
1202 MPI_Recv(watershed_groups_from_other_task.data(), bytes, MPI_BYTE, i, 0, MPI_COMM_WORLD, &status);
1203
1204
1205 for (size_t j = 0; j < ntotal_minima; j++) {
1206 watershed_groups[j].merge(watershed_groups_from_other_task[j]);
1207 }
1209 MPI_Send(watershed_groups.data(), bytes, MPI_BYTE, 0, 0, MPI_COMM_WORLD);
1210 }
1211 MPI_Barrier(MPI_COMM_WORLD);
1212 }
1213#endif
1214
1215
1217 for (size_t i = 0; i < ntotal_minima; i++) {
1218 watershed_groups[i].finalize();
1219 }
1220 } else {
1221
1222 watershed_groups.clear();
1223 watershed_groups.shrink_to_fit();
1224 }
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280 }
void info(HaloModel **hmp)
std::vector< Vertex_handle > & get_vertex_handles_regular()
PeriodicDelaunay & get_delaunay_triangulation()
Return the CGAL triangulation we have created.
std::vector< Vertex_handle > & get_vertex_handles_boundary()
get_vel constexpr double * GetPos(...)
const IDType NoWatershedID
const QuantityType Infinity
double xmax_domain
The local extent of the domain (global domain goes from 0 to 1)
void SumOverTasks(T *value)
Inplace sum over tasks of a single value.
double xmin_domain
The local extent of the domain (global domain goes from 0 to 1)
int NTasks
Total number of MPI tasks.