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

dance858 / PSLP / 21523435862

30 Jan 2026 04:45PM UTC coverage: 88.997% (+0.009%) from 88.988%
21523435862

Pull #34

github

web-flow
Merge 9f2c92b06 into 8137ce2f7
Pull Request #34: fix-windows-build

1249 of 1400 branches covered (89.21%)

Branch coverage included in aggregate %.

222 of 233 new or added lines in 20 files covered. (95.28%)

1 existing line in 1 file now uncovered.

3855 of 4335 relevant lines covered (88.93%)

3374.63 hits per line

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

96.24
/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 <PSLP_warnings.h>
33
#include <math.h> // For round()
34
#include <stdint.h>
35

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

40
// djb2 hash function
41
static inline uint32_t hash_int_array(const int *arr, int size)
2,318✔
42
{
43
    /* disable sign-conversion compiler warning*/
44
    PSLP_DIAG_PUSH();
45
    PSLP_DIAG_IGNORE_SIGN_CONVERSION();
46

47
    uint32_t hash = 5381;
2,318✔
48
    for (int i = 0; i < size; i++)
402,200✔
49
    {
50
        hash = ((hash << 5) + hash) + arr[i];
399,882✔
51
    }
52

53
    /* enable sign-conversion compiler warnings */
54
    PSLP_DIAG_POP();
55
    return hash;
2,318✔
56
}
57

58
#define INV_PRECISION 1e6
59

60
// djb2 hash function, with normalization of values and rounding of doubles
61
static inline uint32_t hash_double_array_with_scale(const double *arr, int size)
2,318✔
62
{
63
    // find max value of row
64
    double max = ABS(arr[0]);
2,318✔
65
    for (int i = 1; i < size; i++)
399,882✔
66
    {
67
        if (ABS(arr[i]) > max)
397,564✔
68
        {
69
            max = ABS(arr[i]);
9,988✔
70
        }
71
    }
72
    double scale = (arr[0] > 0) ? 1 / max : -1 / max;
2,318✔
73

74
    // compute has value
75
    uint32_t hash = 5381;
2,318✔
76
    for (int i = 0; i < size; i++)
402,200✔
77
    {
78
        uint32_t scaled = (uint32_t) (round((arr[i] * scale) * INV_PRECISION));
399,882✔
79
        hash = ((hash << 5) + hash) + scaled;
399,882✔
80
    }
81

82
    return hash;
2,318✔
83
}
84

85
#ifndef TESTING
86
static inline
87
#endif
88
    void compute_supp_and_coeff_hash(const Matrix *A, const RowTag *rowtags,
76✔
89
                                     int *sparsity_IDs, int *coeff_hashes,
90
                                     RowTag INACTIVE_TAG)
91
{
92

93
    for (int i = 0; i < A->m; i++)
2,491✔
94
    {
95
        if (HAS_TAG(rowtags[i], INACTIVE_TAG))
2,415✔
96
        {
97
            // important to set support_ID for inactive rows to place them
98
            // last in the sort
99
            sparsity_IDs[i] = INT_MAX;
97✔
100
            continue;
97✔
101
        }
102

103
        // this check makes sense since we also use this function to
104
        // find parallel columns
105
        assert(!HAS_TAG(rowtags[i], C_TAG_INACTIVE));
2,318✔
106

107
        int len = A->p[i].end - A->p[i].start;
2,318✔
108
        sparsity_IDs[i] = (int) hash_int_array(A->i + A->p[i].start, len);
2,318✔
109
        coeff_hashes[i] =
2,318✔
110
            (int) hash_double_array_with_scale(A->x + A->p[i].start, len);
2,318✔
111
    }
112
}
76✔
113

