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

dance858 / PSLP / 21522825537

30 Jan 2026 04:26PM UTC coverage: 88.995% (+0.007%) from 88.988%
21522825537

Pull #34

github

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

1249 of 1400 branches covered (89.21%)

Branch coverage included in aggregate %.

226 of 243 new or added lines in 20 files covered. (93.0%)

1 existing line in 1 file now uncovered.

3854 of 4334 relevant lines covered (88.92%)

3375.4 hits per line

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

80.47
/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)
424✔
34
{
35
    const Constraints *constraints = prob->constraints;
424✔
36
    const Matrix *AT = constraints->AT;
424✔
37
    const Bound *bounds = constraints->bounds;
424✔
38
    const RowTag *row_tags = constraints->row_tags;
424✔
39
    double *lhs = constraints->lhs;
424✔
40
    double *rhs = constraints->rhs;
424✔
41
    Activity *activities = constraints->state->activities;
424✔
42
    const iVec *fixed_cols_to_delete =
424✔
43
        prob->constraints->state->fixed_cols_to_delete;
424✔
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)
453✔
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
}
424✔
123

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

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

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

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

189
        if (aik > 0)
9✔
190
        {
191
            if ((!rhs_inf && (update_ub(constrs, k, rhs / aik,
9✔
192
                                        row HUGE_BOUND_IS_OK) == INFEASIBLE)) ||
9✔
193
                (!lhs_inf && (update_lb(constrs, k, lhs / aik,
9✔
194
                                        row HUGE_BOUND_IS_OK) == INFEASIBLE)))
195
            {
196
                return INFEASIBLE;
1✔
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)
218✔
220
{
221
    iVec *ston_rows = prob->constraints->state->ston_rows;
218✔
222
    const int *ston_rows_data = ston_rows->data;
218✔
223
    PSLP_uint len = ston_rows->len;
218✔
224
    RowTag *row_tags = prob->constraints->row_tags;
218✔
225
    DEBUG(verify_no_duplicates_sort(ston_rows));
218✔
226

227
    if (len == 0)
218✔
228
    {
229
        return UNCHANGED;
206✔
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 (PSLP_uint i = 0; i < len; ++i)
28✔
236
    {
237
        int row = ston_rows_data[i];
16✔
238

239
        if (!HAS_TAG(row_tags[row], R_TAG_EQ) ||
16✔
240
            HAS_TAG(row_tags[row], R_TAG_INACTIVE))
6✔
241
        {
242
            continue;
10✔
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 (PSLP_uint i = 0; i < len; ++i)
27✔
254
    {
255
        int row = ston_rows_data[i];
16✔
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)))
16✔
261
        {
262
            continue;
6✔
263
        }
264

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

267
        if (INFEASIBLE == remove_stonrow(prob, row))
10✔
268
        {
269
            return INFEASIBLE;
1✔
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;
11✔
276

277
    iVec_clear_no_resize(ston_rows);
11✔
278

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

283
    delete_fixed_cols_from_problem(prob);
11✔
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);
11✔
288

289
    DEBUG(verify_problem_up_to_date(prob->constraints));
11✔
290
    return REDUCED;
11✔
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,
45✔
297
                                    RowTag row_tag)
298
{
299
    assert(!HAS_TAG(row_tag, R_TAG_EQ));
45✔
300
    RowTag return_tag = R_TAG_NONE;
45✔
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))
45✔
307
    {
308
        if (act->n_inf_min == 0 && act->min >= rhs + FEAS_TOL)
37✔
309
        {
310
            return R_TAG_INFEAS;
1✔
311
        }
312
        else if (act->n_inf_max == 0 && act->max <= rhs + FEAS_TOL)
36✔
313
        {
314
            UPDATE_TAG(return_tag, R_TAG_RHS_INF);
8✔
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))
44✔
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))
43✔
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;
43✔
345
}
346

347
PresolveStatus check_activities(Problem *prob)
22✔
348
{
349
    const iVec *acts_to_check = prob->constraints->state->updated_activities;
22✔
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));
22✔
354
    DEBUG(verify_activities(prob->constraints));
22✔
355

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

365
    int i, j, row;
366

367
    for (i = 0; i < acts_to_check->len; ++i)
94✔
368
    {
369
        row = acts_to_check->data[i];
74✔
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)) ||
74✔
377
            row_sizes[row] <= 1)
45✔
378
        {
379
            continue;
29✔
380
        }
381

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

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

387
        // ----------------------------------------------------------------
388
        //              if the constraint is redundant
389
        // ----------------------------------------------------------------
390
        if (HAS_TAG((status_tag | row_tags[row]), R_TAG_LHS_INF) &&
45✔
391
            HAS_TAG((status_tag | row_tags[row]), R_TAG_RHS_INF))
34✔
392
        {
393
            set_row_to_inactive(row, row_tags + row, rows_to_delete,
8✔
394
                                prob->constraints->state->postsolve_info, 0.0);
8✔
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))
37✔
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))
36✔
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))
35✔
454
        {
455
            return INFEASIBLE;
2✔
456
        }
457
    }
458

459
    delete_inactive_rows(prob->constraints);
20✔
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));
20✔
464
    return UNCHANGED;
20✔
465
}
466

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

473
    if (len == 0)
231✔
474
    {
475
        return UNCHANGED;
221✔
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)
363✔
508
{
509
    iVec *empty_cols = prob->constraints->state->empty_cols;
363✔
510
    const int *empty_cols_data = empty_cols->data;
363✔
511
    PSLP_uint len = empty_cols->len;
363✔
512

513
    double val;
514
    int k;
515

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

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

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

532
        assert(col_sizes[k] == 0 && !HAS_TAG(col_tags[k], C_TAG_INACTIVE));
9✔
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)
9✔
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)
9✔
557
        {
558
            if (HAS_TAG(col_tags[k], C_TAG_UB_INF))
6✔
559
            {
560
                return UNBNDORINFEAS;
×
561
            }
562

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

577
            val = bounds[k].lb;
3✔
578
            bounds[k].ub = bounds[k].lb;
3✔
579
        }
580

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

584
        // mark column as fixed
585
        UPDATE_TAG(col_tags[k], C_TAG_FIXED);
9✔
586
        col_sizes[k] = SIZE_INACTIVE_COL;
9✔
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,
9✔
591
                                 0);
592
    }
593

594
    iVec_clear_no_resize(empty_cols);
4✔
595
    return UNCHANGED;
4✔
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, col, len, *cols;
602
    double Aik_abs, cum_changes, diff, *vals;
603
    bool has_finite_bounds, set_coeff_to_zero;
604
    PSLP_uint n_rows, row;
UNCOV
605
    n_rows = A->m;
×
606

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

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

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

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

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

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

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

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

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

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

682
    for (PSLP_uint ii = 0; ii < n_cols; ++ii)
1,753✔
683
    {
684
        if (col_sizes[ii] < 0 ||
1,544✔
685
            HAS_TAG(col_tags[ii], (C_TAG_LB_INF | C_TAG_UB_INF)))
1,252✔
686
        {
687
            continue;
726✔
688
        }
689

690
        assert(!HAS_TAG(col_tags[ii], C_TAG_INACTIVE));
818✔
691

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

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