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

dance858 / PSLP / 20392404677

20 Dec 2025 09:26AM UTC coverage: 88.78% (+0.4%) from 88.341%
20392404677

Pull #28

github

web-flow
Merge 94967eb8b into 5be939408
Pull Request #28: Development

1246 of 1401 branches covered (88.94%)

Branch coverage included in aggregate %.

52 of 55 new or added lines in 6 files covered. (94.55%)

2 existing lines in 1 file now uncovered.

3834 of 4321 relevant lines covered (88.73%)

3387.5 hits per line

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

95.86
/src/explorers/DtonsEq.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 "DTonsEq.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 "Memory_wrapper.h"
28
#include "Numerics.h"
29
#include "Postsolver.h"
30
#include "Problem.h"
31
#include "RowColViews.h"
32
#include "SimpleReductions.h"
33
#include "State.h"
34
#include "Timer.h"
35
#include "Workspace.h"
36
#include <string.h>
37

38
/* Checks if there is sufficient space in AT for the substitution.
39
   This includes functionality for shifting rows to create sufficient
40
   space. Column j stays, column k is substituted */
41
#ifdef NDEBUG
42
static inline bool sufficient_space_AT(Matrix *AT, int j, int k, int max_shift)
43
#else
44
static inline bool sufficient_space_AT(Matrix *AT, int j, int k, int max_shift,
29✔
45
                                       const RowTag *row_tags)
46
#endif
47
{
48
    int len_col_j, len_col_k, fill_in, jj, kk, free_space;
49
    const int *rows_col_j, *rows_col_k;
50

51
    rows_col_j = AT->i + AT->p[j].start;
29✔
52
    len_col_j = AT->p[j].end - AT->p[j].start;
29✔
53
    rows_col_k = AT->i + AT->p[k].start;
29✔
54
    len_col_k = AT->p[k].end - AT->p[k].start;
29✔
55

56
    // check that there are no inactive rows in column j and k
57
    DEBUG(ASSERT_NO_INACTIVE_ROWS(rows_col_j, row_tags, len_col_j););
175✔
58
    DEBUG(ASSERT_NO_INACTIVE_ROWS(rows_col_k, row_tags, len_col_k););
126✔
59

60
    // -----------------------------------------------------------------
61
    // compute the fill-in in column j caused by substituting column k
62
    // -----------------------------------------------------------------
63
    fill_in = -1;
29✔
64
    jj = 0;
29✔
65
    kk = 0;
29✔
66
    while (jj < len_col_j && kk < len_col_k)
150✔
67
    {
68
        if (rows_col_j[jj] == rows_col_k[kk])
121✔
69
        {
70
            jj += 1;
53✔
71
            kk += 1;
53✔
72
        }
73
        else if (rows_col_k[kk] < rows_col_j[jj])
68✔
74
        {
75
            kk += 1;
42✔
76
            fill_in += 1;
42✔
77
        }
78
        else
79
        {
80
            jj += 1;
26✔
81
        }
82
    }
83
    fill_in += len_col_k - kk;
29✔
84

85
    //-------------------------------------------------------------------
86
    // Check if there is sufficient space in AT for the substitution.
87
    // If not, try to shift rows to create space.
88
    //-------------------------------------------------------------------
89
    free_space = AT->p[j + 1].start - AT->p[j].end;
29✔
90
    return (free_space >= fill_in || shift_row(AT, j, fill_in, max_shift));
29✔
91
}
92

93
/* This function checks if a doubleton row should be eliminated. It modifies
94
   the stay and subst variables. If no substitution is possible, stay and subst
95
   are set to -1. If the substitution is possible, stay and subst are set to
96
   the *column indices* of the variables that will stay and be substituted.
97
   The bounds of the variable that will be substituted are NOT modified.
98
*/
99
#ifdef NDEBUG
100
static inline void can_dton_be_eliminated(Matrix *AT, const double *row_vals,
101
                                          const int *row_cols, int *stay, int *subst,
102
                                          int col_size0, int col_size1,
103
                                          int max_shift)
104
#else
105
static inline void can_dton_be_eliminated(Matrix *AT, const double *row_vals,
29✔
106
                                          const int *row_cols, int *stay, int *subst,
107
                                          int col_size0, int col_size1,
108
                                          int max_shift, const RowTag *row_tags)