114
int comparator(const void *a, const void *b)
18,218✔
115
{
116
    int idxA = *(const int *) a;
18,218✔
117
    int idxB = *(const int *) b;
18,218✔
118

119
    if (global_sparsity_IDs[idxA] < global_sparsity_IDs[idxB])
18,218✔
120
    {
121
        return -1;
8,866✔
122
    }
123

124
    if (global_sparsity_IDs[idxA] > global_sparsity_IDs[idxB])
9,352✔
125
    {
126
        return 1;
9,042✔
127
    }
128

129
    if (global_coeff_hashes[idxA] < global_coeff_hashes[idxB])
310✔
130
    {
131
        return -1;
63✔
132
    }
133

134
    if (global_coeff_hashes[idxA] > global_coeff_hashes[idxB])
247✔
135
    {
136
        return 1;
51✔
137
    }
138

139
    return 0;
196✔
140
}
141

142
// Sort function
143
static inline void sort_rows(int *rows, size_t n_rows, int *sparsity_IDs,
74✔
144
                             int *coeff_hashes)
145
{
146
    global_sparsity_IDs = sparsity_IDs;
74✔
147
    global_coeff_hashes = coeff_hashes;
74✔
148
    qsort(rows, n_rows, sizeof(int), comparator);
74✔
149
}
74✔
150

151
static inline int get_bin_size(int start, size_t n_rows, const int *rows,
2,208✔
152
                               const int *sparsity_IDs, const int *coeff_hashes)
153
{
154
    int sparsity_ID = sparsity_IDs[rows[start]];
2,208✔
155
    int coeff_hash = coeff_hashes[rows[start]];
2,208✔
156
    int i;
157
    for (i = start + 1; i < n_rows; ++i)
2,307✔
158
    {
159
        if (sparsity_IDs[rows[i]] != sparsity_ID ||
2,279✔
160
            coeff_hashes[rows[i]] != coeff_hash)
158✔
161
        {
162
            break;
163
        }
164
    }
165
    return i - start;
2,208✔
166
}
167

168
#ifndef NDEBUG
169
// make sure that the rows in the same bin have the same coefficient
170
// hash value and the same sparsity ID
171
void ASSERT_BIN_CORRECT(const int *coeff_hashes, const int *sparsity_IDs,
38✔
172
                        const int *rows, int bin_start, int bin_size,
173
                        const RowTag *row_tags, RowTag INACTIVE_TAG)
174
{
175
    const int *bin_temp = rows + bin_start;
38✔
176
    int coeff_hash_start = coeff_hashes[bin_temp[0]];
38✔
177
    int sparsity_ID_start = sparsity_IDs[bin_temp[0]];
38✔
178

179
    for (int k = 1; k < bin_size; ++k)
137✔
180
    {
181
        assert(coeff_hashes[bin_temp[k]] == coeff_hash_start);
99✔
182
        assert(sparsity_IDs[bin_temp[k]] == sparsity_ID_start);
99✔
183
        assert(!HAS_TAG(row_tags[bin_temp[k]], INACTIVE_TAG));
99✔
184
    }
185
}
38✔
186

187
void VERIFY_PARALLEL_ROWS(const Matrix *A, const RowTag *rows_tags,
74✔
188
                          const int *parallel_rows, const iVec *group_starts)
