pairPatchAgglomeration.C 16.7 KB
Newer Older
Sergio Ferraris's avatar
Sergio Ferraris committed
1 2 3 4
/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
OpenFOAM bot's avatar
OpenFOAM bot committed
5
    \\  /    A nd           | www.openfoam.com
OpenFOAM bot's avatar
OpenFOAM bot committed
6 7
     \\/     M anipulation  |
-------------------------------------------------------------------------------
OpenFOAM bot's avatar
OpenFOAM bot committed
8
    Copyright (C) 2011-2016 OpenFOAM Foundation
9
    Copyright (C) 2016-2020 OpenCFD Ltd.
Sergio Ferraris's avatar
Sergio Ferraris committed
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
-------------------------------------------------------------------------------
License
    This file is part of OpenFOAM.

    OpenFOAM is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    for more details.

    You should have received a copy of the GNU General Public License
    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.

\*---------------------------------------------------------------------------*/

#include "pairPatchAgglomeration.H"
#include "meshTools.H"
31
#include "edgeHashes.H"
andy's avatar
andy committed
32
#include "unitConversion.H"
Sergio Ferraris's avatar
Sergio Ferraris committed
33 34 35 36 37 38 39 40 41 42 43 44 45

// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //

void Foam::pairPatchAgglomeration::compactLevels(const label nCreatedLevels)
{
    nFaces_.setSize(nCreatedLevels);
    restrictAddressing_.setSize(nCreatedLevels);
    patchLevels_.setSize(nCreatedLevels);
}


bool Foam::pairPatchAgglomeration::continueAgglomerating
(
46 47
    const label nLocal,
    const label nLocalOld
Sergio Ferraris's avatar
Sergio Ferraris committed
48 49
)
{
50 51 52 53 54 55 56 57 58 59 60 61 62 63
    // Keep agglomerating
    // - if global number of faces is still changing
    // - and if local number of faces still too large (on any processor)
    //       or if global number of faces still too large

    label nGlobal = returnReduce(nLocal, sumOp<label>());
    label nGlobalOld = returnReduce(nLocalOld, sumOp<label>());

    return
    (
        returnReduce(nLocal > nFacesInCoarsestLevel_, orOp<bool>())
     || nGlobal > nGlobalFacesInCoarsestLevel_
    )
    && nGlobal != nGlobalOld;
Sergio Ferraris's avatar
Sergio Ferraris committed
64 65 66
}


67
void Foam::pairPatchAgglomeration::setLevel0EdgeWeights()
Sergio Ferraris's avatar
Sergio Ferraris committed
68 69 70 71
{
    const bPatch& coarsePatch = patchLevels_[0];
    forAll(coarsePatch.edges(), i)
    {
andy's avatar
andy committed
72
        if (coarsePatch.isInternalEdge(i))
Sergio Ferraris's avatar
Sergio Ferraris committed
73 74 75 76 77 78 79 80 81
        {
            scalar edgeLength =
                coarsePatch.edges()[i].mag(coarsePatch.localPoints());

            const labelList& eFaces = coarsePatch.edgeFaces()[i];

            if (eFaces.size() == 2)
            {
                scalar cosI =
andy's avatar
andy committed
82 83
                    coarsePatch.faceNormals()[eFaces[0]]
                  & coarsePatch.faceNormals()[eFaces[1]];
Sergio Ferraris's avatar
Sergio Ferraris committed
84 85 86

                const edge edgeCommon = edge(eFaces[0], eFaces[1]);

andy's avatar
andy committed
87
                if (facePairWeight_.found(edgeCommon))
Sergio Ferraris's avatar
Sergio Ferraris committed
88 89 90 91 92 93 94 95
                {
                    facePairWeight_[edgeCommon] += edgeLength;
                }
                else
                {
                    facePairWeight_.insert(edgeCommon, edgeLength);
                }

96
                if (mag(cosI) < Foam::cos(degToRad(featureAngle_)))
Sergio Ferraris's avatar
Sergio Ferraris committed
97 98 99 100
                {
                    facePairWeight_[edgeCommon] = -1.0;
                }
            }
101
            else
Sergio Ferraris's avatar
Sergio Ferraris committed
102
            {
103 104 105 106 107 108 109 110 111 112 113
                forAll(eFaces, j)
                {
                    for (label k = j+1; k<eFaces.size(); k++)
                    {
                        facePairWeight_.insert
                        (
                            edge(eFaces[j], eFaces[k]),
                            -1.0
                        );
                    }
                }
Sergio Ferraris's avatar
Sergio Ferraris committed
114 115 116 117 118 119 120 121 122 123 124 125 126
            }
        }
    }
}