109
#endif
110
{
111
    double a_abs = ABS(row_vals[0]);
29✔
112
    double b_abs = ABS(row_vals[1]);
29✔
113
    bool is_integral0 = IS_INTEGRAL(a_abs / b_abs);
29✔
114
    bool is_integral1 = IS_INTEGRAL(b_abs / a_abs);
29✔
115

116
    // ------------------------------------------------------------------------
117
    // Decide which variable to substitute. For a row ax + by = c we can do
118
    // substitutions x = -(b/a)y + c/a or y = -(a/b)x + c/b. If b/a is an
119
    // integer but not a/b we substitute x. If a/b is an integer but not b/a
120
    // we substitute y.
121
    //
122
    // We might want to add additional criteria here? Do we first want to
123
    // check if one of the columns has far more nnz than the other one?
124
    // ------------------------------------------------------------------------
125
    if (col_size0 == 1 && col_size1 != 1)
29✔
126
    {
127
        *subst = 0;
×
128
    }
129
    else if (col_size0 != 1 && col_size1 == 1)
29✔
130
    {
131
        *subst = 1;
×
132
    }
133
    else if (is_integral0 && !is_integral1)
29✔
134
    {
135
        *subst = 1;
9✔
136
    }
137
    else if (is_integral1 && !is_integral0)
20✔
138
    {
139
        *subst = 0;
12✔
140
    }
141
    else
142
    {
143
        if (col_size0 < col_size1)
8✔
144
        {
145
            *subst = 0;
4✔
146
        }
147
        else
148
        {
149
            *subst = 1;
4✔
150
        }
151
    }
152

153
    *stay = 1 - *subst;
29✔
154

155
    // ------------------------------------------------------------------------
156
    // Simple test to reject large or small pivots. We might want to add a more
157
    // sophisticated check here. We could potentially move this in the code.
158
    // ------------------------------------------------------------------------
159
    double abs_pivot = row_vals[*stay] / row_vals[*subst];
29✔
160
    abs_pivot = ABS(abs_pivot);
29✔
161
    if (abs_pivot > MAX_RATIO_PIVOT || abs_pivot < 1 / MAX_RATIO_PIVOT)
29✔
162
    {
163
        assert("Is this ever triggered now with this small threshold?");
164
        *stay = -1;
×
165
        *subst = -1;
×
166
        return;
×
167
    }
168

169
    // ------------------------------------------------------------------------
170
    // Check if there is sufficient space in AT for the substitution.
171
    // ------------------------------------------------------------------------
172
#ifdef NDEBUG
173
    if (!sufficient_space_AT(AT, row_cols[*stay], row_cols[*subst], max_shift))
174
#else
175
    if (!sufficient_space_AT(AT, row_cols[*stay], row_cols[*subst], max_shift,
29✔
176
                             row_tags))
177
#endif
178
    {
179
        *stay = -1;
5✔
180
        *subst = -1;
5✔
181
        return;
5✔
182
    }
183

184
    *stay = row_cols[*stay];
24✔
185
    *subst = row_cols[*subst];
24✔
186
}
187

188
// lb and ub are bounds on variable that gets substituted
189
static inline void modify_bounds(Constraints *constraints, int i, double aij,
24✔
190
                                 double aik, double rhs, double lb_subst,
191
                                 double ub_subst, int stay, ColTag col_tag_subst)
192
{
193
    assert(aij != 0 && aik != 0);
24✔
194
    bool same_sign = (aik * aij > 0.0);
24✔
195

196
    if (same_sign)
24✔
197
    {
198
        if (!HAS_TAG(col_tag_subst, C_TAG_LB_INF))
9✔
199
        {
200
            double new_ub_cand = (rhs - aik * lb_subst) / aij;
8✔
201
            update_ub(constraints, stay, new_ub_cand, i HUGE_BOUND_IS_NOT_OK);
8✔
202

203
            if (HAS_TAG(constraints->col_tags[stay], C_TAG_FIXED))
8✔
204
            {
205
                return;
×
206
            }
207
        }
208

209
        if (!HAS_TAG(col_tag_subst, C_TAG_UB_INF))
9✔
210
        {
211
            double new_lb_cand = (rhs - aik * ub_subst) / aij;
2✔
212
            update_lb(constraints, stay, new_lb_cand, i HUGE_BOUND_IS_NOT_OK);
2✔
213
        }
214
    }
215
    else
216
    {
217
        if (!HAS_TAG(col_tag_subst, C_TAG_LB_INF))
15✔
218
        {
219
            double new_lb_cand = (rhs - aik * lb_subst) / aij;
14✔
220
            update_lb(constraints, stay, new_lb_cand, i HUGE_BOUND_IS_NOT_OK);
14✔
221

222
            if (HAS_TAG(constraints->col_tags[stay], C_TAG_FIXED))
14✔
223
            {
224
                return;
×
225
            }
226
        }
227

228
        if (!HAS_TAG(col_tag_subst, C_TAG_UB_INF))
15✔
229
        {
230
            double new_ub_cand = (rhs - aik * ub_subst) / aij;
6✔
231
            update_ub(constraints, stay, new_ub_cand, i HUGE_BOUND_IS_NOT_OK);
6✔
232
        }
233
    }
234
}
235