189
{
190
    int j, k, start, end, n_rows_this_group, len1;
191
    size_t i;
192
    assert(group_starts->len > 0);
74✔
193
    // if (group_starts->len == 0)
194
    //{
195
    //     return;
196
    // }
197

198
    size_t n_groups = group_starts->len - 1;
74✔
199

200
    for (i = 0; i < n_groups; ++i)
112✔
201
    {
202
        start = group_starts->data[i];
38✔
203
        end = group_starts->data[i + 1];
38✔
204
        n_rows_this_group = end - start;
38✔
205

206
        // check that the group has at least two rows
207
        assert(n_rows_this_group > 1);
38✔
208

209
        // check that all rows in the group are active
210
        const int *temp = parallel_rows + start;
38✔
211
        ASSERT_NO_INACTIVE_ROWS(temp, rows_tags, n_rows_this_group);
175✔
212

213
        // check that all rows have the same support
214
        len1 = A->p[parallel_rows[start]].end - A->p[parallel_rows[start]].start;
38✔
215
        int *cols1 = A->i + A->p[parallel_rows[start]].start;
38✔
216
        for (j = start + 1; j < end; ++j)
137✔
217
        {
218
            assert(len1 ==
99✔
219
                   A->p[parallel_rows[j]].end - A->p[parallel_rows[j]].start);
220
            ARRAYS_EQUAL_INT(cols1, A->i + A->p[parallel_rows[j]].start, len1);
99✔
221
        }
222

223
        // check that all rows have same normalized coefficients
224
        double *vals1 = A->x + A->p[parallel_rows[start]].start;
38✔
225
        for (j = start + 1; j < end; ++j)
137✔
226
        {
227
            double *vals2 = A->x + A->p[parallel_rows[j]].start;
99✔
228
            double ratio = vals1[0] / vals2[0];
99✔
229
            for (k = 1; k < len1; ++k)
7,457✔
230
            {
231
                assert(IS_ZERO_FEAS_TOL(vals1[k] - ratio * vals2[k]));
7,358✔
232
            }
233
        }
234
    }
235
}
74✔
236
#endif
237

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

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

245
    * Returns the number of parallel rows found in the bin.
246
    * bin: pointer pointing to the FIRST row in the bin
247
    * bin_size: number of rows in the bin
248
    * parallel_rows: parallel rows are stored here, starting from
249
   parallel_rows[0]
250
*/
251
static inline int find_parallel_rows_in_bin(const Matrix *A, const int *bin,
38✔
252
                                            int bin_size, int *parallel_rows,
253
                                            iVec *group_starts)
254
{
255
    assert(bin_size > 1);
38✔
256
    int i, j, first_row_in_bin = bin[0];
38✔
257
    int n_new_parallel_rows = 0;
38✔
258

259
    const int *cols1 = A->i + A->p[bin[0]].start;
38✔
260
    const double *vals1 = A->x + A->p[bin[0]].start;
38✔
261
    int len1 = A->p[bin[0]].end - A->p[bin[0]].start;
38✔
262

263
    for (i = 1; i < bin_size; ++i)
137✔
264
    {
265
        const int *cols2 = A->i + A->p[bin[i]].start;
99✔
266
        const double *vals2 = A->x + A->p[bin[i]].start;
99✔
267
        int len2 = A->p[bin[i]].end - A->p[bin[i]].start;
99✔
268

269
        // check that the rows have same size
270
        if (len1 != len2)
99✔
271
        {
272
            continue;
×
273
        }
274

275
        // check that the rows have same support and coefficients up to multiple
276
        // (must start at j = 0 because of support check)
277
        double ratio = vals1[0] / vals2[0];
99✔
278
        for (j = 0; j < len2; ++j)
7,556✔
279
        {
280
            // if we run into numerical issues we might want to do
281
            // diff = vals1[j] / vals2[j] - ratio
282
            double diff = vals1[j] - ratio * vals2[j];
7,457✔
283

284
            if (!IS_ZERO_FEAS_TOL(diff) || cols1[j] != cols2[j])
7,457✔
285
            {
286
                break;
287
            }
288
        }
289

290
        // if j == len2, we have found a parallel row
291
        if (j == len2)
99✔
292
        {
293
            parallel_rows[n_new_parallel_rows] = bin[i];
99✔
294
            n_new_parallel_rows++;
99✔
295
        }
296
    }
297

298
    // add first row in bin
299
    if (n_new_parallel_rows > 0)
38✔
300
    {
301
        parallel_rows[n_new_parallel_rows] = first_row_in_bin;
38✔
302
        n_new_parallel_rows++;
38✔
303
        iVec_append(group_starts,
38✔
304
                    group_starts->data[group_starts->len - 1] + n_new_parallel_rows);
38✔
305
    }
306

307
    return n_new_parallel_rows;
38✔
308
}
309

310
// replaced rows with parallel_rows for less memory usage
311
void find_parallel_rows(const Matrix *A, const RowTag *r_Tags, iVec *group_starts,
74✔
312
                        int *parallel_rows, int *sparsity_IDs, int *coeff_hashes,
313
                        RowTag INACTIVE_TAG)
