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

dance858 / PSLP / 20194334020

13 Dec 2025 03:49PM UTC coverage: 87.707% (-0.5%) from 88.244%
20194334020

Pull #22

github

web-flow
Merge 106c7e353 into 90ab253fc
Pull Request #22: Separate thread for transpose

1217 of 1383 branches covered (88.0%)

Branch coverage included in aggregate %.

26 of 28 new or added lines in 1 file covered. (92.86%)

23 existing lines in 1 file now uncovered.

3763 of 4295 relevant lines covered (87.61%)

3398.25 hits per line

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

78.22
/src/explorers/SimpleReductions.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 "SimpleReductions.h"
20
#include "Activity.h"
21
#include "Bounds.h"
22
#include "Constraints.h"
23
#include "CoreTransformations.h"
24
#include "Debugger.h"
25
#include "Locks.h"
26
#include "Matrix.h"
27
#include "Numerics.h"
28
#include "Postsolver.h"
29
#include "Problem.h"
30
#include "RowColViews.h"
31
#include "State.h"
32

33
void delete_fixed_cols_from_problem(Problem *prob)
360✔
34
{
35
    const Constraints *constraints = prob->constraints;
360✔
36
    const Matrix *AT = constraints->AT;
360✔
37
    const Bound *bounds = constraints->bounds;
360✔
38
    const RowTag *row_tags = constraints->row_tags;
360✔
39
    double *lhs = constraints->lhs;
360✔
40
    double *rhs = constraints->rhs;
360✔
41
    Activity *activities = constraints->state->activities;
360✔
42
    const iVec *fixed_cols_to_delete =
360✔
43
        prob->constraints->state->fixed_cols_to_delete;
360✔
44

45
    int i, j, col, len, row;
46
    int *rows;
47
    double *vals;
48

49
    for (i = 0; i < fixed_cols_to_delete->len; ++i)
389✔
50
    {
51
        col = fixed_cols_to_delete->data[i];
29✔
52
        assert(HAS_TAG(constraints->col_tags[col], C_TAG_FIXED));
29✔
53

54
        // if a variable has been fixed to INF it should not have been pushed to
55
        // fixed_cols_to_delete, so it shouldn't appear here
56
        assert(!IS_ABS_INF(bounds[col].ub));
29✔
57
        assert(bounds[col].lb == bounds[col].ub);
29✔
58

59
        // If the variable has been fixed to 0 or INF we must not do anything.
60
        // It is not necessary to update n_inf_min and n_inf_max (this has
61
        // already been done by boundChange).
62
        double ub = bounds[col].ub;
29✔
63
        if (ub == 0)
29✔
64
        {
65
            continue;
14✔
66
        }
67

68
        vals = AT->x + AT->p[col].start;
15✔
69
        rows = AT->i + AT->p[col].start;
15✔
70
        len = AT->p[col].end - AT->p[col].start;
15✔
71

72
        // ------------------------------------------------------------------------
73
        //           update left-and-right hand sides of constraints
74
        // ------------------------------------------------------------------------
75
        for (j = 0; j < len; ++j)
58✔
76
        {
77
            row = rows[j];
43✔
78

79
            // When a singleton row is used to fix a variable, the singleton row
80
            // will be inactive when trying to remove the contribution of the
81
            // fixed column. We want to skip it.
82
            if (HAS_TAG(row_tags[row], R_TAG_INACTIVE))
43✔
83
            {
84
                continue;
5✔
85
            }
86

87
            double contribution = vals[j] * ub;
38✔
88

89
            if (!HAS_TAG(row_tags[row], R_TAG_LHS_INF))
38✔
90
            {
91
                lhs[row] -= contribution;
24✔
92
            }
93

94
            if (!HAS_TAG(row_tags[row], R_TAG_RHS_INF))
38✔
95
            {
96
                rhs[row] -= contribution;
26✔
97
            }
98

99
            // not necessary to update n_inf_min and n_in_max since a fixed
100
            // variable has lb = ub = finite value so it doesn't contribute with
101
            // infinite bounds to the activity
102
            if (activities[row].n_inf_max == 0)
38✔
103
            {
104
                activities[row].max -= contribution;
24✔
105
            }
106

107
            if (activities[row].n_inf_min == 0)
38✔
108
            {
109
                activities[row].min -= contribution;
27✔
110
            }
111

112
            // Can a ranged row become an inequality due to numerics?
113
            assert(HAS_TAG(row_tags[row], R_TAG_LHS_INF) ||
38✔
114
                   HAS_TAG(row_tags[row], R_TAG_RHS_INF) ||
115
                   HAS_TAG(row_tags[row], R_TAG_EQ) ||
116
                   !IS_EQUAL_FEAS_TOL(lhs[row], rhs[row]));
117
        }
118

119
        // fix variable in the objective
120
        fix_var_in_obj(prob->obj, col, ub);
15✔
121
    }
122
}
360✔
123