236
#ifndef TESTING
237
static inline
238
#endif
239
    Old_and_new_coeff update_row_A_dton(Matrix *A, int i, int q, int j, int k,
70✔
240
                                        double aij, double aik, int *row_size,
241
                                        PostsolveInfo *postsolve_info)
242
{
243
    int ii, start, end, insertion;
244
    double old_val = 0.0;
70✔
245
    int subst_idx = -1;
70✔
246
    int diff_row_size = 0;
70✔
247
    start = A->p[q].start;
70✔
248
    end = A->p[q].end;
70✔
249
    insertion = end;
70✔
250

251
    // -----------------------------------------------------------------
252
    // find index of variable that is substituted and and the index of
253
    // insertion of the variable that stays
254
    // -----------------------------------------------------------------
255
    for (ii = start; ii < end; ++ii)
131✔
256
    {
257
        if (A->i[ii] == k)
131✔
258
        {
259
            subst_idx = ii;
70✔
260
            break;
70✔
261
        }
262
    }
263

264
    assert(subst_idx != -1);
70✔
265

266
    for (ii = start; ii < end; ++ii)
166✔
267
    {
268
        if (A->i[ii] >= j)
157✔
269
        {
270
            insertion = ii;
61✔
271
            break;
61✔
272
        }
273
    }
274

275
    // -----------------------------------------------------------------
276
    // Compute new coefficient of the variable that stays.
277
    // TODO: should we have rejected the reduction if the new value is
278
    // very small? (don't delete todo here)
279
    // -----------------------------------------------------------------
280
    if (insertion != end && A->i[insertion] == j)
70✔
281
    {
282
        old_val = A->x[insertion];
38✔
283
    }
284

285
    double new_val = old_val - (aij / aik) * A->x[subst_idx];
70✔
286
    Old_and_new_coeff old_and_new_coeff;
287
    old_and_new_coeff.old_coeff_stay = old_val;
70✔
288
    old_and_new_coeff.old_coeff_subst = A->x[subst_idx];
70✔
289
    old_and_new_coeff.new_coeff_stay = new_val;
70✔
290

291
    save_retrieval_added_row(postsolve_info, i, q, -A->x[subst_idx] / aik);
70✔
292

293
#ifndef NDEBUG
294
    if (ABS(A->x[subst_idx]) > 1e-6)
70✔
295
    {
296
        assert(ABS(new_val) > 1e-6 || new_val == 0.0);
70✔
297
    }
298

299
    if (ABS(new_val) < 1e-6 && new_val != 0.0)
70✔
300
    {
301
        // when we eliminate row 32833 on momentum and substitute col x20
302
        // into row 30698 this is triggered, because x20 has a very small
303
        // coefficient in row 30698 BEFORE the substitution
304
        printf("warning debug: small coefficient dtonrow \n");
×
305
    }
306
#endif
307

308
    // -------------------------------------------------------------------
309
    //          remove variable that is substituted
310
    // -------------------------------------------------------------------
311
    memmove(A->x + subst_idx, A->x + subst_idx + 1,
70✔
312
            (end - subst_idx - 1) * sizeof(double));
70✔
313
    memmove(A->i + subst_idx, A->i + subst_idx + 1,
70✔
314
            (end - subst_idx - 1) * sizeof(int));
70✔
315
    end -= 1;
70✔
316
    diff_row_size += 1;
70✔
317
    insertion -= (subst_idx < insertion);
70✔
318

319
    // -------------------------------------------------------------------
320
    // if we get unexpected cancellation we should remove the coefficient,
321
    // otherwise we insert it
322
    // -------------------------------------------------------------------
323
    if (ABS(new_val) <= ZERO_TOL)
70✔
324
    {
325
        memmove(A->x + insertion, A->x + insertion + 1,
16✔
326
                (end - insertion - 1) * sizeof(double));
16✔
327
        memmove(A->i + insertion, A->i + insertion + 1,
16✔
328
                (end - insertion - 1) * sizeof(int));
16✔
329
        end -= 1;
16✔
330
        diff_row_size += 1;
16✔
331
    }
332
    // -----------------------------------------------------------------
333
    // Insert the new value. If it exists or should be inserted in
334
    // the end, we don't need to shift values.
335
    // -----------------------------------------------------------------
336
    else
337
    {
338
        if (insertion == end)
54✔
339
        {
340
            A->x[insertion] = new_val;
10✔
341
            A->i[insertion] = j;
10✔
342
            end += 1;
10✔
343
            diff_row_size -= 1;
10✔
344
        }
345
        else if (A->i[insertion] == j)
44✔
346
        {
347
            A->x[insertion] = new_val;
22✔
348
        }
349
        else
350
        {
351
            memmove(A->x + insertion + 1, A->x + insertion,
22✔
352
                    (end - insertion) * sizeof(double));
22✔
353
            memmove(A->i + insertion + 1, A->i + insertion,
22✔
354
                    (end - insertion) * sizeof(int));
22✔
355
            A->x[insertion] = new_val;
22✔
356
            A->i[insertion] = j;
22✔
357
            end += 1;
22✔
358
            diff_row_size -= 1;
22✔
359
        }
360
    }
361

362
    A->p[q].end = end;
70✔
363
    *row_size -= diff_row_size;
70✔
364
    A->nnz -= diff_row_size;
70✔
365

366
    assert(*row_size == end - start);
70✔
367
    DEBUG(ASSERT_INCREASING_I(A->i + start, *row_size););
70✔
368
    DEBUG(ASSERT_NO_ZEROS_D(A->x + start, *row_size););
70✔
369
    assert(A->p[q].end <= A->p[q + 1].start);
70✔
370
    return old_and_new_coeff;
70✔
371
}
372

