• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dance858 / PSLP / 21378445192

26 Jan 2026 11:45PM UTC coverage: 84.938% (-4.1%) from 88.988%
21378445192

Pull #33

github

web-flow
Merge 61d94ca18 into 8137ce2f7
Pull Request #33: Windows build

1191 of 1401 branches covered (85.01%)

Branch coverage included in aggregate %.

148 of 157 new or added lines in 19 files covered. (94.27%)

200 existing lines in 11 files now uncovered.

3687 of 4342 relevant lines covered (84.91%)

130.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.99
/src/explorers/Parallel_rows.c
1
/*
2
 * Copyright 2025 Daniel Cederberg
3
 *
4
 * This file is part of the PSLP project (LP Presolver).
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18

19
#include "Parallel_rows.h"
20
#include "Bounds.h"
21
#include "Constraints.h"
22
#include "CoreTransformations.h"
23
#include "Debugger.h"
24
#include "Matrix.h"
25
#include "Numerics.h"
26
#include "RowColViews.h"
27
#include "State.h"
28
#include "Workspace.h"
29
#include "iVec.h"
30
#include "limits.h" // for INT_MAX
31
#include "utils.h"
32
#include <math.h> // For round()
33
#include <stdint.h>
34

35
// global variables needed for qsort
36
static int *global_sparsity_IDs;
37
static int *global_coeff_hashes;
38

39
// djb2 hash function
40
static inline uint32_t hash_int_array(const int *arr, int size)
124✔
41
{
42
    uint32_t hash = 5381;
124✔
43
    for (int i = 0; i < size; i++)
646✔
44
    {
45
        hash = ((hash << 5) + hash) + arr[i];
522✔
46
    }
47

48
    return hash;
124✔
49
}
50

51
#define INV_PRECISION 1e6
52

53
// djb2 hash function, with normalization of values and rounding of doubles
54
static inline uint32_t hash_double_array_with_scale(const double *arr, int size)
124✔
55
{
56
    // find max value of row
57
    double max = ABS(arr[0]);
124✔
58
    for (int i = 1; i < size; i++)
522✔
59
    {
60
        if (ABS(arr[i]) > max)
398✔
61
        {
62
            max = ABS(arr[i]);
174✔
63
        }
64
    }
65
    double scale = (arr[0] > 0) ? 1 / max : -1 / max;
124✔
66

67
    // compute has value
68
    uint32_t hash = 5381;
124✔
69
    for (int i = 0; i < size; i++)
646✔
70
    {
71
        uint32_t scaled = (uint32_t) (round((arr[i] * scale) * INV_PRECISION));
522✔
72
        hash = ((hash << 5) + hash) + scaled;
522✔
73
    }
74

75
    return hash;
124✔
76
}
77

78
#ifndef TESTING
79
static inline
80
#endif
81
    void compute_supp_and_coeff_hash(const Matrix *A, const RowTag *rowtags,
42✔
82
                                     int *sparsity_IDs, int *coeff_hashes,
83
                                     RowTag INACTIVE_TAG)
84
{
85

86
    for (int i = 0; i < A->m; i++)
236✔
87
    {
88
        if (HAS_TAG(rowtags[i], INACTIVE_TAG))
194✔
89
        {
90
            // important to set support_ID for inactive rows to place them
91
            // last in the sort
92
            sparsity_IDs[i] = INT_MAX;
70✔
93
            continue;
70✔
94
        }
95

96
        // this check makes sense since we also use this function to
97
        // find parallel columns
98
        assert(!HAS_TAG(rowtags[i], C_TAG_INACTIVE));
124✔
99

100
        int len = A->p[i].end - A->p[i].start;
124✔
101
        sparsity_IDs[i] = (int) hash_int_array(A->i + A->p[i].start, len);
124✔
102
        coeff_hashes[i] =
124✔
103
            (int) hash_double_array_with_scale(A->x + A->p[i].start, len);
124✔
104
    }
105
}
42✔
106

107
int comparator(const void *a, const void *b)
270✔
108
{
109
    int idxA = *(const int *) a;
270✔
110
    int idxB = *(const int *) b;
270✔
111

112
    if (global_sparsity_IDs[idxA] < global_sparsity_IDs[idxB])
270✔
113
    {
114
        return -1;
45✔
115
    }
116

117
    if (global_sparsity_IDs[idxA] > global_sparsity_IDs[idxB])
225✔
118
    {
119
        return 1;
96✔
120
    }
121

122
    if (global_coeff_hashes[idxA] < global_coeff_hashes[idxB])
129✔
123
    {
124
        return -1;
43✔
125
    }
126

127
    if (global_coeff_hashes[idxA] > global_coeff_hashes[idxB])
86✔
128
    {
129
        return 1;
32✔
130
    }
131

132
    return 0;
54✔
133
}
134

135
// Sort function
136
static inline void sort_rows(int *rows, int n_rows, int *sparsity_IDs,
42✔
137
                             int *coeff_hashes)
138
{
139
    global_sparsity_IDs = sparsity_IDs;
42✔
140
    global_coeff_hashes = coeff_hashes;
42✔
141
    qsort(rows, (size_t) n_rows, sizeof(int), comparator);
42✔
142
}
42✔
143

144
static inline int get_bin_size(int start, int n_rows, const int *rows,
104✔
145
                               const int *sparsity_IDs, const int *coeff_hashes)
146
{
147
    int sparsity_ID = sparsity_IDs[rows[start]];
104✔
148
    int coeff_hash = coeff_hashes[rows[start]];
104✔
149
    int i;
150
    for (i = start + 1; i < n_rows; ++i)
124✔
151
    {
152
        if (sparsity_IDs[rows[i]] != sparsity_ID ||
119✔
153
            coeff_hashes[rows[i]] != coeff_hash)
68✔
154
        {
155
            break;
156
        }
157
    }
158
    return i - start;
104✔
159
}
160

161
#ifndef NDEBUG
162
// make sure that the rows in the same bin have the same coefficient
163
// hash value and the same sparsity ID
164
void ASSERT_BIN_CORRECT(const int *coeff_hashes, const int *sparsity_IDs,
7✔
165
                        const int *rows, int bin_start, int bin_size,
166
                        const RowTag *row_tags, RowTag INACTIVE_TAG)
167
{
168
    const int *bin_temp = rows + bin_start;
7✔
169
    int coeff_hash_start = coeff_hashes[bin_temp[0]];
7✔
170
    int sparsity_ID_start = sparsity_IDs[bin_temp[0]];
7✔
171

172
    for (int k = 1; k < bin_size; ++k)
27✔
173
    {
174
        assert(coeff_hashes[bin_temp[k]] == coeff_hash_start);
20✔
175
        assert(sparsity_IDs[bin_temp[k]] == sparsity_ID_start);
20✔
176
        assert(!HAS_TAG(row_tags[bin_temp[k]], INACTIVE_TAG));
20✔
177
    }
178
}
7✔
179

180
void VERIFY_PARALLEL_ROWS(const Matrix *A, const RowTag *rows_tags,
42✔
181
                          const int *parallel_rows, const iVec *group_starts)
182
{
183
    int i, j, k, start, end, n_rows_this_group, len1;
184
    int n_groups = (int) group_starts->len - 1;
42✔
185

186
    for (i = 0; i < n_groups; ++i)
49✔
187
    {
188
        start = group_starts->data[i];
7✔
189
        end = group_starts->data[i + 1];
7✔
190
        n_rows_this_group = end - start;
7✔
191

192
        // check that the group has at least two rows
193
        assert(n_rows_this_group > 1);
7✔
194

195
        // check that all rows in the group are active
196
        const int *temp = parallel_rows + start;
7✔
197
        ASSERT_NO_INACTIVE_ROWS(temp, rows_tags, n_rows_this_group);
34✔
198

199
        // check that all rows have the same support
200
        len1 = A->p[parallel_rows[start]].end - A->p[parallel_rows[start]].start;
7✔
201
        int *cols1 = A->i + A->p[parallel_rows[start]].start;
7✔
202
        for (j = start + 1; j < end; ++j)
27✔
203
        {
204
            assert(len1 ==
20✔
205
                   A->p[parallel_rows[j]].end - A->p[parallel_rows[j]].start);
206
            ARRAYS_EQUAL_INT(cols1, A->i + A->p[parallel_rows[j]].start, len1);
20✔
207
        }
208

209
        // check that all rows have same normalized coefficients
210
        double *vals1 = A->x + A->p[parallel_rows[start]].start;
7✔
211
        for (j = start + 1; j < end; ++j)
27✔
212
        {
213
            double *vals2 = A->x + A->p[parallel_rows[j]].start;
20✔
214
            double ratio = vals1[0] / vals2[0];
20✔
215
            for (k = 1; k < len1; ++k)
72✔
216
            {
217
                assert(IS_ZERO_FEAS_TOL(vals1[k] - ratio * vals2[k]));
52✔
218
            }
219
        }
220
    }
221
}
42✔
222
#endif
223

224
/*  In one bin there can theoretically be several groups of parallel rows,
225
    but hopefully it is unlikely that there are more than one group of
226
    parallel rows. We therefore only try to catch one of the groups.
227

228
    Due to our choice of hash function, we must verify that the rows
229
    have both the same support and the same coefficients (up to a multiple).
230

231
    * Returns the number of parallel rows found in the bin.
232
    * bin: pointer pointing to the FIRST row in the bin
233
    * bin_size: number of rows in the bin
234
    * parallel_rows: parallel rows are stored here, starting from
235
   parallel_rows[0]
236
*/
237
static inline int find_parallel_rows_in_bin(const Matrix *A, const int *bin,
7✔
238
                                            int bin_size, int *parallel_rows,
239
                                            iVec *group_starts)