124
static inline PresolveStatus remove_stonrow(Problem *prob, int row)
15✔
125
{
126
    // assume row 'row' is a singleton with variable k
127
    Constraints *constrs = prob->constraints;
15✔
128
    double lhs = constrs->lhs[row];
15✔
129
    double rhs = constrs->rhs[row];
15✔
130
    RowTag *row_tag = constrs->row_tags + row;
15✔
131
    RowRange *row_range = constrs->A->p + row;
15✔
132
    int k = constrs->A->i[row_range->start];
15✔
133
    double aik = constrs->A->x[row_range->start];
15✔
134
    ColTag col_tag = constrs->col_tags[k];
15✔
135
    int *row_size = constrs->state->row_sizes + row;
15✔
136
    PostsolveInfo *postsolve_info = constrs->state->postsolve_info;
15✔
137

138
    assert(!HAS_TAG(*row_tag, R_TAG_INACTIVE) &&
15✔
139
           !HAS_TAG(col_tag, C_TAG_SUBSTITUTED));
140

141
    // if the same variable appears in two singleton rows and the first one
142
    // that is processed is an equality row, then the variable has been fixed
143
    // and we should do nothing
144
    if (HAS_TAG(col_tag, C_TAG_FIXED))
15✔
145
    {
146
        return UNCHANGED;
2✔
147
    }
148

149
    assert(!HAS_TAG(col_tag, C_TAG_INACTIVE));
13✔
150

151
    // ----------------------------------------------------------------------
152
    //        if the row is an equality, we fix the variable
153
    // ----------------------------------------------------------------------
154
    if (HAS_TAG(*row_tag, R_TAG_EQ))
13✔
155
    {
156
        assert(lhs == rhs);
5✔
157

158
        // printf("fixing col %d to %f from row %d \n", k, rhs / aik, row);
159
        if (fix_col(constrs, k, rhs / aik, prob->obj->c[k]) == INFEASIBLE)
5✔
160
        {
161
            return INFEASIBLE;
×
162
        }
163

164
        // We manually mark the row inactive, since it is not necessary to
165
        // append it to the list of rows to delete. (It is not necessary
166
        // because the column has been appended to fixed_cols_to_delete.)
167
        *row_size = SIZE_INACTIVE_ROW;
5✔
168
        RESET_TAG(*row_tag, R_TAG_INACTIVE);
5✔
169
        row_range->end = row_range->start;
5✔
170
        constrs->A->nnz--;
5✔
171

172
        // dual postsolve:
173
        const Matrix *AT = constrs->AT;
5✔
174
        const int *rows = AT->i + AT->p[k].start;
5✔
175
        const double *vals = AT->x + AT->p[k].start;
5✔
176
        int len = AT->p[k].end - AT->p[k].start;
5✔
177
        save_retrieval_added_rows(postsolve_info, row, rows, vals, len, aik);
5✔
178
        save_retrieval_deleted_row(postsolve_info, row, prob->obj->c[k] / aik);
5✔
179
    }
180
    // ----------------------------------------------------------------------
181
    //  if the row is an inequality, we update the bounds (if they are tighter
182
    //  than the current bounds)
183
    // ----------------------------------------------------------------------
184
    else
185
    {
186
        bool rhs_inf = HAS_TAG(*row_tag, R_TAG_RHS_INF);
8✔
187
        bool lhs_inf = HAS_TAG(*row_tag, R_TAG_LHS_INF);
8✔
188

189
        if (aik > 0)
8✔
190
        {
191
            if ((!rhs_inf && (update_ub(constrs, k, rhs / aik,
8✔
192
                                        row HUGE_BOUND_IS_OK) == INFEASIBLE)) ||
8✔
193
                (!lhs_inf && (update_lb(constrs, k, lhs / aik,
8✔
194
                                        row HUGE_BOUND_IS_OK) == INFEASIBLE)))
195
            {
196
                return INFEASIBLE;
×
197
            }
198
        }
199
        else
200
        {
201
            if ((!rhs_inf && update_lb(constrs, k, rhs / aik,
×
202
                                       row HUGE_BOUND_IS_OK) == INFEASIBLE) ||
×
203
                (!lhs_inf && update_ub(constrs, k, lhs / aik,
×
204
                                       row HUGE_BOUND_IS_OK) == INFEASIBLE))
205
            {
206
                return INFEASIBLE;
×
207
            }
208
        }
209

210
        // must call set_row_to_inactive since the column is not appended to
211
        // fixed_cols_to_delete.
212
        set_row_to_inactive(row, row_tag, constrs->state->rows_to_delete,
8✔
213
                            postsolve_info, 0.0);
214
    }
215

216
    return REDUCED;
13✔
217
}
218