373
static inline void subst_col_in_obj_dton(Objective *obj, int stay, int subst,
24✔
374
                                         double aik, double aij, double rhs)
375
{
376
    obj->c[stay] -= (aij / aik) * obj->c[subst];
24✔
377
    obj->offset += (rhs / aik) * obj->c[subst];
24✔
378
}
24✔
379

380
// lhs and rhs should point to the row whose lhs and rhs should be modified
381
// rTag = row_tags[rows_subst[j]], lhs = lhs + rows_subst[j],
382
// rhs = rhs + rows_subst[j], rhs_dton
383
static inline void update_lhs_and_rhs(double *lhs, double *rhs, double aik,
52✔
384
                                      RowTag rTag, double rhs_dton,
385
                                      double coeff_subst)
386
{
387
    if (rhs_dton != 0.0)
52✔
388
    {
389
        double change = coeff_subst / aik * rhs_dton;
34✔
390

391
        if (!HAS_TAG(rTag, R_TAG_LHS_INF))
34✔
392
        {
393
            *lhs -= change;
24✔
394
        }
395

396
        if (!HAS_TAG(rTag, R_TAG_RHS_INF))
34✔
397
        {
398
            *rhs -= change;
32✔
399
        }
400
    }
401
}
52✔
402

403
/* Decides which variable that should be substituted. If the substitution
404
   is rejected for whatever reason, it sets j = -1. */
405
#ifdef NDEBUG
406
static void find_substitution(const RowView *row, const ColTag *col_tags, int *j,
407
                              int *k, double *aik, double *aij, Matrix *AT,
408
                              int *col_sizes, int max_shift_per_row,
409
                              int *iwork_n_rows, int *n_new_dton_rows)
410
#else
411
static void find_substitution(const RowView *row, const ColTag *col_tags, int *j,
32✔
412
                              int *k, double *aik, double *aij, Matrix *AT,
413
                              int *col_sizes, int max_shift_per_row,
414
                              const RowTag *row_tags, int *iwork_n_rows,
415
                              int *n_new_dton_rows)