void Foam::pairPatchAgglomeration::setEdgeWeights
(
    const label fineLevelIndex
)
{
    const bPatch& coarsePatch = patchLevels_[fineLevelIndex];
    const labelList& fineToCoarse = restrictAddressing_[fineLevelIndex];
andy's avatar
andy committed
127 128
    const label nCoarseI =  max(fineToCoarse) + 1;
    labelListList coarseToFine(invertOneToMany(nCoarseI, fineToCoarse));
Sergio Ferraris's avatar
Sergio Ferraris committed
129

130
    edgeHashSet fineFeaturedFaces(coarsePatch.nEdges()/10);
Sergio Ferraris's avatar
Sergio Ferraris committed
131 132

    // Map fine faces with featured edge into coarse faces
133
    forAllConstIters(facePairWeight_, iter)
Sergio Ferraris's avatar
Sergio Ferraris committed
134 135 136 137 138 139 140 141 142 143 144 145 146
    {
        if (iter() == -1.0)
        {
            const edge e = iter.key();
            const edge edgeFeatured
            (
                fineToCoarse[e[0]],
                fineToCoarse[e[1]]
            );
            fineFeaturedFaces.insert(edgeFeatured);
        }
    }

Andrew Heather's avatar
Andrew Heather committed
147
    // Clean old weights
Sergio Ferraris's avatar
Sergio Ferraris committed
148 149 150 151 152
    facePairWeight_.clear();
    facePairWeight_.resize(coarsePatch.nEdges());

    forAll(coarsePatch.edges(), i)
    {
andy's avatar
andy committed
153
        if (coarsePatch.isInternalEdge(i))
Sergio Ferraris's avatar
Sergio Ferraris committed
154 155 156 157 158 159 160 161 162
        {
            scalar edgeLength =
                coarsePatch.edges()[i].mag(coarsePatch.localPoints());

            const labelList& eFaces = coarsePatch.edgeFaces()[i];

            if (eFaces.size() == 2)
            {
                const edge edgeCommon = edge(eFaces[0], eFaces[1]);
andy's avatar
andy committed
163
                if (facePairWeight_.found(edgeCommon))
Sergio Ferraris's avatar
Sergio Ferraris committed
164 165 166 167 168 169 170 171 172 173 174 175 176 177
                {
                    facePairWeight_[edgeCommon] += edgeLength;
                }
                else
                {
                    facePairWeight_.insert(edgeCommon, edgeLength);
                }
                // If the fine 'pair' faces was featured edge so it is
                // the coarse 'pair'
                if (fineFeaturedFaces.found(edgeCommon))
                {
                    facePairWeight_[edgeCommon] = -1.0;
                }
            }
178
            else
Sergio Ferraris's avatar
Sergio Ferraris committed
179
            {
180 181 182 183 184 185 186 187 188 189 190 191
                // Set edge as barrier by setting weight to -1
                forAll(eFaces, j)
                {
                    for (label k = j+1; k<eFaces.size(); k++)
                    {
                        facePairWeight_.insert
                        (
                            edge(eFaces[j], eFaces[k]),
                            -1.0
                        );
                    }
                }
Sergio Ferraris's avatar
Sergio Ferraris committed
192 193 194 195 196 197 198 199 200 201
            }
        }
    }
}


// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //

Foam::pairPatchAgglomeration::pairPatchAgglomeration
(
202 203
    const faceList& faces,
    const pointField& points,
204
    const dictionary& controlDict
Sergio Ferraris's avatar
Sergio Ferraris committed
205 206 207 208
)
:
    mergeLevels_
    (
209
        controlDict.getOrDefault<label>("mergeLevels", 2)
Sergio Ferraris's avatar
Sergio Ferraris committed
210 211 212 213
    ),
    maxLevels_(50),
    nFacesInCoarsestLevel_
    (
214
        controlDict.get<label>("nFacesInCoarsestLevel")
Sergio Ferraris's avatar
Sergio Ferraris committed
215
    ),
216 217
    nGlobalFacesInCoarsestLevel_(labelMax),
    //(
218
    //    controlDict.get<label>("nGlobalFacesInCoarsestLevel")
219
    //),
Sergio Ferraris's avatar
Sergio Ferraris committed
220 221
    featureAngle_
    (
222
        controlDict.getOrDefault<scalar>("featureAngle", 0)
Sergio Ferraris's avatar
Sergio Ferraris committed
223 224 225
    ),
    nFaces_(maxLevels_),
    restrictAddressing_(maxLevels_),
226
    restrictTopBottomAddressing_(identity(faces.size())),
Sergio Ferraris's avatar
Sergio Ferraris committed
227
    patchLevels_(maxLevels_),
228
    facePairWeight_(faces.size())
Sergio Ferraris's avatar
Sergio Ferraris committed
229 230
{
    // Set base fine patch
231
    patchLevels_.set(0, new bPatch(faces, points));
Sergio Ferraris's avatar
Sergio Ferraris committed
232 233

    // Set number of faces for the base patch
234
    nFaces_[0] = faces.size();
Sergio Ferraris's avatar
Sergio Ferraris committed
235 236

    // Set edge weights for level 0
237 238 239 240 241 242
    setLevel0EdgeWeights();
}


Foam::pairPatchAgglomeration::pairPatchAgglomeration
(
243 244
    const faceList& faces,
    const pointField& points,
245 246 247 248 249 250 251 252 253 254 255 256 257 258
    const label mergeLevels,
    const label maxLevels,
    const label nFacesInCoarsestLevel,          // local number of cells
    const label nGlobalFacesInCoarsestLevel,    // global number of cells
    const scalar featureAngle
)
:
    mergeLevels_(mergeLevels),
    maxLevels_(maxLevels),
    nFacesInCoarsestLevel_(nFacesInCoarsestLevel),
    nGlobalFacesInCoarsestLevel_(nGlobalFacesInCoarsestLevel),
    featureAngle_(featureAngle),
    nFaces_(maxLevels_),
    restrictAddressing_(maxLevels_),
259
    restrictTopBottomAddressing_(identity(faces.size())),
260
    patchLevels_(maxLevels_),
261
    facePairWeight_(faces.size())
262 263
{
    // Set base fine patch
264
    patchLevels_.set(0, new bPatch(faces, points));
265 266

    // Set number of faces for the base patch
267
    nFaces_[0] = faces.size();
268 269 270

    // Set edge weights for level 0
    setLevel0EdgeWeights();
Sergio Ferraris's avatar
Sergio Ferraris committed
271 272
}

andy's avatar
andy committed
273

Sergio Ferraris's avatar
Sergio Ferraris committed
274 275 276 277 278
// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //

Foam::pairPatchAgglomeration::~pairPatchAgglomeration()
{}

andy's avatar
andy committed
279

Sergio Ferraris's avatar
Sergio Ferraris committed
280 281
// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //

282 283
const Foam::pairPatchAgglomeration::bPatch&
Foam::pairPatchAgglomeration::patchLevel
Sergio Ferraris's avatar
Sergio Ferraris committed
284 285 286 287 288 289 290 291 292 293 294 295 296 297
(
    const label i
) const
{
    return patchLevels_[i];
}


void Foam::pairPatchAgglomeration::mapBaseToTopAgglom
(
    const label fineLevelIndex
)
{
    const labelList& fineToCoarse = restrictAddressing_[fineLevelIndex];
298
    forAll(restrictTopBottomAddressing_, i)
Sergio Ferraris's avatar
Sergio Ferraris committed
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
    {
        restrictTopBottomAddressing_[i] =
            fineToCoarse[restrictTopBottomAddressing_[i]];
    }
}


