/* ========================================================================== */ /* === Source/Mongoose_ImproveQP.cpp ======================================== */ /* ========================================================================== */ /* ----------------------------------------------------------------------------- * Mongoose Graph Partitioning Library, Copyright (C) 2017-2018, * Scott P. Kolodziej, Nuri S. Yeralan, Timothy A. Davis, William W. Hager * Mongoose is licensed under Version 3 of the GNU General Public License. * Mongoose is also available under other licenses; contact authors for details. * SPDX-License-Identifier: GPL-3.0-only * -------------------------------------------------------------------------- */ #include "Mongoose_ImproveQP.hpp" #include "Mongoose_BoundaryHeap.hpp" #include "Mongoose_Debug.hpp" #include "Mongoose_ImproveFM.hpp" #include "Mongoose_Internal.hpp" #include "Mongoose_Logger.hpp" #include "Mongoose_QPBoundary.hpp" #include "Mongoose_QPLinks.hpp" #include "Mongoose_QPNapsack.hpp" namespace Mongoose { bool improveCutUsingQP(EdgeCutProblem *graph, const EdgeCut_Options *options, bool isInitial) { if (!options->use_QP_gradproj) return false; Logger::tic(QPTiming); /* Unpack structure fields */ Int n = graph->n; Int *Gp = graph->p; double *Gx = graph->x; // edge weights double *Gw = graph->w; // vertex weights double *gains = graph->vertexGains; Int *externalDegree = graph->externalDegree; /* create workspaces */ QPDelta *QP = QPDelta::Create(n); if (!QP) { Logger::toc(QPTiming); return false; } // set the QP parameters double tol = options->soft_split_tolerance; double targetSplit = options->target_split; // ensure target_split and tolerance are valid. These conditions were // already checked on input to Mongoose, in optionsAreValid. ASSERT(tol >= 0); ASSERT(targetSplit >= 0 && targetSplit <= 0.5); // QP upper and lower bounds. target_split +/- tol is in the range 0 to 1, // and then this factor is multiplied by the sum of all vertex weights // (graph->W) to get the QP lo and hi. QP->lo = graph->W * std::max(0., targetSplit - tol); QP->hi = graph->W * std::min(1., targetSplit + tol); ASSERT(QP->lo <= QP->hi); /* Convert the guess from discrete to continuous. */ double *D = QP->D; double *guess = QP->x; bool *partition = graph->partition; for (Int k = 0; k < n; k++) { if (isInitial) { guess[k] = targetSplit; } else { if (partition[k]) { guess[k] = graph->BH_inBoundary(k) ? 0.75 : 1.0; } else { guess[k] = graph->BH_inBoundary(k) ? 0.25 : 0.0; } } double maxWeight = 0; for (Int p = Gp[k]; p < Gp[k + 1]; p++) { maxWeight = std::max(maxWeight, (Gx) ? Gx[p] : 1); } D[k] = maxWeight; } // lo <= a'x <= hi might not hold here QP->lambda = 0; if (QP->b < QP->lo || QP->b > QP->hi) { QP->lambda = QPNapsack(guess, n, QP->lo, QP->hi, graph->w, QP->lambda, QP->FreeSet_status, QP->wx[1], QP->wi[0], QP->wi[1], options->gradproj_tolerance); } // Build the FreeSet, compute grad, possibly adjust QP->lo and QP->hi if (!QPLinks(graph, options, QP)) { Logger::toc(QPTiming); return false; } // lo <= a'x <= hi now holds (lo and hi are modified as needed in QPLinks) /* Do one run of gradient projection. */ QPGradProj(graph, options, QP); QPBoundary(graph, options, QP); QPGradProj(graph, options, QP); QPBoundary(graph, options, QP); /* Use the CutCost to keep track of impacts to the cut cost. */ CutCost cost; cost.cutCost = graph->cutCost; cost.W[0] = graph->W0; cost.W[1] = graph->W1; cost.imbalance = graph->imbalance; /* Do the recommended swaps and compute the new cut cost. */ for (Int k = 0; k < n; k++) { bool newPartition = (guess[k] > 0.5); bool oldPartition = partition[k]; if (newPartition != oldPartition) { /* Update the cut cost. */ cost.cutCost -= 2 * gains[k]; cost.W[oldPartition] -= (Gw) ? Gw[k] : 1; cost.W[newPartition] += (Gw) ? Gw[k] : 1; cost.imbalance = targetSplit - std::min(cost.W[0], cost.W[1]) / graph->W; Int bhVertexPosition = graph->BH_getIndex(k); /* It is possible, although rare, that a vertex may have gone * from not in the boundary to an undo state that places it in * the boundary. It is also possible that a previous swap added * this vertex to the boundary already. */ if (bhVertexPosition != -1) { bhRemove(graph, options, k, gains[k], partition[k], bhVertexPosition); } /* Swap the partition and compute the impact on neighbors. */ fmSwap(graph, options, k, gains[k], partition[k]); if (externalDegree[k] > 0) bhInsert(graph, k); } } // clear the marks from all the vertices graph->clearMarkArray(); /* Free the QP structure */ QP->~QPDelta(); SuiteSparse_free(QP); /* Write the cut cost back to the graph. */ graph->cutCost = cost.cutCost; graph->W0 = cost.W[0]; graph->W1 = cost.W[1]; graph->imbalance = cost.imbalance; double absImbalance = fabs(graph->imbalance); graph->heuCost = graph->cutCost + (absImbalance > options->soft_split_tolerance ? absImbalance * graph->H : 0.0); Logger::toc(QPTiming); return true; } } // end namespace Mongoose