416
#endif
417
{
418

419
    // a previous dtonrow might have changed this dtonrows sparsity pattern,
420
    // or a row that used to be a dtonrow was pushed to the list of dtonrows
421
    // but since then one or both of its variables have been fixed because
422
    // of singleton rows
423
    if (*row->len < 2)
32✔
424
    {
425
        *j = -1;
3✔
426
        return;
3✔
427
    }
428

429
    assert(HAS_TAG(*row->tag, R_TAG_EQ) && !HAS_TAG(*row->tag, R_TAG_INACTIVE));
29✔
430
    assert(*row->lhs == *row->rhs && !IS_ABS_INF(*row->rhs));
29✔
431

432
    // One of the variables might have been fixed when eliminating
433
    // another doubleton row. However, the variable has not been
434
    // removed from the problem yet so the row size check above
435
    // is not sufficient, and we also need this check
436
    if (HAS_TAG((col_tags[row->cols[0]] | col_tags[row->cols[1]]), C_TAG_INACTIVE))
29✔
437
    {
438
        *j = -1;
×
439
        return;
×
440
    }
441

442
    // -------------------------------------------------------------------
443
    // check if there is sufficient space to eliminate the doubleton row,
444
    // if stay == -1 it means that the elimination was rejected
445
    // -------------------------------------------------------------------
446
#ifdef NDEBUG
447
    can_dton_be_eliminated(AT, row->vals, row->cols, j, k, col_sizes[row->cols[0]],
448
                           col_sizes[row->cols[1]], max_shift_per_row);
449
#else
450
    can_dton_be_eliminated(AT, row->vals, row->cols, j, k, col_sizes[row->cols[0]],
29✔
451
                           col_sizes[row->cols[1]], max_shift_per_row, row_tags);
29✔
452
#endif
453

454
    if (*j == -1)
29✔
455
    {
456
        // append the row so we try it again in next round
457
        iwork_n_rows[*n_new_dton_rows] = row->i;
5✔
458
        *n_new_dton_rows += 1;
5✔
459
        return;
5✔
460
    }
461

462
    assert(*j == row->cols[0] || *j == row->cols[1]);
24✔
463
    assert(*k == row->cols[0] || *k == row->cols[1]);
24✔
464
    assert(!HAS_TAG(col_tags[*j], C_TAG_INACTIVE));
24✔
465
    assert(!HAS_TAG(col_tags[*k], C_TAG_INACTIVE));
24✔
466

467
    // Our current implementation of singleton columns does not take
468
    // into account that a column singleton in a dtonrow can always be
469
    // be eliminated. Hence, we might eliminate dtonrows here that
470
    // contains a singleton column.
471
    assert(col_sizes[*j] != 1 || (col_sizes[*j] == 1 && col_sizes[*k] == 1));
24✔
472

473
    // -------------------------------------------------------------------
474
    // Find coefficient that stays. i is the row index, column j
475
    // stays, and column k gets substituted
476
    // -------------------------------------------------------------------
477
    if (*k == row->cols[0])
24✔
478
    {
479
        assert(*j == row->cols[1]);
15✔
480
        *aik = row->vals[0];
15✔
481
        *aij = row->vals[1];
15✔
482
    }
483
    else
484
    {
485
        assert(*j == row->cols[0] && *k == row->cols[1]);
9✔
486
        *aik = row->vals[1];
9✔
487
        *aij = row->vals[0];
9✔
488
    }
489
}
490

491
static void execute_substitution(Constraints *constraints, RowView *row, int j,
24✔
492
                                 int k, double aij, double aik, int *n_new_dton_rows,
493
                                 double ck)