219
PresolveStatus remove_ston_rows(Problem *prob)
190✔
220
{
221
    iVec *ston_rows = prob->constraints->state->ston_rows;
190✔
222
    const int *ston_rows_data = ston_rows->data;
190✔
223
    int len = ston_rows->len;
190✔
224
    RowTag *row_tags = prob->constraints->row_tags;
190✔
225
    DEBUG(verify_no_duplicates_sort(ston_rows));
190✔
226

227
    if (len == 0)
190✔
228
    {
229
        return UNCHANGED;
179✔
230
    }
231

232
    // first we process singleton equality rows, then singleton inequalities
233
    // (this ordering simplifies the dual postsolve when the same variable
234
    // appears in an equality and an inequality row)
235
    for (int i = 0; i < len; ++i)
26✔
236
    {
237
        int row = ston_rows_data[i];
15✔
238

239
        if (!HAS_TAG(row_tags[row], R_TAG_EQ) ||
15✔
240
            HAS_TAG(row_tags[row], R_TAG_INACTIVE))
6✔
241
        {
242
            continue;
9✔
243
        }
244

245
        assert(!HAS_TAG(row_tags[row], R_TAG_INACTIVE));
6✔
246

247
        if (INFEASIBLE == remove_stonrow(prob, row))
6✔
248
        {
249
            assert(false);
×
250
            return INFEASIBLE;
251
        }
252
    }
253

254
    for (int i = 0; i < len; ++i)
26✔
255
    {
256
        int row = ston_rows_data[i];
15✔
257

258
        // eliminated equality rows have been marked as inactive
259
        // (or one might remain if the variable in it has been fixed
260
        // by another equality row)
261
        if (HAS_TAG(row_tags[row], (R_TAG_INACTIVE | R_TAG_EQ)))
15✔
262
        {
263
            continue;
6✔
264
        }
265

266
        assert(!HAS_TAG(row_tags[row], R_TAG_EQ));
9✔
267

268
        if (INFEASIBLE == remove_stonrow(prob, row))
9✔
269
        {
270
            assert(false);
×
271
            return INFEASIBLE;
272
        }
273
    }
274

275
    // we only updated A->nnz inside remove_stonrow so we need to
276
    // update AT->nnz for consistency
277
    prob->constraints->AT->nnz = prob->constraints->A->nnz;
11✔
278

279
    iVec_clear_no_resize(ston_rows);
11✔
280

281
    // singleton inequality rows have been marked as inactive so we
282
    // must remove them
283
    delete_inactive_rows(prob->constraints);
11✔
284

285
    delete_fixed_cols_from_problem(prob);
11✔
286

287
    // variables might have been fixed (by ston equality rows) so we
288
    // must remove them
289
    delete_inactive_cols_from_A_and_AT(prob->constraints);
11✔
290

291
    DEBUG(verify_problem_up_to_date(prob->constraints));
11✔
292
    return REDUCED;
11✔
293
}
294

295
// If the returned row tag is R_TAG_INFEAS, then the constraint is feasible.
296
// If HAS_TAG(return_tag, R_TAG_RHS_INF), then the rhs is finite and redundant.
297
// If HAS_TAG(return_tag, R_TAG_LHS_INF), then the lhs is finite and redundant.
298
static inline RowTag check_activity(const Activity *act, double lhs, double rhs,
43✔
299
                                    RowTag row_tag)
