File: Mongoose_QPNapsack.cpp

package info (click to toggle)
suitesparse 1%3A5.4.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 138,928 kB
  • sloc: ansic: 389,614; cpp: 24,213; makefile: 5,965; fortran: 1,927; java: 1,808; csh: 1,750; ruby: 725; sh: 226; perl: 225; python: 209; sed: 164; awk: 60
file content (408 lines) | stat: -rw-r--r-- 13,904 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
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
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
/* ========================================================================== */
/* === Source/Mongoose_QPNapsack.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.
 * -------------------------------------------------------------------------- */

/* ========================================================================== */
/* === QPNapsack ============================================================ */
/* ========================================================================== */
/*  Find x that minimizes ||x-y|| while satisfying 0 <= x <= 1,
    a'x = b, lo <= b <= hi.  It is assumed that the column vector a is strictly
    positive since, in our application, the vector a is the vertex weights,
    which are >= 1. If a is NULL, then it is assumed that a is identically 1.
    The approach is to solve the dual problem obtained by introducing
    a multiplier lambda for the constraint a'x = b.  The dual function is

    L (lambda) = min { ||x-y||^2 + lambda (a'x - b): 0 <= x <= 1, lo <= b <= hi}

    The dual function is concave. It is continuously differentiable
    except at lambda = 0.  If mu denotes the maximizer of the dual function,
    then the solution of the primal problem is

    x = proj (y - mu*a) ,

    where proj (z) is the projection of z onto the set { x : 0 <= x <= 1}.
    Thus we have

       proj (z)_i = 1   if z_i >= 1,
                    0   if z_i <= 0,
                    z_i otherwise  .

    Note that for any lambda, the minimizing x in the dual function is

       x (lambda) = proj (y - lambda*a).

    The slope of the dual function is

      L'(lambda) = a'proj (x(lambda)) - hi (if lambda > 0)
                   a'proj (x(lambda)) - lo (if lambda < 0)

    The minimizing b in the dual function is b = hi if lambda > 0 and b = lo
    if b <= 0.  When L' (lamdbda) = 0 with lambda != 0, either x'a = hi or
    x'a = lo.  The minimum is attained at lambda = 0 if and only if the
    slope of L is negative at lambda = 0+ and positive at lambda = 0-.
    This is equivalent to the inequalities

              lo <= a' proj (y) <= hi .

    The solution technique is to start with an initial guess lambda for
    mu and search for a zero of L'. We have the following cases:

    1. lambda >= 0, L'(lambda+) >= 0: mu >= lambda. If L' = 0, then done.
                                    Otherwise, increase lambda using napup until
                                    slope vanishes

    2. lambda <= 0, L'(lambda-) <= 0: mu <= lambda. If L' = 0, then done.
                                    Otherwise, decrease lambda using napdown
                                    until slope vanishes

    3. lambda >= 0, L'(lambda+)  < 0: If L' (0-) < 0, then mu < 0. Call napdown
                                    with lambda = 0 as starting guess.  If
                                    L' (0+) > 0, then 0 < mu < lambda. Call
                                    napdown with given starting guess lambda.
                                    Otherwise, if L' (0+) <= 0, then mu = 0.

    4. lambda <= 0, L'(lambda-)  > 0: If L' (0+) > 0, then mu > 0. Call napup
                                    with lambda = 0 as starting guess.  If
                                    L' (0-) < 0, then lambda < mu < 0.  Call
                                    napup with given starting guess lambda.
                                    Otherwise, if L' (0-) >= 0, then mu = 0.

    By the "free set" we mean those i for which 0 < x_i (lambda) < 1.  The
    total time taken by napsack is O (n + h log n), where n is the size of y,
    h is the number of times an element of x (lambda) moves off the boundary
    into the free set (entries between zero and one) plus the number of times
    elements move from the free set to the opposite boundary.  A heap is used
    to hold the entries in the boundary and in the free set.  If the slope
    vanishes at either the starting lambda or at lambda = 0, then no heap is
    constructed, and the time is just O (n).

    If we have a guess for which components of x will be free at the optimal
    solution, then we can obtain a good guess for the starting lambda by
    setting the slope of the dual function to zero and solving for lambda.  If
    FreeSet_status is not NULL, then the FreeSet_status array is used to
    compute a starting guess for lambda based on the estimated free indices.
    Note that FreeSet_status is an INPUT array, it is not modified by this
    routine.
   ========================================================================== */

#include "Mongoose_Debug.hpp"
#include "Mongoose_Internal.hpp"
#include "Mongoose_Logger.hpp"
#include "Mongoose_QPNapDown.hpp"
#include "Mongoose_QPNapUp.hpp"