bool Foam::pairPatchAgglomeration::agglomeratePatch
(
    const bPatch& patch,
    const labelList& fineToCoarse,
    const label fineLevelIndex
)
{
    if (min(fineToCoarse) == -1)
    {
315 316
        FatalErrorInFunction
            << "min(fineToCoarse) == -1" << exit(FatalError);
Sergio Ferraris's avatar
Sergio Ferraris committed
317 318
    }

Sergio Ferraris's avatar
Sergio Ferraris committed
319 320
    if (fineToCoarse.size() == 0)
    {
321
        return false;
Sergio Ferraris's avatar
Sergio Ferraris committed
322 323
    }

Sergio Ferraris's avatar
Sergio Ferraris committed
324 325
    if (fineToCoarse.size() != patch.size())
    {
326 327
        FatalErrorInFunction
            << "restrict map does not correspond to fine level. " << endl
Sergio Ferraris's avatar
Sergio Ferraris committed
328 329 330 331 332
            << " Sizes: restrictMap: " << fineToCoarse.size()
            << " nEqns: " << patch.size()
            << abort(FatalError);
    }

333
    const label nCoarseI =  max(fineToCoarse) + 1;
Sergio Ferraris's avatar
Sergio Ferraris committed
334 335
    List<face> patchFaces(nCoarseI);

Sergio Ferraris's avatar
Sergio Ferraris committed
336

Sergio Ferraris's avatar
Sergio Ferraris committed
337
    // Patch faces per agglomeration
andy's avatar
andy committed
338
    labelListList coarseToFine(invertOneToMany(nCoarseI, fineToCoarse));
Sergio Ferraris's avatar
Sergio Ferraris committed
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388

    for (label coarseI = 0; coarseI < nCoarseI; coarseI++)
    {
        const labelList& fineFaces = coarseToFine[coarseI];

        // Construct single face
        indirectPrimitivePatch upp
        (
            IndirectList<face>(patch, fineFaces),
            patch.points()
        );

        if (upp.edgeLoops().size() != 1)
        {
            if (fineFaces.size() == 2)
            {
                const edge e(fineFaces[0], fineFaces[1]);
                facePairWeight_[e] = -1.0;
            }
            else if (fineFaces.size() == 3)
            {
                const edge e(fineFaces[0], fineFaces[1]);
                const edge e1(fineFaces[0], fineFaces[2]);
                const edge e2(fineFaces[2], fineFaces[1]);
                facePairWeight_[e] = -1.0;
                facePairWeight_[e1] = -1.0;
                facePairWeight_[e2] = -1.0;
            }
            return false;
        }

        patchFaces[coarseI] = face
        (
            renumber
            (
                upp.meshPoints(),
                upp.edgeLoops()[0]
            )
        );
    }

    patchLevels_.set
    (
        fineLevelIndex,
        new bPatch
        (
            SubList<face>(patchFaces, nCoarseI, 0),
            patch.points()
        )
    );
Sergio Ferraris's avatar
Sergio Ferraris committed
389

Sergio Ferraris's avatar
Sergio Ferraris committed
390 391 392 393
    return true;
}