314
{
315
    int i, bin_size;
316
    int n_p_rows_total = 0;
74✔
317
    // ------------------------------------------------------------------------
318
    //             compute sparsity_IDs and coeff_hashes
319
    // ------------------------------------------------------------------------
320
    compute_supp_and_coeff_hash(A, r_Tags, sparsity_IDs, coeff_hashes, INACTIVE_TAG);
74✔
321

322
    // ------------------------------------------------------------------------
323
    // Make rows in the same bin appear next to each other. The sort makes sure
324
    // that rows with the same sparsity pattern and the same coefficient hash
325
    // value end up next to each other.
326
    // ------------------------------------------------------------------------
327
    for (i = 0; i < A->m; i++)
2,478✔
328
    {
329
        parallel_rows[i] = i;
2,404✔
330
    }
331

332
    sort_rows(parallel_rows, A->m, sparsity_IDs, coeff_hashes);
74✔
333

334
#ifndef NDEBUG
335
    if (INACTIVE_TAG == R_TAG_INACTIVE)
74✔
336
    {
337
        ASSERT_NO_ACTIVE_STON_ROWS(A, r_Tags);
52✔
338
    }
339
#endif
340

341
    // ------------------------------------------------------------------------
342
    // Start looping through the bins. Inactive rows have been placed in the
343
    // end of 'rows'. As soon as we meet an inactive row we can stop, since
344
    // all rows appearing after it are also inactive
345
    // ------------------------------------------------------------------------
346
    iVec_clear_no_resize(group_starts);
74✔
347
    iVec_append(group_starts, 0);
74✔
348
    for (i = 0; i < A->m; i++)
2,282✔
349
    {
350
        if (HAS_TAG(r_Tags[parallel_rows[i]], INACTIVE_TAG))
2,252✔
351
        {
352
            break;
44✔
353
        }
354

355
        bin_size = get_bin_size(i, A->m, parallel_rows, sparsity_IDs, coeff_hashes);
2,208✔
356

357
        // find the parallel rows in the bin
358
        if (bin_size > 1)
2,208✔
359
        {
360
            DEBUG(ASSERT_BIN_CORRECT(coeff_hashes, sparsity_IDs, parallel_rows, i,
38✔
361
                                     bin_size, r_Tags, INACTIVE_TAG););
362

363
            n_p_rows_total += find_parallel_rows_in_bin(
76✔
364
                A, parallel_rows + i, bin_size, parallel_rows + n_p_rows_total,
38✔
365
                group_starts);
366

367
            i += bin_size - 1;
38✔
368
        }
369
    }
370
    assert(n_p_rows_total == group_starts->data[group_starts->len - 1]);
74✔
371
    DEBUG(VERIFY_PARALLEL_ROWS(A, r_Tags, parallel_rows, group_starts););
74✔
372
}
74✔
373

374
static inline PresolveStatus process_single_bin(const Constraints *constraints,
12✔
375
                                                const int *bin, int bin_size)
376
{
377
    assert(bin_size > 1);
12✔
378
    const RowTag *row_tags = constraints->row_tags;
12✔
379
    const Matrix *A = constraints->A;
12✔
380
    const double *rhs = constraints->rhs;
12✔
381
    const double *lhs = constraints->lhs;
12✔
382
    double remaining_row_new_rhs, remaining_row_new_lhs, remaining_row_coeff;
383
    double other_row_coeff, ratio, other_row_rhs_scaled, other_row_lhs_scaled;
384
    double other_row_lhs_scale_factor = 1.0;
12✔
385
    double other_row_rhs_scale_factor = 1.0;
12✔
386
    int other_row_lhs_index = -1;
12✔
387
    int other_row_rhs_index = -1;
12✔
388
    bool is_rhs_inf_other_row, is_lhs_inf_other_row;
389

390
    // ------------------------------------------------------------------------
391
    //  Initialize quantities for the remaining row. These quantities will be
392
    //  updated as we process the bin.
393
    // ------------------------------------------------------------------------
394
    bool is_remaining_row_eq, is_rhs_inf_remaining_row, is_lhs_inf_remaining_row;
395
    int remaining_row_idx = bin[0];
12✔
396
    is_remaining_row_eq = HAS_TAG(row_tags[remaining_row_idx], R_TAG_EQ);
12✔
397
    is_rhs_inf_remaining_row = HAS_TAG(row_tags[remaining_row_idx], R_TAG_RHS_INF);
12✔
398
    is_lhs_inf_remaining_row = HAS_TAG(row_tags[remaining_row_idx], R_TAG_LHS_INF);
12✔
399
    remaining_row_new_rhs = rhs[remaining_row_idx];
12✔
400
    remaining_row_new_lhs = lhs[remaining_row_idx];
12✔
401
    remaining_row_coeff = A->x[A->p[remaining_row_idx].start];
12✔
402

403
    for (int i = 1; i < bin_size; ++i)
21✔
404
    {
405
        int other_row_idx = bin[i];
17✔
406
        bool is_other_row_eq = HAS_TAG(row_tags[other_row_idx], R_TAG_EQ);
17✔
407

408
        if (!is_remaining_row_eq && is_other_row_eq)
17✔
409
        {
410
            // swap(remaining_row, other_row)
411
            int temp = remaining_row_idx;
4✔
412
            remaining_row_idx = other_row_idx;
4✔
413
            other_row_idx = temp;
4✔
414

415
            is_remaining_row_eq = true;
4✔
416
            is_other_row_eq = false;
4✔
417
            remaining_row_new_rhs = rhs[remaining_row_idx];
4✔
418
            remaining_row_new_lhs = lhs[remaining_row_idx];
4✔
419
            remaining_row_coeff = A->x[A->p[remaining_row_idx].start];
4✔
420
        }
421

422
        assert(!(is_other_row_eq && !is_remaining_row_eq));
17✔
423

424
        other_row_coeff = A->x[A->p[other_row_idx].start];
17✔
425
        ratio = remaining_row_coeff / other_row_coeff;
17✔
426
        other_row_rhs_scaled = rhs[other_row_idx] * ratio;
17✔
427
        other_row_lhs_scaled = lhs[other_row_idx] * ratio;
17✔
428
        is_rhs_inf_other_row = HAS_TAG(row_tags[other_row_idx], R_TAG_RHS_INF);
17✔
429
        is_lhs_inf_other_row = HAS_TAG(row_tags[other_row_idx], R_TAG_LHS_INF);
17✔
430

431
        // -----------------------------------------------------------------------
432
        // if both rows are equalities we check that they are consistent
433
        // -----------------------------------------------------------------------
434
        if (is_remaining_row_eq && is_other_row_eq)
17✔
435
        {
436
            if (!IS_EQUAL_FEAS_TOL(remaining_row_new_rhs, other_row_rhs_scaled))
2✔
437
            {
438
                return INFEASIBLE;
×
439
            }
440
        }
441
        // -----------------------------------------------------------------------
442
        // if only the remaining row is an equality we check for infeasibility
443
        // -----------------------------------------------------------------------
444
        else if (is_remaining_row_eq)
15✔
445
        {
446
            bool infeas_rhs =
9✔
447
                !is_rhs_inf_other_row &&
14✔
448
                ((ratio > 0 && remaining_row_new_rhs > other_row_rhs_scaled) ||
5✔
449
                 (ratio < 0 && remaining_row_new_rhs < other_row_rhs_scaled));
2✔
450
            bool infeas_lhs =
9✔
451
                !is_lhs_inf_other_row &&
14✔
452
                ((ratio > 0 && remaining_row_new_rhs < other_row_lhs_scaled) ||
5✔
453
                 (ratio < 0 && remaining_row_new_rhs > other_row_lhs_scaled));
2✔
454

455
            if (infeas_rhs || infeas_lhs)
9✔
456
            {
457
                return INFEASIBLE;
8✔
458
            }
459
        }
460
        // ----------------------------------------------------------------------
461
        // if both rows are inequalities we find the tightest lhs and rhs
462
        // ----------------------------------------------------------------------
463
        else
464
        {
465
            assert(!is_remaining_row_eq && !is_other_row_eq);
6✔
466
            if (ratio > 0)
6✔
467
            {
468
                // possibly update rhs of remaining row
469
                if (!is_rhs_inf_other_row &&
2✔
470
                    (is_rhs_inf_remaining_row ||
1✔
471
                     other_row_rhs_scaled < remaining_row_new_rhs))
472
                {
473
                    other_row_rhs_index = other_row_idx;
×
474
                    other_row_rhs_scale_factor = ratio;
×
475
                    remaining_row_new_rhs = other_row_rhs_scaled;
×
476
                    is_rhs_inf_remaining_row = false;
×
477
                }
478

479
                // possibly update lhs of remaining row
480
                if (!is_lhs_inf_other_row &&
2✔
481
                    (is_lhs_inf_remaining_row ||
1✔
482
                     other_row_lhs_scaled > remaining_row_new_lhs))
483
                {
484
                    other_row_lhs_index = other_row_idx;
×
485
                    other_row_lhs_scale_factor = ratio;
×
486
                    remaining_row_new_lhs = other_row_lhs_scaled;
×
487
                    is_lhs_inf_remaining_row = false;
×
488
                }
489
            }
490
            else
491
            {
492
                // possibly update lhs of remaining row
493
                if (!is_rhs_inf_other_row &&
4✔
494
                    (is_lhs_inf_remaining_row ||
3✔
495
                     other_row_rhs_scaled > remaining_row_new_lhs))
496
                {
497
                    other_row_lhs_index = other_row_idx;
1✔
498
                    other_row_lhs_scale_factor = ratio;
1✔
499
                    remaining_row_new_lhs = other_row_rhs_scaled;
1✔
500
                    is_lhs_inf_remaining_row = false;
1✔
501
                }
502

503
                // possibly update rhs of remaining row
504
                if (!is_lhs_inf_other_row &&
4✔
505
                    (is_rhs_inf_remaining_row ||
1✔
506
                     other_row_lhs_scaled < remaining_row_new_rhs))
507
                {
508
                    other_row_rhs_index = other_row_idx;
2✔
509
                    other_row_rhs_scale_factor = ratio;
2✔
510
                    remaining_row_new_rhs = other_row_lhs_scaled;
2✔
511
                    is_rhs_inf_remaining_row = false;
2✔
512
                }
513
            }
514
        }
515
    }
516

517
    // ---------------------------------------------------------------------------------
518
    // if the remaining row is an inequality we may need to change the rhs and
519
    // lhs
520
    // ---------------------------------------------------------------------------------
521
    PostsolveInfo *postsolve_info = constraints->state->postsolve_info;
4✔
522
    if (!is_remaining_row_eq)
4✔
523
    {
524
        assert(!HAS_TAG(row_tags[remaining_row_idx], R_TAG_EQ));
3✔
525
        int start = A->p[remaining_row_idx].start;
3✔
526
        int len = A->p[remaining_row_idx].end - start;
3✔
527
        RowTag *row_tag = constraints->row_tags + remaining_row_idx;
3✔
528
        RowView remaining_row = new_rowview(A->x + start, A->i + start, &len, NULL,
3✔
529
                                            constraints->lhs + remaining_row_idx,
3✔
530
                                            constraints->rhs + remaining_row_idx,
3✔
531
                                            row_tag, remaining_row_idx);
532

533
        if (!is_rhs_inf_remaining_row &&
3✔
534
            remaining_row_new_rhs != rhs[remaining_row_idx])
2✔
535
        {
536
            assert(!IS_ABS_INF(remaining_row_new_rhs));
2✔
537
            assert(remaining_row_new_rhs < rhs[remaining_row_idx]);
2✔
538

539
            if (change_rhs_of_ineq(&remaining_row, remaining_row_new_rhs,
2✔
540
                                   constraints->state->col_locks,
2✔
541
                                   constraints->state->dton_rows, postsolve_info,
2✔
542
                                   other_row_rhs_index,
543
                                   other_row_rhs_scale_factor) == INFEASIBLE)
544
            {
545
                return INFEASIBLE;
×
546
            }
547
        }
548

549
        if (!is_lhs_inf_remaining_row &&
3✔
550
            remaining_row_new_lhs != lhs[remaining_row_idx])
3✔
551
        {
552
            assert(!IS_ABS_INF(remaining_row_new_lhs));
1✔
553
            assert(remaining_row_new_lhs > lhs[remaining_row_idx]);
1✔
554

555
            if (change_lhs_of_ineq(&remaining_row, remaining_row_new_lhs,
1✔
556
                                   constraints->state->col_locks,
1✔
557
                                   constraints->state->dton_rows, postsolve_info,
1✔
558
                                   other_row_lhs_index,
559
                                   other_row_lhs_scale_factor) == INFEASIBLE)
560
            {
561
                return INFEASIBLE;
×
562
            }
563
        }
564
    }
565

566
    // --------------------------------------------------------------------------------
567
    // When we arrive here we know which row should remain in the problem. We
568
    // mark all other rows as inactive.
569
    // --------------------------------------------------------------------------------
570
    for (int i = 0; i < bin_size; ++i)
17✔
571
    {
572
        if (bin[i] != remaining_row_idx)
13✔
573
        {
574
            set_row_to_inactive(bin[i], constraints->row_tags + bin[i],
9✔
575
                                constraints->state->rows_to_delete, postsolve_info,
9✔
576
                                0.0);
577
        }
578
    }
579

580
    return UNCHANGED;
4✔
581
}
582

583
// groups is a vector of size 'n_groups', where 'parallel_rows[i]' is the
584
// index of the first row in group 'i'
585
static PresolveStatus process_all_bins(const Constraints *constraints,
49✔
586
                                       const int *parallel_rows, const iVec *groups)
587
{
588
    if (groups->len == 0)
49✔
589
    {
NEW
590
        return UNCHANGED;
×
591
    }
592

593
    size_t n_groups = groups->len - 1;
49✔
594

595
    for (size_t i = 0; i < n_groups; ++i)
53✔
596
    {
597
        int n_rows_this_group = groups->data[i + 1] - groups->data[i];
12✔
598
        if (process_single_bin(constraints, parallel_rows + groups->data[i],
12✔
599
                               n_rows_this_group) == INFEASIBLE)
600
        {
601
            return INFEASIBLE;
8✔
602
        }
603
    }
604

605
    return UNCHANGED;
41✔
606
}
607

608
PresolveStatus remove_parallel_rows(Constraints *constraints)
49✔
609
{
610
    assert(constraints->state->ston_rows->len == 0);
49✔
611
    assert(constraints->state->empty_rows->len == 0);
49✔
612
    assert(constraints->state->empty_cols->len == 0);
49✔
613
    DEBUG(verify_problem_up_to_date(constraints));
49✔
614

615
    int *parallel_rows = constraints->state->work->iwork_n_rows;
49✔
616
    int *sparsity_IDs = constraints->state->work->iwork1_max_nrows_ncols;
49✔
617
    int *coeff_hashes = constraints->state->work->iwork2_max_nrows_ncols;
49✔
618
    iVec *group_starts = constraints->state->work->int_vec;
49✔
619

620
    find_parallel_rows(constraints->A, constraints->row_tags, group_starts,
49✔
621
                       parallel_rows, sparsity_IDs, coeff_hashes, R_TAG_INACTIVE);
622

623
    PresolveStatus status =
624
        process_all_bins(constraints, parallel_rows, group_starts);
49✔
625

626
    delete_inactive_rows(constraints);
49✔
627

628
    DEBUG(verify_problem_up_to_date(constraints));
49✔
629

630
    return status;
49✔
631
}
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