300
{
301
    assert(!HAS_TAG(row_tag, R_TAG_EQ));
43✔
302
    RowTag return_tag = R_TAG_NONE;
43✔
303

304
    // -------------------------------------------------------------------------
305
    // Compare the activity with the rhs of the constraint. We check for both
306
    // infeasibility and redundancy.
307
    // -------------------------------------------------------------------------
308
    if (!HAS_TAG(row_tag, R_TAG_RHS_INF))
43✔
309
    {
310
        if (act->n_inf_min == 0 && act->min >= rhs + FEAS_TOL)
35✔
311
        {
312
            return R_TAG_INFEAS;
1✔
313
        }
314
        else if (act->n_inf_max == 0 && act->max <= rhs + FEAS_TOL)
34✔
315
        {
316
            UPDATE_TAG(return_tag, R_TAG_RHS_INF);
8✔
317
        }
318
    }
319

320
    // -------------------------------------------------------------------------
321
    // Compare the activity with the lhs of the constraint. We check for both
322
    // infeasibility and redundancy.
323
    // -------------------------------------------------------------------------
324
    if (!HAS_TAG(row_tag, R_TAG_LHS_INF))
42✔
325
    {
326
        if (act->n_inf_max == 0 && act->max <= lhs - FEAS_TOL)
14✔
327
        {
328
            return R_TAG_INFEAS;
1✔
329
        }
330
        else if (act->n_inf_min == 0 && act->min >= lhs - FEAS_TOL)
13✔
331
        {
332
            UPDATE_TAG(return_tag, R_TAG_LHS_INF);
3✔
333
        }
334
    }
335

336
#ifndef NDEBUG
337
    // can it happen that an equality constraint becomes an inequality or
338
    // redundant?
339
    if (HAS_TAG(row_tag, R_TAG_EQ))
41✔
340
    {
341
        assert(!HAS_TAG(return_tag, R_TAG_LHS_INF) &&
×
342
               !HAS_TAG(return_tag, R_TAG_RHS_INF));
343
    }
344
#endif
345

346
    return return_tag;
41✔
347
}
348