394
void Foam::pairPatchAgglomeration::agglomerate()
Sergio Ferraris's avatar
Sergio Ferraris committed
395 396
{
    label nPairLevels = 0;
397
    label nCreatedLevels = 1; // 0 level is the base patch
398

Sergio Ferraris's avatar
Sergio Ferraris committed
399 400
    label nCoarseFaces = 0;
    label nCoarseFacesOld = 0;
Sergio Ferraris's avatar
Sergio Ferraris committed
401 402 403 404

    while (nCreatedLevels < maxLevels_)
    {
        const bPatch& patch = patchLevels_[nCreatedLevels - 1];
405

406 407
        // Agglomerate locally
        tmp<labelField> tfinalAgglom;
408

409 410 411 412 413 414
        bool createdLevel = false;
        while (!createdLevel)
        {
            // Agglomerate locally using edge weights
            // - calculates nCoarseFaces; returns fine to coarse addressing
            tfinalAgglom = agglomerateOneLevel(nCoarseFaces, patch);
Sergio Ferraris's avatar
Sergio Ferraris committed
415

416
            if (nCoarseFaces == 0)
Sergio Ferraris's avatar
Sergio Ferraris committed
417
            {
418 419 420 421 422 423 424 425
                break;
            }
            else
            {
                // Attempt to create coarse face addressing
                // - returns true if successful; otherwise resets edge weights
                //   and assume try again...
                createdLevel = agglomeratePatch
Sergio Ferraris's avatar
Sergio Ferraris committed
426
                (
427 428 429 430 431 432
                    patch,
                    tfinalAgglom,
                    nCreatedLevels
                );
            }
        }
Sergio Ferraris's avatar
Sergio Ferraris committed
433

434 435 436
        if (createdLevel)
        {
            restrictAddressing_.set(nCreatedLevels, tfinalAgglom);
Sergio Ferraris's avatar
Sergio Ferraris committed
437

438 439 440 441 442 443 444
            mapBaseToTopAgglom(nCreatedLevels);

            setEdgeWeights(nCreatedLevels);

            if (nPairLevels % mergeLevels_)
            {
                combineLevels(nCreatedLevels);
Sergio Ferraris's avatar
Sergio Ferraris committed
445 446 447
            }
            else
            {
448
                nCreatedLevels++;
Sergio Ferraris's avatar
Sergio Ferraris committed
449
            }
450

451
            nPairLevels++;
Sergio Ferraris's avatar
Sergio Ferraris committed
452

453 454
            nFaces_[nCreatedLevels] = nCoarseFaces;
        }
Sergio Ferraris's avatar
Sergio Ferraris committed
455

456 457 458
        // Check to see if we need to continue agglomerating
        // - Note: performs parallel reductions
        if (!continueAgglomerating(nCoarseFaces, nCoarseFacesOld))
Sergio Ferraris's avatar
Sergio Ferraris committed
459
        {
Sergio Ferraris's avatar
Sergio Ferraris committed
460
            break;
Sergio Ferraris's avatar
Sergio Ferraris committed
461 462
        }

Sergio Ferraris's avatar
Sergio Ferraris committed
463
        nCoarseFacesOld = nCoarseFaces;
Sergio Ferraris's avatar
Sergio Ferraris committed
464
    }
465 466

    compactLevels(nCreatedLevels);
Sergio Ferraris's avatar
Sergio Ferraris committed
467 468 469 470 471
}