#include <cfloat>

namespace Mongoose
{

#ifndef NDEBUG
void checkatx(double *x, double *a, Int n, double lo, double hi, double tol)
{
    double atx = 0.;
    int ok     = 1;
    for (Int k = 0; k < n; k++)
    {
        if (x[k] < 0.)
        {
            ok = 0;
            PR(("x [%ld] = %g < 0!\n", k, x[k]));
        }
        if (x[k] > 1.)
        {
            ok = 0;
            PR(("x [%ld] = %g > 1!\n", k, x[k]));
        }
        if (a != NULL)
        {
            double ak = (a) ? a[k] : 1;
            PR(("a'x = %g * %g = %g\n", ak, x[k], ak * x[k]));
            atx += ak * x[k];
        }
        else
        {
            PR(("a'x = %g * %g = %g\n", 1.0, x[k], x[k]));
            atx += x[k];
        }
    }
    if (atx < lo - tol)
    {
        ok = 0;
    }
    if (atx > hi + tol)
    {
        ok = 0;
    }
    if (!ok)
    {
        PR(("tol = %g\n", tol));
        PR(("napsack error! lo %g a'x %g hi %g\n", lo, atx, hi));
        FFLUSH;
        ASSERT(0);
    }
}
#endif

double QPNapsack    /* return the final lambda */
    (double *x,     /* holds y on input, and the solution x on output */
     Int n,         /* size of x, constraint lo <= a'x <= hi */
     double lo,     /* partition lower bound */
     double hi,     /* partition upper bound */
     double *Gw,    /* vector of nodal weights */
     double Lambda, /* initial guess for lambda */
     const Int *FreeSet_status,
     /* FreeSet_status [i] = +1,-1, or 0 on input,
        for 3 cases: x_i =1,0, or 0< x_i< 1.  Not modified. */
     double *w,  /* work array of size n   */
     Int *heap1, /* work array of size n+1 */
     Int *heap2, /* work array of size n+1 */
     double tol  /* Gradient projection tolerance */
    )
{
    (void)tol; // unused variable except during debug
    double lambda = Lambda;
    PR(("QPNapsack start [\n"));

    /* ---------------------------------------------------------------------- */
    /* compute starting guess if FreeSet_status is provided and lambda != 0 */
    /* ---------------------------------------------------------------------- */

    if ((FreeSet_status != NULL) && (lambda != 0))
    {
        double asum  = (lambda > 0 ? -hi : -lo);
        double a2sum = 0.;

        for (Int k = 0; k < n; k++)
        {
            if (FreeSet_status[k] == 1)
            {
                asum += (Gw) ? Gw[k] : 1;
            }
            else if (FreeSet_status[k] == 0)
            {
                double ai = (Gw) ? Gw[k] : 1;
                asum += x[k] * ai;
                a2sum += ai * ai;
            }
        }

        if (a2sum != 0.)
            lambda = asum / a2sum;
    }

    /* ---------------------------------------------------------------------- */
    /* compute the initial slope */
    /* ---------------------------------------------------------------------- */

    double slope = 0;
    for (Int k = 0; k < n; k++)
    {
        double xi = x[k] - ((Gw) ? Gw[k] : 1) * lambda;
        if (xi >= 1.)
        {
            slope += ((Gw) ? Gw[k] : 1);
        }
        else if (xi > 0.)
        {
            slope += ((Gw) ? Gw[k] : 1) * xi;
        }
    }
    PR(("slope %g lo %g hi %g\n", slope, lo, hi));

    /* remember: must still adjust slope by "-hi" or "-lo" for its final value
     */

    if ((lambda >= 0.) && (slope >= hi)) /* case 1 */
    {
        if (slope > hi)
        {
            PR(("napsack case 1 up\n"));
            lambda = QPNapUp(x, n, lambda, Gw, hi, w, heap1, heap2);
            lambda = std::max(0., lambda);
        }
        else
        {
            PR(("napsack case 1 nothing\n"));
        }
    }
    else if ((lambda <= 0.) && (slope <= lo)) /* case 2 */
    {
        if (slope < lo)
        {
            PR(("napsack case 2 down\n"));
            lambda = QPNapDown(x, n, lambda, Gw, lo, w, heap1, heap2);
            lambda = std::min(lambda, 0.);
        }
        else
        {
            PR(("napsack case 2 nothing\n"));
        }
    }
    else /* case 3 or 4 */
    {
        if (lambda != 0.)
        {
            double slope0 = 0.;
            for (Int k = 0; k < n; k++)
            {
                double xi = x[k];
                if (xi >= 1.)
                {
                    slope0 += ((Gw) ? Gw[k] : 1);
                }
                else if (xi > 0.)
                {
                    slope0 += ((Gw) ? Gw[k] : 1) * xi;
                }
            }

            if ((lambda >= 0) && (slope < hi)) /* case 3 */
            {
                if (slope0 < lo)
                {
                    PR(("napsack case 3a down\n"));
                    lambda = 0.;
                    lambda = QPNapDown(x, n, lambda, Gw, lo, w, heap1, heap2);
                    if (lambda > 0.)
                    {
                        lambda = 0.;
                    }
                }
                else if (slope0 > hi)
                {
                    PR(("napsack case 3b down\n"));
                    lambda = QPNapDown(x, n, lambda, Gw, hi, w, heap1, heap2);
                    if (lambda < 0.)
                        lambda = 0.;
                }
                else
                {
                    PR(("napsack case 3c nothing\n"));
                    lambda = 0.;
                }
            }
            else /* ( (lambda <= 0) && (slope > lo) )  case 4 */
            {
                if (slope0 > hi)
                {
                    PR(("napsack case 4a up\n"));
                    lambda = 0.;
                    lambda = QPNapUp(x, n, lambda, Gw, hi, w, heap1, heap2);
                    lambda = std::max(lambda, 0.);
                }
                else if (slope0 < lo)
                {
                    PR(("napsack case 4b up\n"));
                    lambda = QPNapUp(x, n, lambda, Gw, lo, w, heap1, heap2);
                    lambda = std::min(0., lambda);
                }
                else
                {
                    PR(("napsack case 4c nothing\n"));
                    lambda = 0.;
                }
            }
        }
        else /* lambda == 0 */
        {
            if (slope < hi) /* case 3 */
            {
                if (slope < lo)
                {
                    PR(("napsack case 3d down\n"));
                    lambda = QPNapDown(x, n, lambda, Gw, lo, w, heap1, heap2);
                    lambda = std::min(0., lambda);
                }
                else
                {
                    PR(("napsack case 3e nothing\n"));
                }
            }
            else /* ( slope > lo )                    case 4 */
            {
                if (slope > hi)
                {
                    PR(("napsack case 4d up\n"));
                    lambda = QPNapUp(x, n, lambda, Gw, hi, w, heap1, heap2);
                    lambda = std::max(lambda, 0.);
                }
                else
                {
                    PR(("napsack case 4e nothing\n"));
                }
            }
        }
    }

    /* ---------------------------------------------------------------------- */
    /* replace x by x (lambda) */
    /* ---------------------------------------------------------------------- */

    PR(("lambda %g\n", lambda));
    double atx    = 0;
    Int last_move = 0;
    for (Int k = 0; k < n; k++)
    {
        double xi = x[k] - ((Gw) ? Gw[k] : 1) * lambda;

        if (xi < 0)
        {
            x[k] = 0;
        }
        else if (xi > 1)
        {
            x[k] = 1;
        }
        else
        {
            x[k]      = xi;
            last_move = k;
        }

        double newatx = atx + ((Gw) ? Gw[k] : 1) * x[k];

        // Correction step if we go too far
        if (newatx > hi)
        {
            double diff = hi - atx - FLT_MIN;
            // Need diff = Gw[k] * x[k], so...
            x[k]   = diff / ((Gw) ? Gw[k] : 1);
            newatx = atx + ((Gw) ? Gw[k] : 1) * x[k];
        }
        atx = newatx;
    }

    // Correction step if we didn't go far enough
    for (Int kk = 0; kk < n && atx < lo; kk++)
    {
        Int k = last_move;
        atx -= ((Gw) ? Gw[k] : 1) * x[k];
        double diff = lo - atx;
        // Need diff = Gw[k] * x[k], so...
        x[k] = std::min(1., diff / ((Gw) ? Gw[k] : 1));
        atx += ((Gw) ? Gw[k] : 1) * x[k];
        last_move = (k + 1) % n;
    }

#ifndef NDEBUG
    // Define check tolerance by lambda values
    double atx_tol = log10(std::max(fabs(lambda), fabs(Lambda))
                           / (1e-9 + std::min(fabs(lambda), fabs(Lambda))));
    atx_tol        = std::max(atx_tol, tol);

    checkatx(x, Gw, n, lo, hi, atx_tol);
#endif

    PR(("QPNapsack done ]\n"));

    return lambda;
}

} // end namespace Mongoose