349
PresolveStatus check_activities(Problem *prob)
19✔
350
{
351
    const iVec *acts_to_check = prob->constraints->state->updated_activities;
19✔
352

353
    // verify that the same row hasn't been appended twice and that the
354
    // activities are correct
355
    DEBUG(verify_no_duplicates_sort(acts_to_check));
19✔
356
    DEBUG(verify_activities(prob->constraints));
19✔
357

358
    const Activity *activities = prob->constraints->state->activities;
19✔
359
    const int *row_sizes = prob->constraints->state->row_sizes;
19✔
360
    Lock *locks = prob->constraints->state->col_locks;
19✔
361
    iVec *rows_to_delete = prob->constraints->state->rows_to_delete;
19✔
362
    RowTag *row_tags = prob->constraints->row_tags;
19✔
363
    const double *lhs = prob->constraints->lhs;
19✔
364
    const double *rhs = prob->constraints->rhs;
19✔
365
    const Matrix *A = prob->constraints->A;
19✔
366

367
    int i, j, row;
368

369
    for (i = 0; i < acts_to_check->len; ++i)
89✔
370
    {
371
        row = acts_to_check->data[i];
72✔
372

373
        // Skip inactive rows, empty rows, and singleton rows. We must include
374
        // the check for an inactive row, since the row size of an inactive
375
        // row may not yet have been updated when this function is called.
376
        // IDEA: it is a design choice to have skip equality rows here, but
377
        // we should try to remove this restriction and see if it matters.
378
        if (HAS_TAG(row_tags[row], (R_TAG_INACTIVE | R_TAG_EQ)) ||
72✔
379
            row_sizes[row] <= 1)
43✔
380
        {
381
            continue;
29✔
382
        }
383

384
        assert(activities[row].n_inf_min == 0 || activities[row].n_inf_max == 0);
43✔
385

386
        RowTag status_tag =
387
            check_activity(activities + row, lhs[row], rhs[row], row_tags[row]);
43✔
388

389
        // ----------------------------------------------------------------
390
        //              if the constraint is redundant
391
        // ----------------------------------------------------------------
392
        if (HAS_TAG((status_tag | row_tags[row]), R_TAG_LHS_INF) &&
43✔
393
            HAS_TAG((status_tag | row_tags[row]), R_TAG_RHS_INF))
32✔
394
        {
395
            set_row_to_inactive(row, row_tags + row, rows_to_delete,
8✔
396
                                prob->constraints->state->postsolve_info, 0.0);
8✔
397
        }
398
        // ----------------------------------------------------------------
399
        //              if lhs is finite but redundant
400
        // (it can't happen that both HAS_TAG(status_tag, R_TAG_LHS_INF)
401
        //  and HAS_TAG(row_tags[row], R_TAG_LHS_INF) are true because of
402
        //  the design of check_activity)
403
        // ----------------------------------------------------------------
404
        else if (HAS_TAG(status_tag, R_TAG_LHS_INF))
35✔
405
        {
406
            assert(!HAS_TAG(row_tags[row], R_TAG_LHS_INF));
1✔
407
            RESET_TAG(row_tags[row], R_TAG_LHS_INF);
1✔
408

409
            // update locks
410
            for (j = A->p[row].start; j < A->p[row].end; ++j)
4✔
411
            {
412
                assert(A->x[j] != 0);
3✔
413
                if (A->x[j] > 0)
3✔
414
                {
415
                    locks[A->i[j]].down--;
3✔
416
                }
417
                else
418
                {
419
                    locks[A->i[j]].up--;
×
420
                }
421
            }
422

423
            prob->constraints->lhs[row] = -INF;
1✔
424
        }
425
        // ----------------------------------------------------------------
426
        //             if rhs is finite but redundant
427
        // (it can't happen that both HAS_TAG(status_tag, R_TAG_RHS_INF)
428
        //  and HAS_TAG(row_tags[row], R_TAG_RHS_INF) are true because of
429
        //  the design of check_activity)
430
        // ----------------------------------------------------------------
431
        else if (HAS_TAG(status_tag, R_TAG_RHS_INF))
34✔
432
        {
433
            assert(!HAS_TAG(row_tags[row], R_TAG_RHS_INF));
1✔
434
            RESET_TAG(row_tags[row], R_TAG_RHS_INF);
1✔
435

436
            // update locks
437
            for (j = A->p[row].start; j < A->p[row].end; ++j)
4✔
438
            {
439
                assert(A->x[j] != 0);
3✔
440
                if (A->x[j] > 0)
3✔
441
                {
442
                    locks[A->i[j]].up--;
3✔
443
                }
444
                else
445
                {
446
                    locks[A->i[j]].down--;
×
447
                }
448
            }
449

450
            prob->constraints->rhs[row] = INF;
1✔
451
        }
452
        // ----------------------------------------------------------------
453
        //             if the constraint is infeasible
454
        // ----------------------------------------------------------------
455
        else if (HAS_TAG(status_tag, R_TAG_INFEAS))
33✔
456
        {
457
            return INFEASIBLE;
2✔
458
        }
459
    }
460

461
    delete_inactive_rows(prob->constraints);
17✔
462

463
    // we should NOT clear updated activities here since we need them in
464
    // primal propagation
465
    DEBUG(verify_problem_up_to_date(prob->constraints));
17✔
466
    return UNCHANGED;
17✔
467
}
468

469
PresolveStatus remove_empty_rows(const Constraints *constraints)
202✔
470
{
471
    iVec *empty_rows = constraints->state->empty_rows;
202✔
472
    const int *empty_rows_data = empty_rows->data;
202✔
473
    int len = empty_rows->len;
202✔
474

475
    if (len == 0)
202✔
476
    {
477
        return UNCHANGED;
192✔
478
    }
479

480
    int *row_sizes = constraints->state->row_sizes;
10✔
481
    RowTag *rtags = constraints->row_tags;
10✔
482
    const double *lhs = constraints->lhs;
10✔
483
    const double *rhs = constraints->rhs;
10✔
484
    PostsolveInfo *postsolve_info = constraints->state->postsolve_info;
10✔
485

486
    for (int i = 0; i < len; ++i)
20✔
487
    {
488
        int row = empty_rows_data[i];
10✔
489
        assert(row_sizes[row] == 0);
10✔
490

491
        bool infeasible =
10✔
492
            (!HAS_TAG(rtags[row], R_TAG_LHS_INF) && lhs[row] >= FEAS_TOL) ||
20✔
493
            (!HAS_TAG(rtags[row], R_TAG_RHS_INF) && rhs[row] <= -FEAS_TOL);
10✔
494

495
        if (infeasible)
10✔
496
        {
497
            return INFEASIBLE;
×
498
        }
499

500
        UPDATE_TAG(rtags[row], R_TAG_INACTIVE);
10✔
501
        row_sizes[row] = SIZE_INACTIVE_ROW;
10✔
502
        save_retrieval_deleted_row(postsolve_info, row, 0.0);
10✔
503
    }
504

505
    iVec_clear_no_resize(empty_rows);
10✔
506
    return UNCHANGED;
10✔
507
}
508