Foam::tmp<Foam::labelField> Foam::pairPatchAgglomeration::agglomerateOneLevel
(
Sergio Ferraris's avatar
Sergio Ferraris committed
472
    label& nCoarseFaces,
Sergio Ferraris's avatar
Sergio Ferraris committed
473 474 475 476 477 478
    const bPatch& patch
)
{
    const label nFineFaces = patch.size();

    tmp<labelField> tcoarseCellMap(new labelField(nFineFaces, -1));
479
    labelField& coarseCellMap = tcoarseCellMap.ref();
Sergio Ferraris's avatar
Sergio Ferraris committed
480 481 482

    const labelListList& faceFaces = patch.faceFaces();

Sergio Ferraris's avatar
Sergio Ferraris committed
483
    nCoarseFaces = 0;
Sergio Ferraris's avatar
Sergio Ferraris committed
484

andy's avatar
andy committed
485
    forAll(faceFaces, facei)
Sergio Ferraris's avatar
Sergio Ferraris committed
486 487 488 489 490 491 492 493 494
    {
        const labelList& fFaces = faceFaces[facei];

        if (coarseCellMap[facei] < 0)
        {
            label matchFaceNo = -1;
            label matchFaceNeibNo = -1;
            scalar maxFaceWeight = -GREAT;

495
            // Check faces to find ungrouped neighbour with largest face weight
Sergio Ferraris's avatar
Sergio Ferraris committed
496 497 498 499 500 501 502
            forAll(fFaces, i)
            {
                label faceNeig = fFaces[i];
                const edge edgeCommon = edge(facei, faceNeig);
                if
                (
                    facePairWeight_[edgeCommon] > maxFaceWeight
andy's avatar
andy committed
503 504
                 && coarseCellMap[faceNeig] < 0
                 && facePairWeight_[edgeCommon] != -1.0
Sergio Ferraris's avatar
Sergio Ferraris committed
505 506 507 508 509 510 511 512 513 514 515 516
                )
                {
                    // Match found. Pick up all the necessary data
                    matchFaceNo = facei;
                    matchFaceNeibNo = faceNeig;
                    maxFaceWeight = facePairWeight_[edgeCommon];
                }
            }

            if (matchFaceNo >= 0)
            {
                // Make a new group
Sergio Ferraris's avatar
Sergio Ferraris committed
517 518 519
                coarseCellMap[matchFaceNo] = nCoarseFaces;
                coarseCellMap[matchFaceNeibNo] = nCoarseFaces;
                nCoarseFaces++;
Sergio Ferraris's avatar
Sergio Ferraris committed
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
            }
            else
            {
                // No match. Find the best neighbouring cluster and
                // put the cell there
                label clusterMatchFaceNo = -1;
                scalar clusterMaxFaceCoeff = -GREAT;

                forAll(fFaces, i)
                {
                    label faceNeig = fFaces[i];
                    const edge edgeCommon = edge(facei, faceNeig);
                    if
                    (
                        facePairWeight_[edgeCommon] > clusterMaxFaceCoeff
535 536
                     && facePairWeight_[edgeCommon] != -1.0
                     && coarseCellMap[faceNeig] >= 0
Sergio Ferraris's avatar
Sergio Ferraris committed
537 538 539 540 541 542 543
                    )
                    {
                        clusterMatchFaceNo = faceNeig;
                        clusterMaxFaceCoeff = facePairWeight_[edgeCommon];
                    }
                }

544
                if (clusterMatchFaceNo > 0)
Sergio Ferraris's avatar
Sergio Ferraris committed
545 546 547 548 549
                {
                    // Add the cell to the best cluster
                    coarseCellMap[facei] = coarseCellMap[clusterMatchFaceNo];
                }
                else
andy's avatar
andy committed
550
                {
551
                    // If not create single-cell "clusters" for each
Sergio Ferraris's avatar
Sergio Ferraris committed
552
                    coarseCellMap[facei] = nCoarseFaces;
553
                    nCoarseFaces++;
Sergio Ferraris's avatar
Sergio Ferraris committed
554 555 556 557 558 559 560 561 562 563
                }
            }
        }
    }

    // Check that all faces are part of clusters,
    for (label facei=0; facei<nFineFaces; facei++)
    {
        if (coarseCellMap[facei] < 0)
        {
564
            FatalErrorInFunction
565 566 567
                << " face " << facei
                << " is not part of a cluster"
                << exit(FatalError);
Sergio Ferraris's avatar
Sergio Ferraris committed
568 569 570 571 572 573
        }
    }

    return tcoarseCellMap;
}

andy's avatar
andy committed
574

Sergio Ferraris's avatar
Sergio Ferraris committed
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
void Foam::pairPatchAgglomeration::combineLevels(const label curLevel)
{
    label prevLevel = curLevel - 1;

    // Set the previous level nCells to the current
    nFaces_[prevLevel] = nFaces_[curLevel];

    // Map the restrictAddressing from the coarser level into the previous
    // finer level

    const labelList& curResAddr = restrictAddressing_[curLevel];
    labelList& prevResAddr = restrictAddressing_[prevLevel];

    forAll(prevResAddr, i)
    {
        prevResAddr[i] = curResAddr[prevResAddr[i]];
    }

    // Delete the restrictAddressing for the coarser level
594
    restrictAddressing_.set(curLevel, nullptr);
Sergio Ferraris's avatar
Sergio Ferraris committed
595

596
    patchLevels_.set(prevLevel, patchLevels_.set(curLevel, nullptr));
Sergio Ferraris's avatar
Sergio Ferraris committed
597
}
andy's avatar
andy committed
598 599


Sergio Ferraris's avatar
Sergio Ferraris committed
600
// ************************************************************************* //