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

dance858 / PSLP / 21378516514

26 Jan 2026 11:48PM UTC coverage: 84.909% (-4.1%) from 88.988%
21378516514

Pull #33

github

web-flow
Merge 8826d2bcc into 8137ce2f7
Pull Request #33: Windows build

1191 of 1401 branches covered (85.01%)

Branch coverage included in aggregate %.

144 of 153 new or added lines in 19 files covered. (94.12%)

201 existing lines in 11 files now uncovered.

3687 of 4344 relevant lines covered (84.88%)

130.29 hits per line

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

78.13
/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)
331✔
34
{
35
    const Constraints *constraints = prob->constraints;
331✔
36
    const Matrix *AT = constraints->AT;
331✔
37
    const Bound *bounds = constraints->bounds;
331✔
38
    const RowTag *row_tags = constraints->row_tags;
331✔
39
    double *lhs = constraints->lhs;
331✔
40
    double *rhs = constraints->rhs;
331✔
41
    Activity *activities = constraints->state->activities;
331✔
42
    const iVec *fixed_cols_to_delete =
331✔
43
        prob->constraints->state->fixed_cols_to_delete;
331✔
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)
358✔
50
    {
51
        col = fixed_cols_to_delete->data[i];
27✔
52
        assert(HAS_TAG(constraints->col_tags[col], C_TAG_FIXED));
27✔
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));
27✔
57
        assert(bounds[col].lb == bounds[col].ub);
27✔
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;
27✔
63
        if (ub == 0)
27✔
64
        {
65
            continue;
13✔
66
        }
67

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

72
        // ------------------------------------------------------------------------
73
        //           update left-and-right hand sides of constraints
74
        // ------------------------------------------------------------------------
75
        for (j = 0; j < len; ++j)
56✔
76
        {
77
            row = rows[j];
42✔
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))
42✔
83
            {
84
                continue;
5✔
85
            }
86

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

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

94
            if (!HAS_TAG(row_tags[row], R_TAG_RHS_INF))
37✔
95
            {
96
                rhs[row] -= contribution;
25✔
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)
37✔
103
            {
104
                activities[row].max -= contribution;
24✔
105
            }
106

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

112
            // Can a ranged row become an inequality due to numerics?
113
            assert(HAS_TAG(row_tags[row], R_TAG_LHS_INF) ||
37✔
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);
14✔
121
    }
122
}
331✔
123

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

138
    assert(!HAS_TAG(*row_tag, R_TAG_INACTIVE) &&
13✔
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))
13✔
145
    {
146
        return UNCHANGED;
2✔
147
    }
148

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

151
    // ----------------------------------------------------------------------
152
    //        if the row is an equality, we fix the variable
153
    // ----------------------------------------------------------------------
154
    if (HAS_TAG(*row_tag, R_TAG_EQ))
11✔
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);
6✔
187
        bool lhs_inf = HAS_TAG(*row_tag, R_TAG_LHS_INF);
6✔
188

189
        if (aik > 0)
6✔
190
        {
191
            if ((!rhs_inf && (update_ub(constrs, k, rhs / aik,
6✔
192
                                        row HUGE_BOUND_IS_OK) == INFEASIBLE)) ||
6✔
193
                (!lhs_inf && (update_lb(constrs, k, lhs / aik,
6✔
194
                                        row HUGE_BOUND_IS_OK) == INFEASIBLE)))
195
            {
UNCOV
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,
6✔
213
                            postsolve_info, 0.0);
214
    }
215

216
    return REDUCED;
11✔
217
}
218

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

227
    if (len == 0)
182✔
228
    {
229
        return UNCHANGED;
172✔
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)
23✔
236
    {
237
        int row = ston_rows_data[i];
13✔
238

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

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

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

253
    for (int i = 0; i < len; ++i)
23✔
254
    {
255
        int row = ston_rows_data[i];
13✔
256

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

265
        assert(!HAS_TAG(row_tags[row], R_TAG_EQ));
7✔
266

267
        if (INFEASIBLE == remove_stonrow(prob, row))
7✔
268
        {
UNCOV
269
            return INFEASIBLE;
×
270
        }
271
    }
272

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

277
    iVec_clear_no_resize(ston_rows);
10✔
278

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

283
    delete_fixed_cols_from_problem(prob);
10✔
284

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

289
    DEBUG(verify_problem_up_to_date(prob->constraints));
10✔
290
    return REDUCED;
10✔
291
}
292

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

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

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

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

344
    return return_tag;
22✔
345
}
346

347
PresolveStatus check_activities(Problem *prob)
17✔
348
{
349
    const iVec *acts_to_check = prob->constraints->state->updated_activities;
17✔
350

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

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

365
    int i, j, row;
366

367
    for (i = 0; i < acts_to_check->len; ++i)
57✔
368
    {
369
        row = acts_to_check->data[i];
42✔
370

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

382
        assert(activities[row].n_inf_min == 0 || activities[row].n_inf_max == 0);
24✔
383

384
        RowTag status_tag =
385
            check_activity(activities + row, lhs[row], rhs[row], row_tags[row]);
24✔
386

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

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

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

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

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

459
    delete_inactive_rows(prob->constraints);
15✔
460

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

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

473
    if (len == 0)
193✔
474
    {
475
        return UNCHANGED;
183✔
476
    }
477

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

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

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

493
        if (infeasible)
10✔
494
        {
495
            return INFEASIBLE;
×
496
        }
497

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

503
    iVec_clear_no_resize(empty_rows);
10✔
504
    return UNCHANGED;
10✔
505
}
506

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

513
    double val;
514
    int i, k;
515

516
    if (len == 0)
292✔
517
    {
518
        return UNCHANGED;
291✔
519
    }
520

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

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

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

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

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

UNCOV
577
            val = bounds[k].lb;
×
UNCOV
578
            bounds[k].ub = bounds[k].lb;
×
579
        }
580

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

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

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

594
    iVec_clear_no_resize(empty_cols);
1✔
595
    return UNCHANGED;
1✔
596
}
597

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

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

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

619
            has_finite_bounds =
×
620
                !HAS_TAG(col_tags[col], (C_TAG_LB_INF | C_TAG_UB_INF));
×
621

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

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

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

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

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

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

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

681
    for (int ii = 0; ii < n_cols; ++ii)
1,182✔
682
    {
683
        if (col_sizes[ii] < 0 ||
1,010✔
684
            HAS_TAG(col_tags[ii], (C_TAG_LB_INF | C_TAG_UB_INF)))
785✔
685
        {
686
            continue;
455✔
687
        }
688

689
        assert(!HAS_TAG(col_tags[ii], C_TAG_INACTIVE));
555✔
690

691
        if (IS_EQUAL_FEAS_TOL(bounds[ii].lb, bounds[ii].ub))
555✔
692
        {
693
            // no need to check return value since bounds are equal
694
            fix_col(constraints, ii, bounds[ii].lb, c[ii]);
×
695
        }
696
        else if (bounds[ii].lb > bounds[ii].ub + FEAS_TOL)
555✔
697
        {
UNCOV
698
            return INFEASIBLE;
×
699
        }
700
    }
701

702
    // remove fixed columns
703
    delete_fixed_cols_from_problem(prob);
172✔
704
    delete_inactive_cols_from_A_and_AT(constraints);
172✔
705
    return UNCHANGED;
172✔
706
}
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