509
PresolveStatus remove_empty_cols(Problem *prob)
306✔
510
{
511
    iVec *empty_cols = prob->constraints->state->empty_cols;
306✔
512
    const int *empty_cols_data = empty_cols->data;
306✔
513
    int len = empty_cols->len;
306✔
514

515
    double val;
516
    int i, k;
517

518
    if (len == 0)
306✔
519
    {
520
        return UNCHANGED;
305✔
521
    }
522

523
    PostsolveInfo *postsolve_info = prob->constraints->state->postsolve_info;
1✔
524
    int *col_sizes = prob->constraints->state->col_sizes;
1✔
525
    ColTag *col_tags = prob->constraints->col_tags;
1✔
526
    Bound *bounds = prob->constraints->bounds;
1✔
527
    double *c = prob->obj->c;
1✔
528
    double *offset = &(prob->obj->offset);
1✔
529

530
    for (i = 0; i < len; ++i)
2✔
531
    {
532
        k = empty_cols_data[i];
1✔
533

534
        assert(col_sizes[k] == 0 && !HAS_TAG(col_tags[k], C_TAG_INACTIVE));
1✔
535

536
        // --------------------------------------------------------------------
537
        // if the objective coefficient is 0 we set the variable to 0 if
538
        // feasible, otherwise we set it equal to one of the bounds
539
        // --------------------------------------------------------------------
540
        if (c[k] == 0)
1✔
541
        {
542
            if (!HAS_TAG(col_tags[k], C_TAG_LB_INF) && bounds[k].lb > 0)
×
543
            {
544
                val = bounds[k].lb;
×
545
            }
546
            else if (!HAS_TAG(col_tags[k], C_TAG_UB_INF) && bounds[k].ub < 0)
×
547
            {
548
                val = bounds[k].ub;
×
549
            }
550
            else
551
            {
552
                val = 0;
×
553
            }
554
        }
555
        // ------------------------------------------------------------------
556
        //                  fix variable to upper bound
557
        // ------------------------------------------------------------------
558
        else if (c[k] < 0)
1✔
559
        {
560
            if (HAS_TAG(col_tags[k], C_TAG_UB_INF))
1✔
561
            {
562
                return UNBNDORINFEAS;
×
563
            }
564

565
            val = bounds[k].ub;
1✔
566
            bounds[k].lb = bounds[k].ub;
1✔
567
        }
568
        // ------------------------------------------------------------------
569
        //                   fix variable to lower bound
570
        // ------------------------------------------------------------------
571
        else
572
        {
573
            assert(c[k] > 0);
×
574
            if (HAS_TAG(col_tags[k], C_TAG_LB_INF))
×
575
            {
576
                return UNBNDORINFEAS;
×
577
            }
578

579
            val = bounds[k].lb;
×
580
            bounds[k].ub = bounds[k].lb;
×
581
        }
582

583
        // add offset to objective
584
        *offset += c[k] * val;
1✔
585

586
        // mark column as fixed
587
        UPDATE_TAG(col_tags[k], C_TAG_FIXED);
1✔
588
        col_sizes[k] = SIZE_INACTIVE_COL;
1✔
589

590
        // store postsolve information (for empty cols we want zk = ck)
591
        // passing NULL ptrs are safe since the length is 0
592
        save_retrieval_fixed_col(postsolve_info, k, val, prob->obj->c[k], NULL, NULL,
1✔
593
                                 0);
594
    }
595

596
    iVec_clear_no_resize(empty_cols);
1✔
597
    return UNCHANGED;
1✔
598
}
599

UNCOV
600
void clean_small_coeff_A(Matrix *A, const Bound *bounds, const RowTag *row_tags,
×
601
                         const ColTag *col_tags, double *rhs, double *lhs)