240
{
241
    assert(bin_size > 1);
7✔
242
    int i, j, first_row_in_bin = bin[0];
7✔
243
    int n_new_parallel_rows = 0;
7✔
244

245
    const int *cols1 = A->i + A->p[bin[0]].start;
7✔
246
    const double *vals1 = A->x + A->p[bin[0]].start;
7✔
247
    int len1 = A->p[bin[0]].end - A->p[bin[0]].start;
7✔
248

249
    for (i = 1; i < bin_size; ++i)
27✔
250
    {
251
        const int *cols2 = A->i + A->p[bin[i]].start;
20✔
252
        const double *vals2 = A->x + A->p[bin[i]].start;
20✔
253
        int len2 = A->p[bin[i]].end - A->p[bin[i]].start;
20✔
254

255
        // check that the rows have same size
256
        if (len1 != len2)
20✔
257
        {
258
            continue;
×
259
        }
260

261
        // check that the rows have same support and coefficients up to multiple
262
        // (must start at j = 0 because of support check)
263
        double ratio = vals1[0] / vals2[0];
20✔
264
        for (j = 0; j < len2; ++j)
92✔
265
        {
266
            // if we run into numerical issues we might want to do
267
            // diff = vals1[j] / vals2[j] - ratio
268
            double diff = vals1[j] - ratio * vals2[j];
72✔
269

270
            if (!IS_ZERO_FEAS_TOL(diff) || cols1[j] != cols2[j])
72✔
271
            {
272
                break;
273
            }
274
        }
275

276
        // if j == len2, we have found a parallel row
277
        if (j == len2)
20✔
278
        {
279
            parallel_rows[n_new_parallel_rows] = bin[i];
20✔
280
            n_new_parallel_rows++;
20✔
281
        }
282
    }
283

284
    // add first row in bin
285
    if (n_new_parallel_rows > 0)
7✔
286
    {
287
        parallel_rows[n_new_parallel_rows] = first_row_in_bin;
7✔
288
        n_new_parallel_rows++;
7✔
289
        iVec_append(group_starts,
7✔
290
                    group_starts->data[group_starts->len - 1] + n_new_parallel_rows);
7✔
291
    }
292

293
    return n_new_parallel_rows;
7✔
294
}
295