494
{
495
    Matrix *A = constraints->A;
24✔
496
    Matrix *AT = constraints->AT;
24✔
497
    double *lhs = constraints->lhs;
24✔
498
    double *rhs = constraints->rhs;
24✔
499
    int *col_sizes = constraints->state->col_sizes;
24✔
500
    int *row_sizes = constraints->state->row_sizes;
24✔
501
    Activity *activities = constraints->state->activities;
24✔
502
    PostsolveInfo *postsolve_info = constraints->state->postsolve_info;
24✔
503
    Bound *bounds = constraints->bounds;
24✔
504
    RowTag *row_tags = constraints->row_tags;
24✔
505
    ColTag *col_tags = constraints->col_tags;
24✔
506

507
    iVec *empty_rows = constraints->state->empty_rows;
24✔
508
    iVec *ston_rows = constraints->state->ston_rows;
24✔
509
    int *iwork_n_rows = constraints->state->work->iwork_n_rows;
24✔
510

511
    Altered_Activity altered1, altered2;
512
    int jj, q, len, old_len;
513
    int *rows_subst;
514

515
    rows_subst = AT->i + AT->p[k].start;
24✔
516
    len = AT->p[k].end - AT->p[k].start;
24✔
517
    RowView rowj_AT = new_rowview(AT->x + AT->p[j].start, AT->i + AT->p[j].start,
24✔
518
                                  col_sizes + j, AT->p + j, NULL, NULL, NULL, j);
24✔
519

520
    // remove contribution of xk in AT (we do it before the loop to free space)
521
    remove_coeff(&rowj_AT, row->i);
24✔
522
    AT->p[k].end = AT->p[k].start;
24✔
523

524
    // we substitute variable xk from all rows q
525
    for (jj = 0; jj < len; ++jj)
100✔
526
    {
527
        q = rows_subst[jj];
76✔
528
        if (HAS_TAG(row_tags[q], R_TAG_INACTIVE))
76✔
529
        {
530
            continue;
24✔
531
        }
532

533
        RowView sub_row =
534
            new_rowview(A->x + A->p[q].start, A->i + A->p[q].start, row_sizes + q,
52✔
535
                        A->p + q, lhs + q, rhs + q, row_tags + q, q);
52✔
536

537
        assert(sub_row.i != row->i);
52✔
538

539
        // update constraint matrix A (updates row size)
540
        old_len = *sub_row.len;
52✔
541
        Old_and_new_coeff coeffs = update_row_A_dton(
52✔
542
            A, row->i, sub_row.i, j, k, aij, aik, sub_row.len, postsolve_info);
543

544
        // Check row size and push to list of empty rows, stonrows and
545
        // dtonrows. The elimination of one dtonrow might lead to another
546
        // dtonrow, so we must be careful with how we append. We use a
547
        // temporary buffer to handle this.
548
        switch (*sub_row.len)
52✔
549
        {
550
            case 0:
2✔
551
                assert(!iVec_contains(empty_rows, sub_row.i));
2✔
552
                iVec_append(empty_rows, sub_row.i);
2✔
553
                assert(!HAS_TAG(*sub_row.tag, R_TAG_INACTIVE));
2✔
554
                assert(sub_row.range->start == sub_row.range->end);
2✔
555
                break;
2✔
556
            case 1:
3✔
557
                if (old_len != 1)
3✔
558
                {
559
                    assert(!iVec_contains(ston_rows, sub_row.i));
3✔
560
                    iVec_append(ston_rows, sub_row.i);
3✔
561
                    assert(!HAS_TAG(*sub_row.tag, R_TAG_INACTIVE));
3✔
562
                }
563
                break;
3✔
564
            case 2:
11✔
565
                if (HAS_TAG(*sub_row.tag, R_TAG_EQ) && old_len != 2)
11✔
566
                {
567
                    iwork_n_rows[*n_new_dton_rows] = sub_row.i;
×
568
                    *n_new_dton_rows += 1;
×
569
                    assert(!HAS_TAG(*sub_row.tag, R_TAG_INACTIVE));
×
570
                }
571
                break;
11✔
572
        }
573

574
        // update constraint matrix AT (updates column size)
575
        insert_or_update_coeff(AT, j, sub_row.i, coeffs.new_coeff_stay,
52✔
576
                               col_sizes + j);
52✔
577

578
        // update lhs and rhs
579
        update_lhs_and_rhs(sub_row.lhs, sub_row.rhs, aik, *sub_row.tag, rhs[row->i],
52✔
580
                           coeffs.old_coeff_subst);
581

582
        // update activity due to the coefficient change of variable that stays
583
        altered1 = update_activity_coeff_change(activities + q, bounds[j].lb,
52✔
584
                                                bounds[j].ub, coeffs.old_coeff_stay,
52✔
585
                                                coeffs.new_coeff_stay, col_tags[j]);
52✔
586

587
        // update activity due to the coefficient change of variable that is
588
        // substituted
589
        altered2 =
590
            update_activity_coeff_change(activities + q, bounds[k].lb, bounds[k].ub,
52✔
591
                                         coeffs.old_coeff_subst, 0.0, col_tags[k]);
52✔
592

593
        if ((altered1 | altered2) & MIN_ALTERED_RECOMPUTE)
52✔
594
        {
595
            assert(*sub_row.len == A->p[q].end - A->p[q].start);
4✔
596
            activities[sub_row.i].min = compute_min_act_no_tags(
8✔
597
                sub_row.vals, sub_row.cols, *sub_row.len, bounds);
4✔
598

599
            if (activities[sub_row.i].status == NOT_ADDED)
4✔
600
            {
601
                activities[sub_row.i].status = ADDED;
4✔
602
                iVec_append(constraints->state->updated_activities, sub_row.i);
4✔
603
            }
604
        }
605

606
        if ((altered1 | altered2) & MAX_ALTERED_RECOMPUTE)
52✔
607
        {
608
            // if this assertion fails, replace *sub_row.len with the rhs
609
            assert(*sub_row.len == A->p[q].end - A->p[q].start);
2✔
610
            activities[sub_row.i].max = compute_max_act_no_tags(
4✔
611
                sub_row.vals, sub_row.cols, *sub_row.len, bounds);
2✔
612

613
            if (activities[sub_row.i].status == NOT_ADDED)
2✔
614
            {
615
                activities[sub_row.i].status = ADDED;
1✔
616
                iVec_append(constraints->state->updated_activities, sub_row.i);
1✔
617
            }
618
        }
619
    }
620

621
    // update list of empty and singleton columns
622
    switch (col_sizes[j])
24✔
623
    {
624
        case 0:
2✔
625
            assert(!HAS_TAG(col_tags[j], C_TAG_INACTIVE));
2✔
626
            assert(!iVec_contains(constraints->state->empty_cols, j));
2✔
627
            iVec_append(constraints->state->empty_cols, j);
2✔
628
            assert(AT->p[j].start == AT->p[j].end);
2✔
629
            break;
2✔
630
        case 1:
2✔
631
            assert(!HAS_TAG(col_tags[j], C_TAG_INACTIVE));
2✔
632
            assert(!iVec_contains(constraints->state->ston_cols, j));
2✔
633
            iVec_append(constraints->state->ston_cols, j);
2✔
634
            break;
2✔
635
        default:
20✔
636
            break;
20✔
637
    }
638

639
    // mark row of AT as inactive (must do it after loop due to
640
    // assertion in update activities)
641
    col_tags[k] = C_TAG_INACTIVE;
24✔
642
    col_sizes[k] = SIZE_INACTIVE_COL;
24✔
643

644
    assert(AT->p[j].end - AT->p[j].start == col_sizes[j]);
24✔
645
    count_locks_one_column(&rowj_AT, constraints->state->col_locks + j, row_tags);
24✔
646

647
    // postsolve information
648
    save_retrieval_sub_col(postsolve_info, k, row->cols, row->vals, 2, rhs[row->i],
24✔
649
                           row->i, ck);
650
    save_retrieval_deleted_row(postsolve_info, row->i, ck / aik);
24✔
651
}
24✔
652

