Foam::faceAreaPairGAMGAgglomeration::faceAreaPairGAMGAgglomeration
(
    const lduMesh& mesh,
    const dictionary& controlDict
)
:
    pairGAMGAgglomeration(mesh, controlDict)
{
    const fvMesh& fvmesh = refCast<const fvMesh>(mesh);

    //agglomerate(mesh, sqrt(fvmesh.magSf().internalField()));
    agglomerate
    (
        mesh,
        mag
        (
            cmptMultiply
            (
                fvmesh.Sf().internalField()
               /sqrt(fvmesh.magSf().internalField()),
                vector(1, 1.01, 1.02)
                //vector::one
            )
        )
    );
}
Foam::faceAreaPairGAMGAgglomeration::faceAreaPairGAMGAgglomeration
(
    const lduMesh& mesh,
    const scalarField& cellVolumes,
    const vectorField& faceAreas,
    const dictionary& controlDict
)
:
    pairGAMGAgglomeration(mesh, controlDict)
{
    // agglomerate(mesh, sqrt(mag(faceAreas)));
    agglomerate
    (
        mesh,
        mag
        (
            cmptMultiply
            (
                faceAreas
               /sqrt(mag(faceAreas)),
                vector(1, 1.01, 1.02)
                // vector::one
            )
        )
    );
}
Foam::algebraicPairGAMGAgglomeration::algebraicPairGAMGAgglomeration
(
    const lduMatrix& matrix,
    const dictionary& dict
)
:
    pairGAMGAgglomeration(matrix.mesh(), dict)
{
    agglomerate(matrix.mesh(), mag(matrix.upper()));
}
Foam::algebraicPairGAMGAgglomeration::algebraicPairGAMGAgglomeration
(
    const lduMatrix& matrix,
    const dictionary& controlDict
)
:
    pairGAMGAgglomeration(matrix.mesh(), controlDict)
{
    const lduMesh& mesh = matrix.mesh();

    if (matrix.hasLower())
    {
        agglomerate(mesh, max(mag(matrix.upper()), mag(matrix.lower())));
    }
    else
    {
        agglomerate(mesh, mag(matrix.upper()));
    }
}
Foam::MGridGenGAMGAgglomeration::MGridGenGAMGAgglomeration
(
    const lduMesh& mesh,
    const dictionary& dict
)
:
    GAMGAgglomeration(mesh, dict),
    fvMesh_(refCast<const fvMesh>(mesh))
{
    // Min, max size of agglomerated cells
    label minSize(readLabel(dict.lookup("minSize")));
    label maxSize(readLabel(dict.lookup("maxSize")));


    // Get the finest-level interfaces from the mesh
    interfaceLevels_.set
    (
        0,
        new lduInterfacePtrsList(fvMesh_.boundary().interfaces())
    );

    // Start geometric agglomeration from the cell volumes and areas of the mesh
    scalarField* VPtr = const_cast<scalarField*>(&fvMesh_.cellVolumes());
    vectorField* SfPtr = const_cast<vectorField*>(&fvMesh_.faceAreas());

    // Create the boundary area cell field
    scalarField* SbPtr(new scalarField(fvMesh_.nCells(), 0));

    {
        scalarField& Sb = *SbPtr;

        const labelList& own = fvMesh_.faceOwner();
        const vectorField& Sf = fvMesh_.faceAreas();

        forAll(Sf, facei)
        {
            if (!fvMesh_.isInternalFace(facei))
            {
                Sb[own[facei]] += mag(Sf[facei]);
            }
        }
    }


    // Agglomerate until the required number of cells in the coarsest level
    // is reached

    label nCreatedLevels = 0;

    while (nCreatedLevels < maxLevels_ - 1)
    {
        label nCoarseCells = -1;

        tmp<labelField> finalAgglomPtr = agglomerate
        (
            nCoarseCells,
            minSize,
            maxSize,
            meshLevel(nCreatedLevels).lduAddr(),
            *VPtr,
            *SfPtr,
            *SbPtr
        );

        if (continueAgglomerating(nCoarseCells))
        {
            nCells_[nCreatedLevels] = nCoarseCells;
            restrictAddressing_.set(nCreatedLevels, finalAgglomPtr);
        }
        else
        {
            break;
        }

        agglomerateLduAddressing(nCreatedLevels);

        // Agglomerate the cell volumes field for the next level
        {
            scalarField* aggVPtr
            (
                new scalarField(meshLevels_[nCreatedLevels].size())
            );

            restrictField(*aggVPtr, *VPtr, nCreatedLevels);

            if (nCreatedLevels)
            {
                delete VPtr;
            }

            VPtr = aggVPtr;
        }

        // Agglomerate the face areas field for the next level
        {
            vectorField* aggSfPtr
            (
                new vectorField
                (
                    meshLevels_[nCreatedLevels].upperAddr().size(),
                    vector::zero
                )
            );

            restrictFaceField(*aggSfPtr, *SfPtr, nCreatedLevels);

            if (nCreatedLevels)
            {
                delete SfPtr;
            }

            SfPtr = aggSfPtr;
        }

        // Agglomerate the cell boundary areas field for the next level
        {
            scalarField* aggSbPtr
            (
                new scalarField(meshLevels_[nCreatedLevels].size())
            );

            restrictField(*aggSbPtr, *SbPtr, nCreatedLevels);

            delete SbPtr;
            SbPtr = aggSbPtr;
        }

        nCreatedLevels++;
    }

    // Shrink the storage of the levels to those created
    compactLevels(nCreatedLevels);

    // Delete temporary geometry storage
    if (nCreatedLevels)
    {
        delete VPtr;
        delete SfPtr;
    }
    delete SbPtr;
}