296
// replaced rows with parallel_rows for less memory usage
297
void find_parallel_rows(const Matrix *A, const RowTag *r_Tags, iVec *group_starts,
42✔
298
                        int *parallel_rows, int *sparsity_IDs, int *coeff_hashes,
299
                        RowTag INACTIVE_TAG)
300
{
301
    int i, bin_size;
302
    int n_p_rows_total = 0;
42✔
303
    // ------------------------------------------------------------------------
304
    //             compute sparsity_IDs and coeff_hashes
305
    // ------------------------------------------------------------------------
306
    compute_supp_and_coeff_hash(A, r_Tags, sparsity_IDs, coeff_hashes, INACTIVE_TAG);
42✔
307

308
    // ------------------------------------------------------------------------
309
    // Make rows in the same bin appear next to each other. The sort makes sure
310
    // that rows with the same sparsity pattern and the same coefficient hash
311
    // value end up next to each other.
312
    // ------------------------------------------------------------------------
313
    for (i = 0; i < A->m; i++)
236✔
314
    {
315
        parallel_rows[i] = i;
194✔
316
    }
317

318
    sort_rows(parallel_rows, A->m, sparsity_IDs, coeff_hashes);
42✔
319

320
#ifndef NDEBUG
321
    if (INACTIVE_TAG == R_TAG_INACTIVE)
42✔
322
    {
323
        ASSERT_NO_ACTIVE_STON_ROWS(A, r_Tags);
34✔
324
    }
325
#endif
326

327
    // ------------------------------------------------------------------------
328
    // Start looping through the bins. Inactive rows have been placed in the
329
    // end of 'rows'. As soon as we meet an inactive row we can stop, since
330
    // all rows appearing after it are also inactive
331
    // ------------------------------------------------------------------------
332
    iVec_clear_no_resize(group_starts);
42✔
333
    iVec_append(group_starts, 0);
42✔
334
    for (i = 0; i < A->m; i++)
146✔
335
    {
336
        if (HAS_TAG(r_Tags[parallel_rows[i]], INACTIVE_TAG))
141✔
337
        {
338
            break;
37✔
339
        }
340

341
        bin_size = get_bin_size(i, A->m, parallel_rows, sparsity_IDs, coeff_hashes);
104✔
342

343
        // find the parallel rows in the bin
344
        if (bin_size > 1)
104✔
345
        {
346
            DEBUG(ASSERT_BIN_CORRECT(coeff_hashes, sparsity_IDs, parallel_rows, i,
7✔
347
                                     bin_size, r_Tags, INACTIVE_TAG););
348

349
            n_p_rows_total += find_parallel_rows_in_bin(
14✔
350
                A, parallel_rows + i, bin_size, parallel_rows + n_p_rows_total,
7✔
351
                group_starts);
352

353
            i += bin_size - 1;
7✔
354
        }
355
    }
356
    assert(n_p_rows_total == group_starts->data[group_starts->len - 1]);
42✔
357
    DEBUG(VERIFY_PARALLEL_ROWS(A, r_Tags, parallel_rows, group_starts););
42✔
358
}
42✔
359