653
/* We assume the row i is a doubleton equality row with variables
654
xj and xk, where xk is substituted and xj stays. We eliminate
655
xk from every row q that contains xk. */
656
static PresolveStatus remove_dton_eq_rows__(Problem *prob, int max_shift_per_row)
23✔
657
{
658
    int ii, i, j, k;
659
    int n_new_dton_rows = 0;
23✔
660
    double aik, aij;
661
    Constraints *constraints = prob->constraints;
23✔
662
    Matrix *A = constraints->A;
23✔
663
    Matrix *AT = constraints->AT;
23✔
664
    double *rhs = constraints->rhs;
23✔
665
    double *lhs = constraints->lhs;
23✔
666
    RowRange *row_r = A->p;
23✔
667
    RowTag *row_tags = constraints->row_tags;
23✔
668
    ColTag *col_tags = constraints->col_tags;
23✔
669
    const iVec *dton_rows = constraints->state->dton_rows;
23✔
670
    int *col_sizes = constraints->state->col_sizes;
23✔
671
    int *row_sizes = constraints->state->row_sizes;
23✔
672

673
    const Bound *bounds = constraints->bounds;
23✔
674
    PresolveStatus status = UNCHANGED;
23✔
675
    int *iwork_n_rows = constraints->state->work->iwork_n_rows;
23✔
676

677
    DEBUG(verify_no_duplicates_sort(dton_rows));
23✔
678

679
    for (ii = 0; ii < dton_rows->len; ++ii)
55✔
680
    {
681
        i = dton_rows->data[ii];
32✔
682
        RowView row =
683
            new_rowview(A->x + row_r[i].start, A->i + row_r[i].start, row_sizes + i,
32✔
684
                        A->p + i, lhs + i, rhs + i, row_tags + i, i);
32✔
685

686
#ifdef NDEBUG
687
        //  find which variable that should be substituted (j stays, k goes)
688
        find_substitution(&row, col_tags, &j, &k, &aik, &aij, AT, col_sizes,
689
                          max_shift_per_row, iwork_n_rows, &n_new_dton_rows);
690
#else
691
        find_substitution(&row, col_tags, &j, &k, &aik, &aij, AT, col_sizes,
32✔
692
                          max_shift_per_row, row_tags, iwork_n_rows,
693
                          &n_new_dton_rows);
694
#endif
695

696
        // no variable could be substituted
697
        if (j == -1)
32✔
698
        {
699
            // todo: here we could run simple primal sparsification?
700
            continue;
8✔
701
        }
702

703
        status = REDUCED;
24✔
704

705
        // transfer bounds to variable that stays and skip the reduction
706
        // if the variable that stays was fixed because of the bound change
707
        modify_bounds(constraints, i, aij, aik, *row.rhs, bounds[k].lb, bounds[k].ub,
24✔
708
                      j, col_tags[k]);
24✔
709

710
        if (HAS_TAG(col_tags[j], C_TAG_INACTIVE))
24✔
711
        {
712
            continue;
×
713
        }
714

715
        //  substitute variable in objective and mark the eliminated row as
716
        //  inactive
717
        subst_col_in_obj_dton(prob->obj, j, k, aik, aij, *row.rhs);
24✔
718
        RESET_TAG(*row.tag, R_TAG_INACTIVE);
24✔
719
        *row.len = SIZE_INACTIVE_ROW;
24✔
720
        row.range->end = row.range->start;
24✔
721
        A->nnz -= 2;
24✔
722

723
        // Substitute the variable in the constraints, using special
724
        // functionality
725
        execute_substitution(constraints, &row, j, k, aij, aik, &n_new_dton_rows,
24✔
726
                             prob->obj->c[k]);
24✔
727
    }
728

729
    AT->nnz = A->nnz;
23✔
730

731
    // clear the list of processed dton rows
732
    iVec_clear_no_resize(constraints->state->dton_rows);
23✔
733

734
    // append the new dton rows
735
    if (n_new_dton_rows > 0)
23✔
736
    {
737
        DEBUG(verify_no_duplicates_sort_ptr(iwork_n_rows, n_new_dton_rows));
5✔
738
        iVec_append_array(constraints->state->dton_rows, iwork_n_rows,
5✔
739
                          n_new_dton_rows);
740
    }
741

742
    return status;
23✔
743
}
744

