| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2000-2022 Inria | ||
| 3 | * All rights reserved. | ||
| 4 | * | ||
| 5 | * Redistribution and use in source and binary forms, with or without | ||
| 6 | * modification, are permitted provided that the following conditions are met: | ||
| 7 | * | ||
| 8 | * * Redistributions of source code must retain the above copyright notice, | ||
| 9 | * this list of conditions and the following disclaimer. | ||
| 10 | * * Redistributions in binary form must reproduce the above copyright notice, | ||
| 11 | * this list of conditions and the following disclaimer in the documentation | ||
| 12 | * and/or other materials provided with the distribution. | ||
| 13 | * * Neither the name of the ALICE Project-Team nor the names of its | ||
| 14 | * contributors may be used to endorse or promote products derived from this | ||
| 15 | * software without specific prior written permission. | ||
| 16 | * | ||
| 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
| 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
| 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
| 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
| 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
| 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
| 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
| 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
| 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
| 27 | * POSSIBILITY OF SUCH DAMAGE. | ||
| 28 | * | ||
| 29 | * Contact: Bruno Levy | ||
| 30 | * | ||
| 31 | * https://www.inria.fr/fr/bruno-levy | ||
| 32 | * | ||
| 33 | * Inria, | ||
| 34 | * Domaine de Voluceau, | ||
| 35 | * 78150 Le Chesnay - Rocquencourt | ||
| 36 | * FRANCE | ||
| 37 | * | ||
| 38 | */ | ||
| 39 | |||
| 40 | #include <exploragram/optimal_transport/optimal_transport_2d.h> | ||
| 41 | #include <geogram/voronoi/generic_RVD_vertex.h> | ||
| 42 | #include <geogram/voronoi/generic_RVD_polygon.h> | ||
| 43 | #include <geogram/voronoi/RVD_callback.h> | ||
| 44 | #include <geogram/basic/geometry.h> | ||
| 45 | #include <geogram/basic/geometry_nd.h> | ||
| 46 | #include <geogram/basic/stopwatch.h> | ||
| 47 | |||
| 48 | |||
| 49 | namespace { | ||
| 50 | using namespace GEO; | ||
| 51 | |||
| 52 | /**************************************************************************/ | ||
| 53 | |||
| 54 | /** | ||
| 55 | * \brief Clips a polygon by a half-space. | ||
| 56 | * \param[in] P the polygon to be clipped | ||
| 57 | * \param[in] Pi the equation of the half-space, | ||
| 58 | * Pi.x*x + Pi.y*y + Pi.z >= 0 | ||
| 59 | * \param[out] clipped the result | ||
| 60 | * \param[in,out] first index to be used for intersections | ||
| 61 | * \param[in] alloc the PointAllocator used to create the | ||
| 62 | * new vertices | ||
| 63 | */ | ||
| 64 | ✗ | void clip_polygon_by_halfplane( | |
| 65 | const GEOGen::Polygon& P, | ||
| 66 | vec3 Pi, | ||
| 67 | GEOGen::Polygon& target, | ||
| 68 | index_t& n, | ||
| 69 | GEOGen::PointAllocator* alloc | ||
| 70 | ) { | ||
| 71 | target.clear(); | ||
| 72 | ✗ | if(P.nb_vertices() == 0) { | |
| 73 | return; | ||
| 74 | } | ||
| 75 | |||
| 76 | // The predecessor of the first vertex is the last vertex | ||
| 77 | ✗ | index_t prev_k = P.nb_vertices() - 1; | |
| 78 | const GEOGen::Vertex* prev_vk = &(P.vertex(prev_k)); | ||
| 79 | const double* geo_restrict prev_pk = prev_vk->point(); | ||
| 80 | |||
| 81 | GEO::Sign prev_status = GEO::geo_sgn( | ||
| 82 | ✗ | prev_pk[0]*Pi.x + prev_pk[1]*Pi.y + Pi.z | |
| 83 | ); | ||
| 84 | |||
| 85 | ✗ | for(index_t k = 0; k < P.nb_vertices(); k++) { | |
| 86 | const GEOGen::Vertex* vk = &(P.vertex(k)); | ||
| 87 | const double* pk = vk->point(); | ||
| 88 | |||
| 89 | ✗ | GEO::Sign status = GEO::geo_sgn(pk[0]*Pi.x + pk[1]*Pi.y + Pi.z); | |
| 90 | |||
| 91 | // If status of edge extremities differ, | ||
| 92 | // then there is an intersection. | ||
| 93 | if(status != prev_status && (prev_status != 0)) { | ||
| 94 | GEOGen::Vertex I; | ||
| 95 | ✗ | double* Ipoint = alloc->new_item(); | |
| 96 | I.set_point(Ipoint); | ||
| 97 | |||
| 98 | // Compute lambda1 and lambda2, the | ||
| 99 | // barycentric coordinates of the intersection I | ||
| 100 | // in the segment [prev_vk vk] | ||
| 101 | // Note that d and l (used for the predicates) | ||
| 102 | // are reused here. | ||
| 103 | |||
| 104 | |||
| 105 | ✗ | double denom = Pi.x * ( | |
| 106 | ✗ | pk[0] - prev_pk[0]) + Pi.y * (pk[1] - prev_pk[1] | |
| 107 | ); | ||
| 108 | ✗ | double lambda2 = | |
| 109 | ✗ | -(Pi.z + Pi.x * prev_pk[0] + Pi.y * prev_pk[1]); | |
| 110 | ✗ | double lambda1 = denom - lambda2; | |
| 111 | |||
| 112 | // Shit happens ! [Forrest Gump] | ||
| 113 | ✗ | if(::fabs(denom) < 1e-20) { | |
| 114 | lambda1 = 0.5; | ||
| 115 | lambda2 = 0.5; | ||
| 116 | } else { | ||
| 117 | ✗ | lambda1 = lambda1 / denom; | |
| 118 | ✗ | lambda2 = lambda2 / denom; | |
| 119 | } | ||
| 120 | |||
| 121 | ✗ | Ipoint[0] = lambda1 * prev_pk[0] + lambda2 * pk[0]; | |
| 122 | ✗ | Ipoint[1] = lambda1 * prev_pk[1] + lambda2 * pk[1]; | |
| 123 | |||
| 124 | ✗ | I.set_weight( | |
| 125 | ✗ | lambda1 * prev_vk->weight() + lambda2 * vk->weight() | |
| 126 | ); | ||
| 127 | ✗ | if(status > 0) { | |
| 128 | I.copy_edge_from(*prev_vk); | ||
| 129 | ✗ | I.set_adjacent_seed(signed_index_t(n)); | |
| 130 | ✗ | ++n; | |
| 131 | } else { | ||
| 132 | I.set_flag(GEOGen::INTERSECT); | ||
| 133 | I.set_adjacent_seed(vk->adjacent_seed()); | ||
| 134 | } | ||
| 135 | target.add_vertex(I); | ||
| 136 | } | ||
| 137 | ✗ | if(status > 0) { | |
| 138 | target.add_vertex(*vk); | ||
| 139 | } | ||
| 140 | prev_vk = vk; | ||
| 141 | prev_pk = pk; | ||
| 142 | prev_status = status; | ||
| 143 | prev_k = k; | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | |||
| 148 | /** | ||
| 149 | * \brief Clips a polygon by a ball. | ||
| 150 | * \param[in] P the polygon to be clipped | ||
| 151 | * \param[in] center the center of the ball | ||
| 152 | * \param[in] radius the radius of the ball | ||
| 153 | * \param[out] clipped the result | ||
| 154 | * \param[in] n first index to be used for intersections (number of | ||
| 155 | * vertices in Delaunay). | ||
| 156 | * \param[in] alloc the PointAllocator used to create the | ||
| 157 | * new vertices | ||
| 158 | * \param[out] work a temporary work zone | ||
| 159 | */ | ||
| 160 | ✗ | void clip_polygon_by_ball( | |
| 161 | const GEOGen::Polygon& P, | ||
| 162 | vec2 center, double radius, | ||
| 163 | GEOGen::Polygon& clipped, | ||
| 164 | index_t n, | ||
| 165 | GEOGen::PointAllocator* alloc, | ||
| 166 | GEOGen::Polygon& work | ||
| 167 | ) { | ||
| 168 | const index_t N = 16; | ||
| 169 | const double dalpha = M_PI * 2.0 / double(N); | ||
| 170 | ✗ | index_t first_new_index = n; | |
| 171 | clipped.copy(P); | ||
| 172 | ✗ | FOR(i,N) { | |
| 173 | ✗ | double s = ::sin(dalpha*i); | |
| 174 | ✗ | double c = ::cos(dalpha*i); | |
| 175 | ✗ | double off = (center.x+radius*c)*c + (center.y+radius*s)*s; | |
| 176 | ✗ | vec3 Pi(-c,-s,off); | |
| 177 | ✗ | clip_polygon_by_halfplane( | |
| 178 | clipped, Pi, work, first_new_index, alloc | ||
| 179 | ); | ||
| 180 | clipped.swap(work); | ||
| 181 | } | ||
| 182 | ✗ | } | |
| 183 | |||
| 184 | /**************************************************************************/ | ||
| 185 | |||
| 186 | // For more details on the algorithm / structure of the objective function, | ||
| 187 | // see also implementation notes at the beginning of | ||
| 188 | // optimal_transport_3d.cpp. | ||
| 189 | |||
| 190 | /** | ||
| 191 | * \brief Computes the contribution of a polygon | ||
| 192 | * to the objective function minimized by a semi-discrete | ||
| 193 | * optimal transport map in 2D. | ||
| 194 | */ | ||
| 195 | class OTMPolygonCallback : | ||
| 196 | public OptimalTransportMap::Callback, | ||
| 197 | public RVDPolygonCallback { | ||
| 198 | public: | ||
| 199 | |||
| 200 | /** | ||
| 201 | * \brief OTMPolygonCallback constructor. | ||
| 202 | * \param[in] OTM a pointer to the OptimalTransportMap2d | ||
| 203 | */ | ||
| 204 | ✗ | OTMPolygonCallback(OptimalTransportMap2d* OTM) : | |
| 205 | ✗ | OptimalTransportMap::Callback(OTM) { | |
| 206 | ✗ | } | |
| 207 | |||
| 208 | /** | ||
| 209 | * \copydoc RVDPolygonCallback::operator() | ||
| 210 | */ | ||
| 211 | ✗ | void operator() ( | |
| 212 | index_t v, | ||
| 213 | index_t t, | ||
| 214 | const GEOGen::Polygon& P | ||
| 215 | ) const override { | ||
| 216 | geo_argused(t); | ||
| 217 | ✗ | if(OTM_->air_fraction() != 0.0 && OTM_->nb_air_particles() == 0) { | |
| 218 | ✗ | if(v < OTM_->nb_points()) { | |
| 219 | OptimalTransportMap2d* OTM = | ||
| 220 | static_cast<OptimalTransportMap2d*>(OTM_); | ||
| 221 | double R = OTM_->weight(v); | ||
| 222 | ✗ | geo_assert(R > 0.0); | |
| 223 | ✗ | R = ::sqrt(R); | |
| 224 | vec2 center(OTM_->point_ptr(v)); | ||
| 225 | ✗ | clip_polygon_by_ball( | |
| 226 | ✗ | P, center, R, OTM->clipped_, | |
| 227 | ✗ | OTM_->nb_points(), | |
| 228 | ✗ | OTM_->RVD()->point_allocator(), | |
| 229 | ✗ | OTM->work_ | |
| 230 | ); | ||
| 231 | ✗ | do_it(v,t,OTM->clipped_); | |
| 232 | } | ||
| 233 | } else { | ||
| 234 | ✗ | do_it(v,t,P); | |
| 235 | } | ||
| 236 | ✗ | } | |
| 237 | |||
| 238 | ✗ | void do_it( | |
| 239 | index_t v, | ||
| 240 | index_t t, | ||
| 241 | const GEOGen::Polygon& P | ||
| 242 | ) const { | ||
| 243 | |||
| 244 | // v can be an air particle. | ||
| 245 | ✗ | if(v >= n_) { | |
| 246 | ✗ | return; | |
| 247 | } | ||
| 248 | |||
| 249 | ✗ | if(P.nb_vertices() == 0) { | |
| 250 | return; | ||
| 251 | } | ||
| 252 | |||
| 253 | geo_argused(t); | ||
| 254 | double m, mgx, mgy; | ||
| 255 | ✗ | compute_m_and_mg(P, m, mgx, mgy); | |
| 256 | |||
| 257 | ✗ | if(spinlocks_ != nullptr) { | |
| 258 | ✗ | spinlocks_->acquire_spinlock(v); | |
| 259 | } | ||
| 260 | |||
| 261 | // +m because we maximize F <=> minimize -F | ||
| 262 | ✗ | g_[v] += m; | |
| 263 | |||
| 264 | ✗ | if(Newton_step_) { | |
| 265 | // ... but here -m because Newton step = | ||
| 266 | // solve H p = -g (minus g in the RHS). | ||
| 267 | ✗ | OTM_->add_i_right_hand_side(v,-m); | |
| 268 | } | ||
| 269 | |||
| 270 | ✗ | if(mg_ != nullptr) { | |
| 271 | ✗ | mg_[2*v] += mgx; | |
| 272 | ✗ | mg_[2*v+1] += mgy; | |
| 273 | } | ||
| 274 | |||
| 275 | ✗ | if(spinlocks_ != nullptr) { | |
| 276 | ✗ | spinlocks_->release_spinlock(v); | |
| 277 | } | ||
| 278 | |||
| 279 | ✗ | if(Newton_step_) { | |
| 280 | // Spinlocks are managed internally by update_Hessian(). | ||
| 281 | ✗ | update_Hessian(P, v); | |
| 282 | } | ||
| 283 | |||
| 284 | |||
| 285 | ✗ | if(eval_F_) { | |
| 286 | ✗ | Thread* thread = Thread::current(); | |
| 287 | index_t current_thread_id = | ||
| 288 | (thread == nullptr) ? 0 : thread->id(); | ||
| 289 | ✗ | double F = weighted_ ? eval_F_weighted(P, v) : eval_F(P, v); | |
| 290 | const_cast<OTMPolygonCallback*>(this)-> | ||
| 291 | funcval_[current_thread_id] += F; | ||
| 292 | } | ||
| 293 | } | ||
| 294 | |||
| 295 | protected: | ||
| 296 | |||
| 297 | /** | ||
| 298 | * \brief Computes the mass and mass times centroid of the | ||
| 299 | * current intersection polygon. | ||
| 300 | * \details Weights are taken into account if present. | ||
| 301 | * \param[in] P a const reference to the current intersection polygon. | ||
| 302 | * \param[out] m , mgx , mgy the mass and the mass times the | ||
| 303 | * centroid of the ConvexCell. mgx and mgy are not computed | ||
| 304 | * if mg_ is nullptr. | ||
| 305 | */ | ||
| 306 | ✗ | void compute_m_and_mg( | |
| 307 | const GEOGen::Polygon& P, | ||
| 308 | double& m, double& mgx, double& mgy | ||
| 309 | ) const { | ||
| 310 | ✗ | m = 0.0; | |
| 311 | ✗ | mgx = 0.0; | |
| 312 | ✗ | mgy = 0.0; | |
| 313 | const GEOGen::Vertex& V0 = P.vertex(0); | ||
| 314 | const double* p0 = V0.point(); | ||
| 315 | ✗ | for(index_t i=1; i+1<P.nb_vertices(); ++i) { | |
| 316 | const GEOGen::Vertex& V1 = P.vertex(i); | ||
| 317 | const double* p1 = V1.point(); | ||
| 318 | const GEOGen::Vertex& V2 = P.vertex(i+1); | ||
| 319 | const double* p2 = V2.point(); | ||
| 320 | ✗ | double cur_m = triangle_mass(V0,V1,V2); | |
| 321 | ✗ | m += cur_m; | |
| 322 | ✗ | if(mg_ != nullptr) { | |
| 323 | ✗ | if(weighted_) { | |
| 324 | double w0 = V0.weight(); | ||
| 325 | double w1 = V1.weight(); | ||
| 326 | double w2 = V2.weight(); | ||
| 327 | ✗ | double s = cur_m/(w0+w1+w2); | |
| 328 | ✗ | mgx += s * (w0*p0[0] + w1*p1[0] + w2*p2[0]); | |
| 329 | ✗ | mgy += s * (w0*p0[1] + w1*p1[1] + w2*p2[1]); | |
| 330 | } else { | ||
| 331 | ✗ | mgx += cur_m * (p0[0] + p1[0] + p2[0]) / 3.0; | |
| 332 | ✗ | mgy += cur_m * (p0[1] + p1[1] + p2[1]) / 3.0; | |
| 333 | } | ||
| 334 | } | ||
| 335 | } | ||
| 336 | ✗ | } | |
| 337 | |||
| 338 | /** | ||
| 339 | * \brief Updates the Hessian according to the current intersection | ||
| 340 | * polygon. | ||
| 341 | * \param[in] P a const reference to the current intersection polygon. | ||
| 342 | * \param[in] i the current seed | ||
| 343 | */ | ||
| 344 | ✗ | void update_Hessian( | |
| 345 | const GEOGen::Polygon& P, index_t i | ||
| 346 | ) const { | ||
| 347 | |||
| 348 | // The coefficient of the Hessian associated to a pair of | ||
| 349 | // adjacent cells Lag(i),Lag(j) is : | ||
| 350 | // - mass(Lag(i) /\ Lag(j)) / (2*distance(pi,pj)) | ||
| 351 | |||
| 352 | ✗ | const double* pi = OTM_->point_ptr(i); | |
| 353 | |||
| 354 | ✗ | for(index_t k1=0; k1<P.nb_vertices(); ++k1) { | |
| 355 | ✗ | index_t k2 = k1+1; | |
| 356 | ✗ | if(k2 == P.nb_vertices()) { | |
| 357 | k2 = 0; | ||
| 358 | } | ||
| 359 | // Note: | ||
| 360 | // It is P.vertex(k2).adjacent_seed(), | ||
| 361 | // not P.vertex(k1).adjacent_seed() !!! | ||
| 362 | ✗ | index_t j = index_t(P.vertex(k2).adjacent_seed()); | |
| 363 | ✗ | if(j != index_t(-1)) { | |
| 364 | double hij = 0.0; | ||
| 365 | ✗ | if(j < n_) { | |
| 366 | ✗ | const double* pj = OTM_->point_ptr(j); | |
| 367 | ✗ | hij = | |
| 368 | ✗ | edge_mass(P.vertex(k1), P.vertex(k2)) / | |
| 369 | ✗ | (2.0 * GEO::Geom::distance(pi,pj,2)) ; | |
| 370 | ✗ | } else if(OTM_->nb_air_particles() != 0) { | |
| 371 | const double* pj = OTM_->point_ptr(j); // points and air | ||
| 372 | ✗ | hij = | |
| 373 | ✗ | edge_mass(P.vertex(k1), P.vertex(k2)) / | |
| 374 | ✗ | (2.0 * GEO::Geom::distance(pi,pj,2)) ; | |
| 375 | } else { | ||
| 376 | double R = OTM_->weight(i); | ||
| 377 | ✗ | geo_assert(R >= 0.0); | |
| 378 | ✗ | R = ::sqrt(R); | |
| 379 | ✗ | hij = edge_mass(P.vertex(k1), P.vertex(k2)) / (2.0 * R); | |
| 380 | } | ||
| 381 | |||
| 382 | // -hij because we maximize F <=> minimize -F | ||
| 383 | ✗ | if(hij != 0.0) { | |
| 384 | ✗ | if(spinlocks_ != nullptr) { | |
| 385 | ✗ | spinlocks_->acquire_spinlock(i); | |
| 386 | } | ||
| 387 | // Diagonal is positive, extra-diagonal | ||
| 388 | // coefficients are negative, | ||
| 389 | // this is a convex function. | ||
| 390 | ✗ | if(j < n_) { | |
| 391 | ✗ | OTM_->add_ij_coefficient(i, j, -hij); | |
| 392 | } | ||
| 393 | ✗ | OTM_->add_ij_coefficient(i, i, hij); | |
| 394 | ✗ | if(spinlocks_ != nullptr) { | |
| 395 | ✗ | spinlocks_->release_spinlock(i); | |
| 396 | } | ||
| 397 | } | ||
| 398 | } | ||
| 399 | } | ||
| 400 | ✗ | } | |
| 401 | |||
| 402 | /** | ||
| 403 | * \brief Computes the contribution of the current polygon | ||
| 404 | * to the objective function, in the uniform (non-weighted) | ||
| 405 | * case. | ||
| 406 | * \param[in] P a const reference to the current polygon. | ||
| 407 | * \param[in] i the current seed | ||
| 408 | */ | ||
| 409 | ✗ | double eval_F(const GEOGen::Polygon& P, index_t i) const { | |
| 410 | geo_debug_assert(!weighted_); | ||
| 411 | geo_argused(P); | ||
| 412 | geo_argused(i); | ||
| 413 | // Not implemented yet. | ||
| 414 | ✗ | geo_assert_not_reached; | |
| 415 | } | ||
| 416 | |||
| 417 | /** | ||
| 418 | * \brief Computes the contribution of the current polygon | ||
| 419 | * to the objective function, in the weighted case. | ||
| 420 | * \param[in] P a const reference to the current polygon. | ||
| 421 | * \param[in] i the current seed | ||
| 422 | */ | ||
| 423 | ✗ | double eval_F_weighted(const GEOGen::Polygon& P, index_t i) const { | |
| 424 | geo_argused(P); | ||
| 425 | geo_argused(i); | ||
| 426 | // Not implemented yet. | ||
| 427 | ✗ | geo_assert_not_reached; | |
| 428 | } | ||
| 429 | |||
| 430 | /** | ||
| 431 | * \brief Computes the mass of a triangle. | ||
| 432 | * \details Weights are taken into account in weighted_ mode. | ||
| 433 | * \param[in] V0 , V1 , V2 the three vertices of the triangle, | ||
| 434 | * given as RVD vertices. | ||
| 435 | * \return the area of the triangle in 3D times the average value of the | ||
| 436 | * three weights. | ||
| 437 | */ | ||
| 438 | ✗ | double triangle_mass( | |
| 439 | const GEOGen::Vertex& V0, | ||
| 440 | const GEOGen::Vertex& V1, | ||
| 441 | const GEOGen::Vertex& V2 | ||
| 442 | ) const { | ||
| 443 | double m = | ||
| 444 | Geom::triangle_area_2d(V0.point(), V1.point(), V2.point()); | ||
| 445 | ✗ | if(weighted_) { | |
| 446 | ✗ | m *= ((V0.weight() + V1.weight() + V2.weight())/3.0); | |
| 447 | } | ||
| 448 | ✗ | return m; | |
| 449 | } | ||
| 450 | |||
| 451 | /** | ||
| 452 | * \brief Computes the mass of an edge. | ||
| 453 | * \details Weights are taken into account in weighted_ mode. | ||
| 454 | * \param[in] V0 , V1 the two vertices of the edge, | ||
| 455 | * given as RVD vertices. | ||
| 456 | * \return the length of the edge in 3D times the average value of the | ||
| 457 | * two weights. | ||
| 458 | */ | ||
| 459 | ✗ | double edge_mass( | |
| 460 | const GEOGen::Vertex& V0, | ||
| 461 | const GEOGen::Vertex& V1 | ||
| 462 | ) const { | ||
| 463 | ✗ | double m = Geom::distance(V0.point(), V1.point(), 2); | |
| 464 | ✗ | if(weighted_) { | |
| 465 | ✗ | m *= ((V0.weight() + V1.weight())/2.0); | |
| 466 | } | ||
| 467 | ✗ | return m; | |
| 468 | } | ||
| 469 | }; | ||
| 470 | |||
| 471 | /********************************************************************/ | ||
| 472 | |||
| 473 | /** | ||
| 474 | * \brief A RVDPolygonCallback that stores the Restricted Voronoi | ||
| 475 | * Diagram in a Mesh. | ||
| 476 | */ | ||
| 477 | class ComputeRVDPolygonCallback : public RVDPolygonCallback { | ||
| 478 | public: | ||
| 479 | ✗ | ComputeRVDPolygonCallback(OptimalTransportMap* OTM, Mesh* target) : | |
| 480 | ✗ | OTM_(OTM), target_(target) { | |
| 481 | ✗ | target_->clear(); | |
| 482 | ✗ | target_->vertices.set_dimension(3); | |
| 483 | ✗ | chart_.bind(target_->facets.attributes(), "chart"); | |
| 484 | ✗ | } | |
| 485 | |||
| 486 | ✗ | ~ComputeRVDPolygonCallback() override { | |
| 487 | ✗ | chart_.unbind(); | |
| 488 | ✗ | } | |
| 489 | |||
| 490 | ✗ | void operator() ( | |
| 491 | index_t v, | ||
| 492 | index_t t, | ||
| 493 | const GEOGen::Polygon& P | ||
| 494 | ) const override { | ||
| 495 | geo_argused(t); | ||
| 496 | ✗ | if(OTM_->air_fraction() != 0.0 && OTM_->nb_air_particles() == 0) { | |
| 497 | ✗ | if(v < OTM_->nb_points()) { | |
| 498 | OptimalTransportMap2d* OTM = | ||
| 499 | static_cast<OptimalTransportMap2d*>(OTM_); | ||
| 500 | double R = OTM_->weight(v); | ||
| 501 | ✗ | if(R < 0.0) { | |
| 502 | ✗ | std::cerr << '-' << std::flush; | |
| 503 | } | ||
| 504 | ✗ | R = R > 0.0 ? ::sqrt(R) : 0.0; | |
| 505 | ✗ | vec2 center(OTM_->point_ptr(v)); | |
| 506 | ✗ | clip_polygon_by_ball( | |
| 507 | ✗ | P, center, R, OTM->clipped_, | |
| 508 | ✗ | OTM_->nb_points(), | |
| 509 | ✗ | OTM_->RVD()->point_allocator(), | |
| 510 | ✗ | OTM->work_ | |
| 511 | ); | ||
| 512 | ✗ | do_it(v,t,OTM->clipped_); | |
| 513 | } | ||
| 514 | } else { | ||
| 515 | ✗ | do_it(v,t,P); | |
| 516 | } | ||
| 517 | ✗ | } | |
| 518 | |||
| 519 | ✗ | void do_it( | |
| 520 | index_t v, | ||
| 521 | index_t t, | ||
| 522 | const GEOGen::Polygon& P | ||
| 523 | ) const { | ||
| 524 | geo_argused(v); | ||
| 525 | geo_argused(t); | ||
| 526 | |||
| 527 | ✗ | if(P.nb_vertices() == 0) { | |
| 528 | return; | ||
| 529 | } | ||
| 530 | |||
| 531 | ✗ | index_t voffset = target_->vertices.nb(); | |
| 532 | ✗ | FOR(i,P.nb_vertices()) { | |
| 533 | const double* p = P.vertex(i).point(); | ||
| 534 | ✗ | if(OTM_->dimension() == 2) { | |
| 535 | ✗ | target_->vertices.create_vertex( | |
| 536 | ✗ | vec3(p[0], p[1], 0.0).data() | |
| 537 | ); | ||
| 538 | } else { | ||
| 539 | ✗ | target_->vertices.create_vertex( | |
| 540 | ✗ | vec3(p[0], p[1], p[2]).data() | |
| 541 | ); | ||
| 542 | } | ||
| 543 | } | ||
| 544 | ✗ | index_t f = target_->facets.create_polygon(P.nb_vertices()); | |
| 545 | ✗ | FOR(i,P.nb_vertices()) { | |
| 546 | ✗ | target_->facets.set_vertex(f,i,voffset+i); | |
| 547 | } | ||
| 548 | ✗ | const_cast<Attribute<index_t>&>(chart_)[f] = v; | |
| 549 | } | ||
| 550 | |||
| 551 | private: | ||
| 552 | OptimalTransportMap* OTM_; | ||
| 553 | Mesh* target_; | ||
| 554 | Attribute<index_t> chart_; | ||
| 555 | }; | ||
| 556 | |||
| 557 | /********************************************************************/ | ||
| 558 | } | ||
| 559 | |||
| 560 | namespace GEO { | ||
| 561 | |||
| 562 | ✗ | OptimalTransportMap2d::OptimalTransportMap2d( | |
| 563 | Mesh* mesh, const std::string& delaunay, bool BRIO | ||
| 564 | ✗ | ) : | |
| 565 | OptimalTransportMap( | ||
| 566 | 2, | ||
| 567 | mesh, | ||
| 568 | ✗ | (delaunay == "default") ? "BPOW2d" : delaunay, | |
| 569 | BRIO | ||
| 570 | ✗ | ) { | |
| 571 | ✗ | callback_ = new OTMPolygonCallback(this); | |
| 572 | ✗ | total_mass_ = total_mesh_mass(); | |
| 573 | ✗ | } | |
| 574 | |||
| 575 | ✗ | OptimalTransportMap2d::~OptimalTransportMap2d() { | |
| 576 | ✗ | } | |
| 577 | |||
| 578 | ✗ | void OptimalTransportMap2d::get_RVD(Mesh& RVD_mesh) { | |
| 579 | ✗ | ComputeRVDPolygonCallback callback(this, &RVD_mesh); | |
| 580 | ✗ | RVD()->for_each_polygon(callback, false, false, false); | |
| 581 | /* | ||
| 582 | // NOTE: Does not work, TODO: determine why | ||
| 583 | Attribute<index_t> tet_region(RVD_mesh.cells.attributes(),"region"); | ||
| 584 | RVD()->compute_RVD( | ||
| 585 | RVD_mesh, | ||
| 586 | 0, // dim (0 means use default) | ||
| 587 | false, // borders_only | ||
| 588 | show_RVD_seed_ // integration_simplices | ||
| 589 | ); | ||
| 590 | */ | ||
| 591 | ✗ | } | |
| 592 | |||
| 593 | ✗ | void OptimalTransportMap2d::compute_Laguerre_centroids(double* centroids) { | |
| 594 | ✗ | vector<double> g(nb_points(), 0.0); | |
| 595 | ✗ | Memory::clear(centroids, nb_points()*sizeof(double)*2); | |
| 596 | |||
| 597 | ✗ | callback_->set_Laguerre_centroids(centroids); | |
| 598 | callback_->set_g(g.data()); | ||
| 599 | { | ||
| 600 | Stopwatch* W = nullptr; | ||
| 601 | ✗ | if(newton_ && verbose_) { | |
| 602 | ✗ | W = new Stopwatch("RVD"); | |
| 603 | ✗ | Logger::out("OTM") << "In RVD (centroids)..." << std::endl; | |
| 604 | } | ||
| 605 | ✗ | RVD_->for_each_polygon( | |
| 606 | ✗ | *dynamic_cast<RVDPolygonCallback*>(callback_), | |
| 607 | false, false, true | ||
| 608 | ); | ||
| 609 | ✗ | if(newton_ && verbose_) { | |
| 610 | ✗ | delete W; | |
| 611 | } | ||
| 612 | } | ||
| 613 | |||
| 614 | ✗ | callback_->set_Laguerre_centroids(nullptr); | |
| 615 | |||
| 616 | ✗ | for(index_t v=0; v<nb_points(); ++v) { | |
| 617 | ✗ | centroids[2*v ] /= g[v]; | |
| 618 | ✗ | centroids[2*v+1] /= g[v]; | |
| 619 | } | ||
| 620 | ✗ | } | |
| 621 | |||
| 622 | ✗ | double OptimalTransportMap2d::total_mesh_mass() const { | |
| 623 | double result = 0.0; | ||
| 624 | |||
| 625 | // This is terribly confusing, the parameters for | ||
| 626 | // a power diagram are called "weights", and the | ||
| 627 | // standard attribute name for vertices density is | ||
| 628 | // also called "weight" (and is unrelated). | ||
| 629 | // In this program, what is called weight corresponds | ||
| 630 | // to the parameters of the power diagram (except the | ||
| 631 | // name of the attribute), and everything that corresponds | ||
| 632 | // to mass/density is called mass. | ||
| 633 | Attribute<double> vertex_mass; | ||
| 634 | ✗ | vertex_mass.bind_if_is_defined( | |
| 635 | ✗ | mesh_->vertices.attributes(), "weight" | |
| 636 | ); | ||
| 637 | |||
| 638 | ✗ | for(index_t t: mesh_->facets) { | |
| 639 | double tri_mass = GEO::Geom::triangle_area( | ||
| 640 | vec2(mesh_->vertices.point_ptr(mesh_->facets.vertex(t, 0))), | ||
| 641 | vec2(mesh_->vertices.point_ptr(mesh_->facets.vertex(t, 1))), | ||
| 642 | vec2(mesh_->vertices.point_ptr(mesh_->facets.vertex(t, 2))) | ||
| 643 | ); | ||
| 644 | if(vertex_mass.is_bound()) { | ||
| 645 | ✗ | tri_mass *= ( | |
| 646 | ✗ | vertex_mass[mesh_->facets.vertex(t, 0)] + | |
| 647 | ✗ | vertex_mass[mesh_->facets.vertex(t, 1)] + | |
| 648 | vertex_mass[mesh_->facets.vertex(t, 2)] | ||
| 649 | ✗ | ) / 3.0; | |
| 650 | } | ||
| 651 | ✗ | result += tri_mass; | |
| 652 | } | ||
| 653 | ✗ | return result; | |
| 654 | } | ||
| 655 | |||
| 656 | ✗ | void OptimalTransportMap2d::call_callback_on_RVD() { | |
| 657 | ✗ | RVD_->for_each_polygon( | |
| 658 | ✗ | *dynamic_cast<RVDPolygonCallback*>(callback_), | |
| 659 | false, // symbolic | ||
| 660 | false, // connected components priority | ||
| 661 | ✗ | !clip_by_balls_ // parallel | |
| 662 | ); | ||
| 663 | // clip_by_balls deactivates parallel mode, because it needs to access | ||
| 664 | // the PointAllocator of the current thread, and we do not have any | ||
| 665 | // access (for now). | ||
| 666 | ✗ | } | |
| 667 | |||
| 668 | /**********************************************************************/ | ||
| 669 | |||
| 670 | ✗ | void compute_Laguerre_centroids_2d( | |
| 671 | Mesh* omega, | ||
| 672 | index_t nb_points, | ||
| 673 | const double* points, | ||
| 674 | double* centroids, | ||
| 675 | Mesh* RVD, | ||
| 676 | bool verbose, | ||
| 677 | index_t nb_air_particles, | ||
| 678 | const double* air_particles, | ||
| 679 | index_t air_particles_stride, | ||
| 680 | double air_fraction, | ||
| 681 | const double* weights_in, | ||
| 682 | double* weights_out, | ||
| 683 | index_t nb_iter | ||
| 684 | ) { | ||
| 685 | // Omega can be either 2d or 3d with third coordinate set to | ||
| 686 | // zero. | ||
| 687 | index_t omega_dim_backup = omega->vertices.dimension(); | ||
| 688 | ✗ | omega->vertices.set_dimension(3); | |
| 689 | |||
| 690 | // false = no BRIO | ||
| 691 | // (OTM does not use multilevel and lets Delaunay | ||
| 692 | // reorder the vertices) | ||
| 693 | OptimalTransportMap2d OTM( | ||
| 694 | omega, | ||
| 695 | ✗ | std::string("BPOW2d"), | |
| 696 | false | ||
| 697 | ✗ | ); | |
| 698 | |||
| 699 | static bool initialized = false; | ||
| 700 | static bool has_cholmod = false; | ||
| 701 | ✗ | if(!initialized) { | |
| 702 | ✗ | initialized = true; | |
| 703 | ✗ | has_cholmod = (nlInitExtension("CHOLMOD") == NL_TRUE); | |
| 704 | } | ||
| 705 | |||
| 706 | ✗ | if(has_cholmod) { | |
| 707 | OTM.set_regularization(1e-3); | ||
| 708 | OTM.set_linear_solver(OT_CHOLMOD); | ||
| 709 | } | ||
| 710 | |||
| 711 | OTM.set_Newton(true); | ||
| 712 | OTM.set_air_particles( | ||
| 713 | nb_air_particles, air_particles, air_particles_stride, air_fraction | ||
| 714 | ); | ||
| 715 | ✗ | OTM.set_points(nb_points, points); | |
| 716 | ✗ | if(weights_in != nullptr) { | |
| 717 | ✗ | FOR(i, nb_points) { | |
| 718 | ✗ | OTM.set_initial_weight(i, weights_in[i]); | |
| 719 | } | ||
| 720 | } | ||
| 721 | OTM.set_epsilon(0.01); | ||
| 722 | OTM.set_Laguerre_centroids(centroids); | ||
| 723 | OTM.set_verbose(verbose); | ||
| 724 | ✗ | OTM.optimize(nb_iter); | |
| 725 | |||
| 726 | ✗ | if(RVD != nullptr) { | |
| 727 | ✗ | OTM.get_RVD(*RVD); | |
| 728 | } | ||
| 729 | |||
| 730 | ✗ | omega->vertices.set_dimension(omega_dim_backup); | |
| 731 | |||
| 732 | ✗ | if(weights_out != nullptr) { | |
| 733 | ✗ | FOR(v, OTM.nb_points()) { | |
| 734 | ✗ | weights_out[v] = OTM.weight(v); | |
| 735 | } | ||
| 736 | } | ||
| 737 | ✗ | } | |
| 738 | } | ||
| 739 |