360
static inline PresolveStatus process_single_bin(const Constraints *constraints,
1✔
361
                                                const int *bin, int bin_size)
362
{
363
    assert(bin_size > 1);
1✔
364
    const RowTag *row_tags = constraints->row_tags;
1✔
365
    const Matrix *A = constraints->A;
1✔
366
    const double *rhs = constraints->rhs;
1✔
367
    const double *lhs = constraints->lhs;
1✔
368
    double remaining_row_new_rhs, remaining_row_new_lhs, remaining_row_coeff;
369
    double other_row_coeff, ratio, other_row_rhs_scaled, other_row_lhs_scaled;
370
    double other_row_lhs_scale_factor = 1.0;
1✔
371
    double other_row_rhs_scale_factor = 1.0;
1✔
372
    int other_row_lhs_index = -1;
1✔
373
    int other_row_rhs_index = -1;
1✔
374
    bool is_rhs_inf_other_row, is_lhs_inf_other_row;
375

376
    // ------------------------------------------------------------------------
377
    //  Initialize quantities for the remaining row. These quantities will be
378
    //  updated as we process the bin.
379
    // ------------------------------------------------------------------------
380
    bool is_remaining_row_eq, is_rhs_inf_remaining_row, is_lhs_inf_remaining_row;
381
    int remaining_row_idx = bin[0];
1✔
382
    is_remaining_row_eq = HAS_TAG(row_tags[remaining_row_idx], R_TAG_EQ);
1✔
383
    is_rhs_inf_remaining_row = HAS_TAG(row_tags[remaining_row_idx], R_TAG_RHS_INF);
1✔
384
    is_lhs_inf_remaining_row = HAS_TAG(row_tags[remaining_row_idx], R_TAG_LHS_INF);
1✔
385
    remaining_row_new_rhs = rhs[remaining_row_idx];
1✔
386
    remaining_row_new_lhs = lhs[remaining_row_idx];
1✔
387
    remaining_row_coeff = A->x[A->p[remaining_row_idx].start];
1✔
388

389
    for (int i = 1; i < bin_size; ++i)
3✔
390
    {
391
        int other_row_idx = bin[i];
2✔
392
        bool is_other_row_eq = HAS_TAG(row_tags[other_row_idx], R_TAG_EQ);
2✔
393

394
        if (!is_remaining_row_eq && is_other_row_eq)
2✔
395
        {
396
            // swap(remaining_row, other_row)
UNCOV
397
            int temp = remaining_row_idx;
×
UNCOV
398
            remaining_row_idx = other_row_idx;
×
UNCOV
399
            other_row_idx = temp;
×
400

UNCOV
401
            is_remaining_row_eq = true;
×
UNCOV
402
            is_other_row_eq = false;
×
UNCOV
403
            remaining_row_new_rhs = rhs[remaining_row_idx];
×
UNCOV
404
            remaining_row_new_lhs = lhs[remaining_row_idx];
×
UNCOV
405
            remaining_row_coeff = A->x[A->p[remaining_row_idx].start];
×
406
        }
407

408
        assert(!(is_other_row_eq && !is_remaining_row_eq));
2✔
409

410
        other_row_coeff = A->x[A->p[other_row_idx].start];
2✔
411
        ratio = remaining_row_coeff / other_row_coeff;
2✔
412
        other_row_rhs_scaled = rhs[other_row_idx] * ratio;
2✔
413
        other_row_lhs_scaled = lhs[other_row_idx] * ratio;
2✔
414
        is_rhs_inf_other_row = HAS_TAG(row_tags[other_row_idx], R_TAG_RHS_INF);
2✔
415
        is_lhs_inf_other_row = HAS_TAG(row_tags[other_row_idx], R_TAG_LHS_INF);
2✔
416

417
        // -----------------------------------------------------------------------
418
        // if both rows are equalities we check that they are consistent
419
        // -----------------------------------------------------------------------
420
        if (is_remaining_row_eq && is_other_row_eq)
2✔
421
        {
UNCOV
422
            if (!IS_EQUAL_FEAS_TOL(remaining_row_new_rhs, other_row_rhs_scaled))
×
423
            {
424
                return INFEASIBLE;
×
425
            }
426
        }
427
        // -----------------------------------------------------------------------
428
        // if only the remaining row is an equality we check for infeasibility
429
        // -----------------------------------------------------------------------
430
        else if (is_remaining_row_eq)
2✔
431
        {
UNCOV
432
            bool infeas_rhs =
×
UNCOV
433
                !is_rhs_inf_other_row &&
×
UNCOV
434
                ((ratio > 0 && remaining_row_new_rhs > other_row_rhs_scaled) ||
×
UNCOV
435
                 (ratio < 0 && remaining_row_new_rhs < other_row_rhs_scaled));
×
UNCOV
436
            bool infeas_lhs =
×
UNCOV
437
                !is_lhs_inf_other_row &&
×
UNCOV
438
                ((ratio > 0 && remaining_row_new_rhs < other_row_lhs_scaled) ||
×
UNCOV
439
                 (ratio < 0 && remaining_row_new_rhs > other_row_lhs_scaled));
×
440

UNCOV
441
            if (infeas_rhs || infeas_lhs)
×
442
            {
UNCOV
443
                return INFEASIBLE;
×
444
            }
445
        }
446
        // ----------------------------------------------------------------------
447
        // if both rows are inequalities we find the tightest lhs and rhs
448
        // ----------------------------------------------------------------------
449
        else
450
        {
451
            assert(!is_remaining_row_eq && !is_other_row_eq);
2✔
452
            if (ratio > 0)
2✔
453
            {
454
                // possibly update rhs of remaining row
455
                if (!is_rhs_inf_other_row &&
1✔
UNCOV
456
                    (is_rhs_inf_remaining_row ||
×
457
                     other_row_rhs_scaled < remaining_row_new_rhs))
458
                {
459
                    other_row_rhs_index = other_row_idx;
×
460
                    other_row_rhs_scale_factor = ratio;
×
461
                    remaining_row_new_rhs = other_row_rhs_scaled;
×
462
                    is_rhs_inf_remaining_row = false;
×
463
                }
464

465
                // possibly update lhs of remaining row
466
                if (!is_lhs_inf_other_row &&
1✔
467
                    (is_lhs_inf_remaining_row ||
1✔
468
                     other_row_lhs_scaled > remaining_row_new_lhs))
469
                {
470
                    other_row_lhs_index = other_row_idx;
×
471
                    other_row_lhs_scale_factor = ratio;
×
472
                    remaining_row_new_lhs = other_row_lhs_scaled;
×
473
                    is_lhs_inf_remaining_row = false;
×
474
                }
475
            }
476
            else
477
            {
478
                // possibly update lhs of remaining row
479
                if (!is_rhs_inf_other_row &&
1✔
480
                    (is_lhs_inf_remaining_row ||
1✔
481
                     other_row_rhs_scaled > remaining_row_new_lhs))
482
                {
483
                    other_row_lhs_index = other_row_idx;
1✔
484
                    other_row_lhs_scale_factor = ratio;
1✔
485
                    remaining_row_new_lhs = other_row_rhs_scaled;
1✔
486
                    is_lhs_inf_remaining_row = false;
1✔
487
                }
488

489
                // possibly update rhs of remaining row
490
                if (!is_lhs_inf_other_row &&
1✔
UNCOV
491
                    (is_rhs_inf_remaining_row ||
×
492
                     other_row_lhs_scaled < remaining_row_new_rhs))
493
                {
UNCOV
494
                    other_row_rhs_index = other_row_idx;
×
UNCOV
495
                    other_row_rhs_scale_factor = ratio;
×
UNCOV
496
                    remaining_row_new_rhs = other_row_lhs_scaled;
×
UNCOV
497
                    is_rhs_inf_remaining_row = false;
×
498
                }
499
            }
500
        }
501
    }
502

503
    // ---------------------------------------------------------------------------------
504
    // if the remaining row is an inequality we may need to change the rhs and
505
    // lhs
506
    // ---------------------------------------------------------------------------------
507
    PostsolveInfo *postsolve_info = constraints->state->postsolve_info;
1✔
508
    if (!is_remaining_row_eq)
1✔
509
    {
510
        assert(!HAS_TAG(row_tags[remaining_row_idx], R_TAG_EQ));
1✔
511
        int start = A->p[remaining_row_idx].start;
1✔
512
        int len = A->p[remaining_row_idx].end - start;
1✔
513
        RowTag *row_tag = constraints->row_tags + remaining_row_idx;
1✔
514
        RowView remaining_row = new_rowview(A->x + start, A->i + start, &len, NULL,
1✔
515
                                            constraints->lhs + remaining_row_idx,
1✔
516
                                            constraints->rhs + remaining_row_idx,
1✔
517
                                            row_tag, remaining_row_idx);
518

519
        if (!is_rhs_inf_remaining_row &&
1✔
UNCOV
520
            remaining_row_new_rhs != rhs[remaining_row_idx])
×
521
        {
UNCOV
522
            assert(!IS_ABS_INF(remaining_row_new_rhs));
×
UNCOV
523
            assert(remaining_row_new_rhs < rhs[remaining_row_idx]);
×
524

UNCOV
525
            if (change_rhs_of_ineq(&remaining_row, remaining_row_new_rhs,
×
UNCOV
526
                                   constraints->state->col_locks,
×
UNCOV
527
                                   constraints->state->dton_rows, postsolve_info,
×
528
                                   other_row_rhs_index,
529
                                   other_row_rhs_scale_factor) == INFEASIBLE)
530
            {
531
                return INFEASIBLE;
×
532
            }
533
        }
534

535
        if (!is_lhs_inf_remaining_row &&
1✔
536
            remaining_row_new_lhs != lhs[remaining_row_idx])
1✔
537
        {
538
            assert(!IS_ABS_INF(remaining_row_new_lhs));
1✔
539
            assert(remaining_row_new_lhs > lhs[remaining_row_idx]);
1✔
540

541
            if (change_lhs_of_ineq(&remaining_row, remaining_row_new_lhs,
1✔
542
                                   constraints->state->col_locks,
1✔
543
                                   constraints->state->dton_rows, postsolve_info,
1✔
544
                                   other_row_lhs_index,
545
                                   other_row_lhs_scale_factor) == INFEASIBLE)
546
            {
547
                return INFEASIBLE;
×
548
            }
549
        }
550
    }
551

552
    // --------------------------------------------------------------------------------
553
    // When we arrive here we know which row should remain in the problem. We
554
    // mark all other rows as inactive.
555
    // --------------------------------------------------------------------------------
556
    for (int i = 0; i < bin_size; ++i)
4✔
557
    {
558
        if (bin[i] != remaining_row_idx)
3✔
559
        {
560
            set_row_to_inactive(bin[i], constraints->row_tags + bin[i],
2✔
561
                                constraints->state->rows_to_delete, postsolve_info,
2✔
562
                                0.0);
563
        }
564
    }
565

566
    return UNCHANGED;
1✔
567
}
568