745
PresolveStatus remove_dton_eq_rows(Problem *prob, int max_shift_per_row)
64✔
746
{
747
    assert(prob->constraints->state->ston_rows->len == 0);
64✔
748
    assert(prob->constraints->state->empty_rows->len == 0);
64✔
749
    assert(prob->constraints->state->empty_cols->len == 0);
64✔
750
    DEBUG(verify_problem_up_to_date(prob->constraints));
64✔
751

752
    // PresolveStatus status = UNCHANGED;
753

754
    // important to have double ptr in case of realloc when appending
755
    iVec **dton_rows = &(prob->constraints->state->dton_rows);
64✔
756
    while ((*dton_rows)->len > 0)
84✔
757
    {
758
        PresolveStatus temp = remove_dton_eq_rows__(prob, max_shift_per_row);
23✔
759

760
        if (temp != REDUCED)
23✔
761
        {
762
            break;
3✔
763
        }
764

765
        /*
766
        if (temp == REDUCED)
767
        {
768
            status = REDUCED;
769
        }
770
        else
771
        {
772
            break;
773
        }
774
        */
775
    }
776

777
    // some variables might have been fixed because of the bound change
778
    if (prob->constraints->state->fixed_cols_to_delete->len > 0)
64✔
779
    {
UNCOV
780
        delete_fixed_cols_from_problem(prob);
×
UNCOV
781
        delete_inactive_cols_from_A_and_AT(prob->constraints);
×
782
    }
783

784
    DEBUG(verify_problem_up_to_date(prob->constraints));
64✔
785

786
    // always return UNCHANGED because this function can't detect infeas/unbnd.
787
    return UNCHANGED;
64✔
788
}
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