602
{
603
    int ii, row, col, len, n_rows, *cols;
604
    double Aik_abs, cum_changes, diff, *vals;
605
    bool has_finite_bounds, set_coeff_to_zero;
UNCOV
606
    n_rows = A->m;
×
607

UNCOV
608
    for (row = 0; row < n_rows; ++row)
×
609
    {
UNCOV
610
        cols = A->i + A->p[row].start;
×
UNCOV
611
        vals = A->x + A->p[row].start;
×
UNCOV
612
        len = A->p[row].end - A->p[row].start;
×
UNCOV
613
        cum_changes = 0.0;
×
614

UNCOV
615
        for (ii = 0; ii < len; ++ii)
×
616
        {
UNCOV
617
            set_coeff_to_zero = false;
×
UNCOV
618
            col = cols[ii];
×
UNCOV
619
            Aik_abs = ABS(vals[ii]);
×
620

UNCOV
621
            has_finite_bounds =
×
UNCOV
622
                !HAS_TAG(col_tags[col], (C_TAG_LB_INF | C_TAG_UB_INF));
×
623

UNCOV
624
            if (has_finite_bounds)
×
625
            {
UNCOV
626
                assert(!IS_ABS_INF(bounds[col].lb) && !IS_ABS_INF(bounds[col].ub));
×
UNCOV
627
                diff = bounds[col].ub - bounds[col].lb;
×
UNCOV
628
                assert(diff >= 0);
×
629

630
                // we take care of fixed variables later
UNCOV
631
                if (diff == 0)
×
632
                {
633
                    continue;
×
634
                }
635

UNCOV
636
                if (Aik_abs < CLEAN1 && Aik_abs * diff * len < 1e-2 * FEAS_TOL)
×
637
                {
638
                    set_coeff_to_zero = true;
×
639
                }
UNCOV
640
                else if (cum_changes + Aik_abs * diff < 1e-1 * FEAS_TOL)
×
641
                {
642
                    cum_changes += Aik_abs * diff;
×
643
                    set_coeff_to_zero = true;
×
644
                }
645
            }
646

UNCOV
647
            if (set_coeff_to_zero)
×
648
            {
649
                if (!HAS_TAG(row_tags[row], R_TAG_LHS_INF))
×
650
                {
651
                    assert(bounds[col].lb != -INF);
×
652
                    lhs[row] -= vals[ii] * bounds[col].lb;
×
653
                }
654

655
                if (!HAS_TAG(row_tags[row], R_TAG_RHS_INF))
×
656
                {
657
                    assert(bounds[col].lb != -INF);
×
658
                    rhs[row] -= vals[ii] * bounds[col].lb;
×
659
                }
660
            }
661

UNCOV
662
            if (Aik_abs < CLEAN2 || set_coeff_to_zero)
×
663
            {
664
                RowView row_view =
665
                    new_rowview(vals, cols, &len, A->p + row, NULL, NULL, NULL, -1);
×
666
                remove_coeff(&row_view, col);
×
667
                ii--;
×
668
                A->nnz--;
×
669
            }
670
        }
671
    }
UNCOV
672
}
×
673

674
void remove_variables_with_close_bounds(Problem *prob)
179✔
675
{
676
    Constraints *constraints = prob->constraints;
179✔
677
    const double *c = prob->obj->c;
179✔
678
    const Bound *bounds = constraints->bounds;
179✔
679
    const ColTag *col_tags = constraints->col_tags;
179✔
680
    int n_cols = constraints->n;
179✔
681
    const int *col_sizes = constraints->state->col_sizes;
179✔
682

683
    for (int ii = 0; ii < n_cols; ++ii)
1,645✔
684
    {
685
        if (col_sizes[ii] <= 0 ||
1,466✔
686
            HAS_TAG(col_tags[ii], (C_TAG_LB_INF | C_TAG_UB_INF)))
1,207✔
687
        {
688
            continue;
668✔
689
        }
690

691
        assert(!HAS_TAG(col_tags[ii], C_TAG_INACTIVE));
798✔
692

693
        if (IS_EQUAL_FEAS_TOL(bounds[ii].lb, bounds[ii].ub))
798✔
694
        {
695
            // no need to check return value since bounds are equal
696
            fix_col(constraints, ii, bounds[ii].lb, c[ii]);
×
697
        }
698
    }
699

700
    // remove fixed columns
701
    delete_fixed_cols_from_problem(prob);
179✔
702
    delete_inactive_cols_from_A_and_AT(constraints);
179✔
703
}
179✔
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