569
// groups is a vector of size 'n_groups', where 'parallel_rows[i]' is the
570
// index of the first row in group 'i'
571
static PresolveStatus process_all_bins(const Constraints *constraints,
34✔
572
                                       const int *parallel_rows, const iVec *groups)
573
{
574
    int n_groups = (int) groups->len - 1;
34✔
575

576
    for (int i = 0; i < n_groups; ++i)
35✔
577
    {
578
        int n_rows_this_group = groups->data[i + 1] - groups->data[i];
1✔
579
        if (process_single_bin(constraints, parallel_rows + groups->data[i],
1✔
580
                               n_rows_this_group) == INFEASIBLE)
581
        {
UNCOV
582
            return INFEASIBLE;
×
583
        }
584
    }
585

586
    return UNCHANGED;
34✔
587
}
588

589
PresolveStatus remove_parallel_rows(Constraints *constraints)
34✔
590
{
591
    assert(constraints->state->ston_rows->len == 0);
34✔
592
    assert(constraints->state->empty_rows->len == 0);
34✔
593
    assert(constraints->state->empty_cols->len == 0);
34✔
594
    DEBUG(verify_problem_up_to_date(constraints));
34✔
595

596
    int *parallel_rows = constraints->state->work->iwork_n_rows;
34✔
597
    int *sparsity_IDs = constraints->state->work->iwork1_max_nrows_ncols;
34✔
598
    int *coeff_hashes = constraints->state->work->iwork2_max_nrows_ncols;
34✔
599
    iVec *group_starts = constraints->state->work->int_vec;
34✔
600

601
    find_parallel_rows(constraints->A, constraints->row_tags, group_starts,
34✔
602
                       parallel_rows, sparsity_IDs, coeff_hashes, R_TAG_INACTIVE);
603

604
    PresolveStatus status =
605
        process_all_bins(constraints, parallel_rows, group_starts);
34✔
606

607
    delete_inactive_rows(constraints);
34✔
608

609
    DEBUG(verify_problem_up_to_date(constraints));
34✔
610

611
    return status;
34✔
612
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc