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

dance858 / PSLP / 22280181486

22 Feb 2026 03:43PM UTC coverage: 89.598% (+0.6%) from 89.013%
22280181486

Pull #38

github

web-flow
Merge 05aa0f1b8 into e13878911
Pull Request #38: [WIP] Speedup

1267 of 1412 branches covered (89.73%)

Branch coverage included in aggregate %.

186 of 198 new or added lines in 11 files covered. (93.94%)

39 existing lines in 5 files now uncovered.

3953 of 4414 relevant lines covered (89.56%)

6170.57 hits per line

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

90.86
/src/explorers/Parallel_cols.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_cols.h"
20
#include "Activity.h"
21
#include "Bounds.h"
22
#include "Constraints.h"
23
#include "CoreTransformations.h"
24
#include "Debugger.h"
25
#include "Matrix.h"
26
#include "Numerics.h"
27
#include "Parallel_rows.h"
28
#include "Problem.h"
29
#include "RowColViews.h"
30
#include "SimpleReductions.h"
31
#include "State.h"
32
#include "Tags.h"
33
#include "Workspace.h"
34

35
static inline PresolveStatus process_single_bin(const Problem *prob, const int *bin,
18✔
36
                                                int bin_size)
37
{
38
    assert(bin_size > 1);
18✔
39
    Constraints *constraints = prob->constraints;
18✔
40
    ColTag *col_tags = constraints->col_tags;
18✔
41
    Bound *bounds = constraints->bounds;
18✔
42
    const Matrix *AT = constraints->AT;
18✔
43
    const RowRange *col_ranges = AT->p;
18✔
44
    const double *c = prob->obj->c;
18✔
45
    Activity *acts = constraints->state->activities;
18✔
46
    iVec *sub_cols_to_delete = constraints->state->sub_cols_to_delete;
18✔
47
    PostsolveInfo *postsolve_info = constraints->state->postsolve_info;
18✔
48

49
    bool recount_ninfs = false;
18✔
50

51
    // column j stays, column k goes
52
    int ii, jj, j, k, len_j;
53
    const int *aj_cols;
54
    double ratio, cj, ck, ak0, lb_j_old, ub_j_old;
55
    const double *aj_vals;
56

57
    for (ii = 0; ii < bin_size - 1; ++ii)
56✔
58
    {
59
        j = bin[ii];
40✔
60

61
        if (HAS_TAG(col_tags[j], C_TAG_INACTIVE))
40✔
62
        {
63
            continue;
15✔
64
        }
65

66
        cj = c[j];
25✔
67
        aj_vals = AT->x + col_ranges[j].start;
25✔
68
        aj_cols = AT->i + col_ranges[j].start;
25✔
69
        len_j = col_ranges[j].end - col_ranges[j].start;
25✔
70
        recount_ninfs = false;
25✔
71

72
        for (jj = ii + 1; jj < bin_size; ++jj)
79✔
73
        {
74
            k = bin[jj];
56✔
75

76
            if (HAS_TAG(col_tags[k], C_TAG_INACTIVE))
56✔
77
            {
78
                continue;
5✔
79
            }
80

81
            ck = c[k];
51✔
82
            ak0 = AT->x[col_ranges[k].start];
51✔
83
            ratio = ak0 / aj_vals[0];
51✔
84
            assert(ratio != 0);
51✔
85

86
            // if column j and column k are parallel, remove column k
87
            // if you run into issues with parallel cols activated, try to
88
            // change this
89
            if (IS_EQUAL_FEAS_TOL(ck * aj_vals[0], cj * ak0))
51✔
90
            {
91
                // mark that we must recount n_infs for rows in which column j
92
                // appears in
93
                recount_ninfs = true;
18✔
94

95
                // ---------------------------------------------------------------------
96
                //                    Update the bounds of column j
97
                // ---------------------------------------------------------------------
98
                lb_j_old = bounds[j].lb;
18✔
99
                ub_j_old = bounds[j].ub;
18✔
100
                if (ratio > 0)
18✔
101
                {
102
                    // update lb
103
                    if (HAS_TAG((col_tags[j] | col_tags[k]), C_TAG_LB_INF))
11✔
104
                    {
105
                        bounds[j].lb = -INF;
2✔
106
                        UPDATE_TAG(col_tags[j], C_TAG_LB_INF);
2✔
107
                    }
108
                    else
109
                    {
110
                        assert(!IS_ABS_INF(bounds[j].lb) &&
9✔
111
                               !IS_ABS_INF(bounds[k].lb));
112
                        bounds[j].lb += bounds[k].lb * ratio;
9✔
113
                    }
114

115
                    // update ub
116
                    if (HAS_TAG((col_tags[j] | col_tags[k]), C_TAG_UB_INF))
11✔
117
                    {
118
                        bounds[j].ub = INF;
2✔
119
                        UPDATE_TAG(col_tags[j], C_TAG_UB_INF);
2✔
120
                    }
121
                    else
122
                    {
123
                        assert(!IS_ABS_INF(bounds[j].ub) &&
9✔
124
                               !IS_ABS_INF(bounds[k].ub));
125
                        bounds[j].ub += bounds[k].ub * ratio;
9✔
126
                    }
127
                }
128
                else
129
                {
130
                    // update lb
131
                    if (HAS_TAG(col_tags[j], C_TAG_LB_INF) ||
7✔
132
                        HAS_TAG(col_tags[k], C_TAG_UB_INF))
6✔
133
                    {
134
                        bounds[j].lb = -INF;
1✔
135
                        UPDATE_TAG(col_tags[j], C_TAG_LB_INF);
1✔
136
                    }
137
                    else
138
                    {
139
                        assert(!IS_ABS_INF(bounds[j].lb) &&
6✔
140
                               !IS_ABS_INF(bounds[k].ub));
141
                        bounds[j].lb += bounds[k].ub * ratio;
6✔
142
                    }
143

144
                    // update ub
145
                    if (HAS_TAG(col_tags[j], C_TAG_UB_INF) ||
7✔
146
                        HAS_TAG(col_tags[k], C_TAG_LB_INF))
7✔
147
                    {
148
                        bounds[j].ub = INF;
2✔
149
                        UPDATE_TAG(col_tags[j], C_TAG_UB_INF);
2✔
150
                    }
151
                    else
152
                    {
153
                        assert(!IS_ABS_INF(bounds[j].ub) &&
5✔
154
                               !IS_ABS_INF(bounds[k].lb));
155
                        bounds[j].ub += bounds[k].lb * ratio;
5✔
156
                    }
157
                }
158

159
                // ---------------------------------------------------------------------
160
                //                  mark col as substituted
161
                // ---------------------------------------------------------------------
162
                set_col_to_substituted(k, col_tags + k, sub_cols_to_delete);
18✔
163
                save_retrieval_parallel_col(postsolve_info, ub_j_old, lb_j_old,
18✔
164
                                            bounds[k].lb, bounds[k].ub, ratio, j, k,
18✔
165
                                            col_tags[j], col_tags[k]);
18✔
166
            }
167
            else
168
            {
169
                // we could add an implied check here but it doesn't seem to
170
                // make a difference on miplib. If you do it, don't forget that
171
                // the implied free check implicitly corresponds to a bound
172
                // change that must be dual postsolved
173

174
                bool fix_xk_to_lower = false;
33✔
175
                bool fix_xk_to_upper = false;
33✔
176
                bool fix_xj_to_upper = false;
33✔
177
                bool fix_xj_to_lower = false;
33✔
178

179
                assert(!HAS_TAG(col_tags[k], C_TAG_INACTIVE));
33✔
180
                assert(!HAS_TAG(col_tags[j], C_TAG_INACTIVE));
33✔
181

182
                if (ck > ratio * cj)
33✔
183
                {
184
                    if (ratio > 0)
19✔
185
                    {
186
                        fix_xk_to_lower = HAS_TAG(col_tags[j], C_TAG_UB_INF);
11✔
187
                        fix_xj_to_upper = HAS_TAG(col_tags[k], C_TAG_LB_INF);
11✔
188
                    }
189
                    else
190
                    {
191
                        fix_xk_to_lower = HAS_TAG(col_tags[j], C_TAG_LB_INF);
8✔
192
                        fix_xj_to_lower = HAS_TAG(col_tags[k], C_TAG_LB_INF);
8✔
193
                    }
194
                }
195
                else
196
                {
197
                    assert(ck < ratio * cj);
14✔
198
                    if (ratio > 0)
14✔
199
                    {
200
                        fix_xk_to_upper = HAS_TAG(col_tags[j], C_TAG_LB_INF);
10✔
201
                        fix_xj_to_lower = HAS_TAG(col_tags[k], C_TAG_UB_INF);
10✔
202
                    }
203
                    else
204
                    {
205
                        fix_xk_to_upper = HAS_TAG(col_tags[j], C_TAG_UB_INF);
4✔
206
                        fix_xj_to_upper = HAS_TAG(col_tags[k], C_TAG_UB_INF);
4✔
207
                    }
208
                }
209

210
                // check if we can fix xk
211
                if (fix_xk_to_lower)
33✔
212
                {
213
                    if (HAS_TAG(col_tags[k], C_TAG_LB_INF))
3✔
214
                    {
215
                        return UNBNDORINFEAS;
1✔
216
                    }
217

218
                    assert(!HAS_TAG(col_tags[k], C_TAG_INACTIVE));
2✔
219
                    assert(!IS_ABS_INF(bounds[k].lb));
2✔
220
                    fix_col(constraints, k, bounds[k].lb, ck);
2✔
221
                    continue;
2✔
222
                }
223
                else if (fix_xk_to_upper)
30✔
224
                {
225
                    if (HAS_TAG(col_tags[k], C_TAG_UB_INF))
3✔
226
                    {
227
                        return UNBNDORINFEAS;
1✔
228
                    }
229

230
                    assert(!HAS_TAG(col_tags[k], C_TAG_INACTIVE));
2✔
231
                    assert(!IS_ABS_INF(bounds[k].ub));
2✔
232
                    fix_col(constraints, k, bounds[k].ub, ck);
2✔
233
                    continue;
2✔
234
                }
235

236
                // check if we can fix xj
237
                if (fix_xj_to_lower)
27✔
238
                {
UNCOV
239
                    if (HAS_TAG(col_tags[j], C_TAG_LB_INF))
×
240
                    {
UNCOV
241
                        return UNBNDORINFEAS;
×
242
                    }
243

244
                    assert(!HAS_TAG(col_tags[k], C_TAG_INACTIVE));
×
245
                    assert(!IS_ABS_INF(bounds[j].lb));
×
246
                    fix_col(constraints, j, bounds[j].lb, cj);
×
247
                    // break from checking if xj is parallel to other cols since
248
                    // we fix it
UNCOV
249
                    break;
×
250
                }
251
                else if (fix_xj_to_upper)
27✔
252
                {
253
                    if (HAS_TAG(col_tags[j], C_TAG_UB_INF))
×
254
                    {
255
                        return UNBNDORINFEAS;
×
256
                    }
257

258
                    assert(!HAS_TAG(col_tags[k], C_TAG_INACTIVE));
×
259
                    assert(!IS_ABS_INF(bounds[j].ub));
×
260
                    fix_col(constraints, j, bounds[j].ub, cj);
×
261
                    // break from checking if xj is parallel to other cols since
262
                    // we fix it
263
                    break;
×
264
                }
265
            }
266
        }
267

268
        // we might have to recount n_inf_min and n_inf_max for the rows column
269
        // j appears in due to bound change
270
        if (recount_ninfs)
23✔
271
        {
272
            const Matrix *A = constraints->A;
14✔
273

274
            for (jj = 0; jj < len_j; jj++)
52✔
275
            {
276
                int row = aj_cols[jj];
38✔
277

278
                recompute_n_infs(acts + row, A->x + A->p[row].start,
38✔
279
                                 A->i + A->p[row].start,
38✔
280
                                 A->p[row].end - A->p[row].start, col_tags);
38✔
281
            }
282
        }
283
    }
284

285
    return UNCHANGED;
16✔
286
}
287

288
static PresolveStatus process_all_bins(const Problem *prob, const int *parallel_cols,
20✔
289
                                       const iVec *groups)
290
{
291
    if (groups->len <= 1)
20✔
292
    {
293
        return UNCHANGED;
4✔
294
    }
295

296
    size_t n_groups = groups->len - 1;
16✔
297
    PresolveStatus status = UNCHANGED;
16✔
298

299
    for (size_t i = 0; i < n_groups; ++i)
34✔
300
    {
301
        int n_cols_this_group = groups->data[i + 1] - groups->data[i];
18✔
302
        status |= process_single_bin(prob, parallel_cols + groups->data[i],
18✔
303
                                     n_cols_this_group);
304
    }
305

306
    return status;
16✔
307
}
308

309
PresolveStatus remove_parallel_cols(Problem *prob)
20✔
310
{
311
    assert(prob->constraints->state->ston_rows->len == 0);
20✔
312
    assert(prob->constraints->state->empty_rows->len == 0);
20✔
313
    assert(prob->constraints->state->empty_cols->len == 0);
20✔
314
    DEBUG(verify_problem_up_to_date(prob->constraints));
20✔
315

316
    Constraints *constraints = prob->constraints;
20✔
317
    int *parallel_cols = constraints->state->work->iwork_n_cols;
20✔
318
    int *sparsity_IDs = constraints->state->work->iwork1_max_nrows_ncols;
20✔
319
    int *coeff_hashes = constraints->state->work->iwork2_max_nrows_ncols;
20✔
320
    iVec *group_starts = constraints->state->work->int_vec;
20✔
321

322
    // finding parallel rows of AT is the same as finding parallel cols of A
323
    find_parallel_rows(constraints->AT, constraints->col_tags, group_starts,
20✔
324
                       parallel_cols, sparsity_IDs, coeff_hashes, C_TAG_INACTIVE,
325
                       constraints->state->work->radix_aux);
20✔
326
#ifndef NDEBUG
327
    // there should be no inactive cols in group_starts here
328
    for (int i = 0; i < group_starts->len - 1; ++i)
38✔
329
    {
330
        for (int j = group_starts->data[i]; j < group_starts->data[i + 1]; ++j)
78✔
331
        {
332
            assert(
60✔
333
                !HAS_TAG(constraints->col_tags[parallel_cols[j]], C_TAG_INACTIVE));
334
            assert(constraints->state->col_sizes[parallel_cols[j]] !=
60✔
335
                   SIZE_INACTIVE_COL);
336
        }
337
    }
338
#endif
339

340
    PresolveStatus status = process_all_bins(prob, parallel_cols, group_starts);
20✔
341
    assert(constraints->state->empty_rows->len == 0);
20✔
342

343
    delete_fixed_cols_from_problem(prob);
20✔
344
    delete_inactive_cols_from_A_and_AT(prob->constraints);
20✔
345

346
    DEBUG(verify_problem_up_to_date(constraints));
20✔
347

348
    return status;
20✔
349
}
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