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

dance858 / PSLP / 19475979537

18 Nov 2025 05:53PM UTC coverage: 87.687% (-0.02%) from 87.702%
19475979537

push

github

web-flow
small changes to API (#12)

* code dump

* workflows

* ran formatter

* readme

* workflow

* removed old workflows

* new workflow

* c++ workflow

* C++ workflow

* new line at eof to formatter

* improved interface

* new workflow

* new workflow

* new workflow

* updated default settings

* small changes to API

1223 of 1389 branches covered (88.05%)

Branch coverage included in aggregate %.

1 of 2 new or added lines in 2 files covered. (50.0%)

1 existing line in 1 file now uncovered.

3769 of 4304 relevant lines covered (87.57%)

3390.71 hits per line

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

90.63
/src/core/Presolver.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 "Activity.h"
20
#include "Constraints.h"
21
#include "CoreTransformations.h"
22
#include "DTonsEq.h"
23
#include "Debugger.h"
24
#include "Locks.h"
25
#include "Matrix.h"
26
#include "Memory_wrapper.h"
27
#include "Numerics.h"
28
#include "PSLP_API.h"
29
#include "Parallel_cols.h"
30
#include "Parallel_rows.h"
31
#include "Postsolver.h"
32
#include "PresolveStats.h"
33
#include "Primal_propagation.h"
34
#include "Problem.h"
35
#include "RowColViews.h"
36
#include "SimpleReductions.h"
37
#include "Simple_dual_fix.h"
38
#include "State.h"
39
#include "StonCols.h"
40
#include "Tags.h"
41
#include "Timer.h"
42
#include "Workspace.h"
43
#include "dVec.h"
44
#include "glbopts.h"
45
#include "iVec.h"
46
#include <stdio.h>
47
#include <stdlib.h>
48

49
#define NNZ_REMOVED_FAST 0.95
50
#define NNZ_REMOVED_CYCLE 0.95
51

52
typedef uint8_t Complexity;
53

54
enum Complexity
55
{
56
    FAST = 0,
57
    MEDIUM = 1 << 0
58
};
59

60
PresolveStats *init_stats(int n_rows, int n_cols, int nnz)
87✔
61
{
62
    PresolveStats *stats = (PresolveStats *) ps_calloc(1, sizeof(PresolveStats));
87✔
63
    stats->n_rows_original = n_rows;
87✔
64
    stats->n_cols_original = n_cols;
87✔
65
    stats->nnz_original = nnz;
87✔
66
    return stats;
87✔
67
}
68

69
void free_settings(Settings *stgs)
×
70
{
71
    PS_FREE(stgs);
×
72
}
×
73

74
Settings *default_settings()
91✔
75
{
76
    Settings *stgs = (Settings *) ps_malloc(1, sizeof(Settings));
91✔
77
    RETURN_PTR_IF_NULL(stgs, NULL);
91✔
78
    stgs->ston_cols = true;
91✔
79
    stgs->dton_eq = true;
91✔
80
    stgs->parallel_rows = true;
91✔
81
    stgs->parallel_cols = true;
91✔
82
    stgs->primal_propagation = true;
91✔
83
    stgs->dual_fix = true;
91✔
84
    stgs->clean_small_coeff = false;
91✔
85
    stgs->finite_bound_tightening = true;
91✔
86
    stgs->relax_bounds = true;
91✔
87
    stgs->max_shift = 10;
91✔
88
    stgs->max_time = 60.0;
91✔
89
    stgs->verbose = true;
91✔
90
    return stgs;
91✔
91
}
92

93
void set_settings_true(Settings *stgs)
16✔
94
{
95
    stgs->ston_cols = true;
16✔
96
    stgs->dton_eq = true;
16✔
97
    stgs->parallel_rows = true;
16✔
98
    stgs->parallel_cols = true;
16✔
99
    stgs->primal_propagation = true;
16✔
100
    stgs->dual_fix = true;
16✔
101
    stgs->clean_small_coeff = true;
16✔
102
    stgs->finite_bound_tightening = true;
16✔
103
    stgs->relax_bounds = true;
16✔
104
    stgs->verbose = true;
16✔
105
}
16✔
106

107
void set_settings_false(Settings *stgs)
17✔
108
{
109
    stgs->ston_cols = false;
17✔
110
    stgs->dton_eq = false;
17✔
111
    stgs->parallel_rows = false;
17✔
112
    stgs->parallel_cols = false;
17✔
113
    stgs->primal_propagation = false;
17✔
114
    stgs->dual_fix = false;
17✔
115
    stgs->clean_small_coeff = false;
17✔
116
    stgs->finite_bound_tightening = false;
17✔
117
    stgs->relax_bounds = false;
17✔
118
    stgs->verbose = false;
17✔
119
}
17✔
120

121
typedef struct clean_up_scope
122
{
123
    Matrix *A, *AT;
124
    double *lhs_copy, *rhs_copy, *c_copy;
125
    int *row_sizes, *col_sizes;
126
    RowTag *row_tags;
127
    Lock *locks;
128
    Activity *activities;
129
    State *data;
130
    Constraints *constraints;
131
    Presolver *presolver;
132
    Objective *obj;
133
    ColTag *col_tags;
134
    Bound *bounds;
135
    Work *work;
136
} clean_up_scope;
137

138
void presolver_clean_up(clean_up_scope scope)
×
139
{
140
    free_matrix(scope.A);
×
141
    free_matrix(scope.AT);
×
142
    PS_FREE(scope.lhs_copy);
×
143
    PS_FREE(scope.rhs_copy);
×
144
    PS_FREE(scope.c_copy);
×
145
    PS_FREE(scope.row_sizes);
×
146
    PS_FREE(scope.col_sizes);
×
147
    PS_FREE(scope.row_tags);
×
148
    PS_FREE(scope.col_tags);
×
149
    PS_FREE(scope.bounds);
×
150
    free_activities(scope.activities);
×
151
    free_locks(scope.locks);
×
152
    free_work(scope.work);
×
153
    free_state(scope.data);
×
154
    free_constraints(scope.constraints);
×
155
    free_objective(scope.obj);
×
156
    free_presolver(scope.presolver);
×
157
}
×
158

159
Presolver *new_presolver(const double *Ax, const int *Ai, const int *Ap, int m,
87✔
160
                         int n, int nnz, const double *lhs, const double *rhs,
161
                         const double *lbs, const double *ubs, const double *c,
162
                         const Settings *stgs, bool CSR)
163
{
164
    Timer timer;
165
    clock_gettime(CLOCK_MONOTONIC, &timer.start);
87✔
166
    Matrix *A = NULL, *AT = NULL;
87✔
167
    double *lhs_copy = NULL, *rhs_copy = NULL, *c_copy = NULL;
87✔
168
    int *row_sizes = NULL, *col_sizes = NULL;
87✔
169
    RowTag *row_tags = NULL;
87✔
170
    Lock *locks = NULL;
87✔
171
    Activity *activities = NULL;
87✔
172
    State *data = NULL;
87✔
173
    Constraints *constraints = NULL;
87✔
174
    Presolver *presolver = NULL;
87✔
175
    Objective *obj = NULL;
87✔
176
    ColTag *col_tags = NULL;
87✔
177
    Bound *bounds = NULL;
87✔
178
    Work *work = NULL;
87✔
179

180
    //  ---------------------------------------------------------------------------
181
    //   Copy data and allocate memory. For an exact specification of the
182
    //   workspace, see the workspace class. The problem object owns all the
183
    //   memory that is allocated in this block of code (and therefore frees it).
184
    //  ---------------------------------------------------------------------------
185
    int n_rows = m;
87✔
186
    int n_cols = n;
87✔
187
    lhs_copy = (double *) ps_malloc(n_rows, sizeof(double));
87✔
188
    rhs_copy = (double *) ps_malloc(n_rows, sizeof(double));
87✔
189
    c_copy = (double *) ps_malloc(n_cols, sizeof(double));
87✔
190
    col_tags = (ColTag *) ps_calloc(n_cols, sizeof(ColTag));
87✔
191
    bounds = (Bound *) ps_malloc(n_cols, sizeof(Bound));
87✔
192
    work = new_work(n_rows, n_cols);
87✔
193
    row_sizes = (int *) ps_malloc(n_rows, sizeof(int));
87✔
194
    col_sizes = (int *) ps_malloc(n_cols, sizeof(int));
87✔
195

196
    if (!lhs_copy || !rhs_copy || !c_copy || !col_tags || !bounds || !work ||
87✔
197
        !row_sizes || !col_sizes)
87✔
198
    {
199
        goto cleanup;
×
200
    }
201

202
    memcpy(lhs_copy, lhs, n_rows * sizeof(double));
87✔
203
    memcpy(rhs_copy, rhs, n_rows * sizeof(double));
87✔
204
    memcpy(c_copy, c, n_cols * sizeof(double));
87✔
205

206
    // ---------------------------------------------------------------------------
207
    //                  Build bounds, row tags, A and AT.
208
    // ---------------------------------------------------------------------------
209
    row_tags = new_rowtags(lhs_copy, rhs_copy, n_rows);
87✔
210
    if (!row_tags) goto cleanup;
87✔
211

212
    for (int i = 0; i < n_cols; i++)
520✔
213
    {
214
        bounds[i].lb = lbs[i];
433✔
215
        bounds[i].ub = ubs[i];
433✔
216

217
        if (IS_NEG_INF(lbs[i]))
433✔
218
        {
219
            UPDATE_TAG(col_tags[i], C_TAG_LB_INF);
43✔
220
        }
221

222
        if (IS_POS_INF(ubs[i]))
433✔
223
        {
224
            UPDATE_TAG(col_tags[i], C_TAG_UB_INF);
270✔
225
        }
226
    }
227

228
    if (CSR)
87✔
229
    {
230
        A = matrix_new_no_extra_space(Ax, Ai, Ap, n_rows, n_cols, nnz);
87✔
231
        if (!A) goto cleanup;
87✔
232

233
        if (stgs->clean_small_coeff)
87✔
234
        {
235
            clean_small_coeff_A(A, bounds, row_tags, col_tags, rhs_copy, lhs_copy);
16✔
236
        }
237

238
        AT = transpose(A, work->iwork_n_cols);
87✔
239
        if (!AT) goto cleanup;
87✔
240
    }
241
    else
242
    {
243
        AT = matrix_new(Ax, Ai, Ap, n_cols, n_rows, nnz);
×
244
        if (!AT) goto cleanup;
×
245
        A = transpose(AT, work->iwork_n_rows);
×
246
        if (!A) goto cleanup;
×
247
    }
248

249
    // ---------------------------------------------------------------------------
250
    //           locks, activities, row sizes and column sizes
251
    // ---------------------------------------------------------------------------
252
    locks = new_locks(A, row_tags);
87✔
253
    activities = new_activities(A, col_tags, bounds);
87✔
254
    if (!locks || !activities) goto cleanup;
87✔
255
    count_rows(A, row_sizes);
87✔
256
    count_rows(AT, col_sizes);
87✔
257

258
    // ---------------------------------------------------------------------------
259
    //  Initialize internal data and constraints
260
    // ---------------------------------------------------------------------------
261
    data = new_state(row_sizes, col_sizes, locks, n_rows, n_cols, activities, work,
87✔
262
                     row_tags);
263

264
    if (!data) goto cleanup;
87✔
265
    constraints =
266
        constraints_new(A, AT, lhs_copy, rhs_copy, bounds, data, row_tags, col_tags);
87✔
267
    if (!constraints) goto cleanup;
87✔
268

269
    // ---------------------------------------------------------------------------
270
    //             Allocate the actual presolver
271
    // ---------------------------------------------------------------------------
272
    presolver = (Presolver *) ps_malloc(1, sizeof(Presolver));
87✔
273
    obj = objective_new(c_copy);
87✔
274
    if (!presolver || !obj) goto cleanup;
87✔
275
    presolver->stgs = stgs;
87✔
276
    presolver->prob = new_problem(constraints, obj);
87✔
277
    presolver->stats = init_stats(A->m, A->n, nnz);
87✔
278
    presolver->stats->nnz_removed_trivial = nnz - A->nnz; // due to clean_small_coeff
87✔
279
    clock_gettime(CLOCK_MONOTONIC, &timer.end);
87✔
280
    presolver->stats->ps_time_init = GET_ELAPSED_SECONDS(timer);
87✔
281
    presolver->reduced_prob =
87✔
282
        (PresolvedProblem *) ps_calloc(1, sizeof(PresolvedProblem));
87✔
283
    DEBUG(run_debugger(constraints, false));
87✔
284

285
    // ---------------------------------------------------------------------------
286
    //           Allocate space for returning the solution
287
    // ---------------------------------------------------------------------------
288
    presolver->sol = (Solution *) ps_malloc(1, sizeof(Solution));
87✔
289
    if (!presolver->sol) goto cleanup;
87✔
290
    presolver->sol->x = (double *) ps_malloc(n_cols, sizeof(double));
87✔
291
    presolver->sol->y = (double *) ps_malloc(n_rows, sizeof(double));
87✔
292
    presolver->sol->z = (double *) ps_malloc(n_cols, sizeof(double));
87✔
293
    presolver->sol->dim_x = n_cols;
87✔
294
    presolver->sol->dim_y = n_rows;
87✔
295
    if (!presolver->sol->x || !presolver->sol->y || !presolver->sol->z)
87✔
296
    {
297
        goto cleanup;
×
298
    }
299

300
    return presolver;
87✔
301

302
cleanup:
×
303
{
304
    struct clean_up_scope scope = {
×
305
        A,         AT,       lhs_copy, rhs_copy,   c_copy, row_sizes,
306
        col_sizes, row_tags, locks,    activities, data,   constraints,
307
        presolver, obj,      col_tags, bounds,     work};
308
    presolver_clean_up(scope);
×
309
}
310
    return NULL;
×
311
}
312

313
/* This function updates the termination criterion for the presolver.
314
   We terminate if one of the following conditions is satisfied:
315
   1. The problem is infeasible or unbounded.
316
   2. A cycle reduced the number of non-zeros by less than
317
     (1 - NNZ_REMOVED_CYCLE)%.
318
   3. A maximum time limit is reached.
319
*/
320
static inline bool update_termination(int nnz_after_cycle, int nnz_before_cycle,
76✔
321
                                      Complexity complexity, PresolveStatus status,
322
                                      double max_time, Timer outer_timer)
323
{
324
    if (HAS_STATUS(status, UNBNDORINFEAS))
76✔
325
    {
326
        return true;
×
327
    }
328

329
    if (complexity == MEDIUM &&
76✔
330
        nnz_after_cycle >= NNZ_REMOVED_CYCLE * nnz_before_cycle)
34✔
331
    {
332
        return true;
17✔
333
    }
334

335
    clock_gettime(CLOCK_MONOTONIC, &outer_timer.end);
59✔
336

337
    if (GET_ELAPSED_SECONDS(outer_timer) >= max_time)
59✔
338
    {
NEW
339
        printf("Maximum time limit of %.2f seconds reached.\n", max_time);
×
UNCOV
340
        return true;
×
341
    }
342

343
    return false;
59✔
344
}
345

346
/* This function updates the complexity class according to
347
   the following rules:
348
    * If the current complexity is fast, the next complexity is
349
      fast if sufficiently many non-zeros were removed, otherwise
350
      it is medium.
351
    * If the current complexity is medium, the next complexity is
352
      fast.
353
*/
354
static inline Complexity update_complexity(Complexity curr_complexity,
76✔
355
                                           int nnz_before_phase, int nnz_after_phase)
356
{
357
    if (curr_complexity == FAST)
76✔
358
    {
359
        if (nnz_after_phase < NNZ_REMOVED_FAST * nnz_before_phase)
42✔
360
        {
361
            return FAST;
8✔
362
        }
363

364
        return MEDIUM;
34✔
365
    }
366
    else if (curr_complexity == MEDIUM)
34✔
367
    {
368
        return FAST;
34✔
369
    }
370
    else
371
    {
372
        assert(false);
×
373
    }
374

375
    return FAST; // to suppress compiler warning
376
}
377

378
/* This function exhaustively removes singleton rows from the problem. It
379
   also eliminates empty rows and empty columns. If the problem is feasible
380
   and bounded, it returns UNCHANGED (even if the problem was reduced).
381
*/
382
static inline PresolveStatus run_trivial_explorers(Problem *prob,
179✔
383
                                                   const Settings *stgs)
384
{
385
    PresolveStatus status = UNCHANGED;
179✔
386

387
    remove_variables_with_close_bounds(prob);
179✔
388

389
    if (stgs->dual_fix)
179✔
390
    {
391
        //   after simple dual fix there can be new empty rows and singleton rows,
392
        //   and empty columns (if rows are marked as inactive due to a variable
393
        //   being set to inf)
394
        status |= remove_empty_cols(prob);
127✔
395
        status |= simple_dual_fix(prob);
127✔
396
        RETURN_IF_UNBNDORINFEAS(status);
127✔
397
    }
398

399
    do
400
    {
401
        status = remove_ston_rows(prob);
189✔
402
        RETURN_IF_INFEASIBLE(status);
189✔
403
    } while (status == REDUCED);
189✔
404

405
    status = remove_empty_rows(prob->constraints);
179✔
406
    RETURN_IF_INFEASIBLE(status);
179✔
407

408
    status = remove_empty_cols(prob);
179✔
409
    RETURN_IF_UNBNDORINFEAS(status);
179✔
410

411
    assert(prob->constraints->state->ston_rows->len == 0);
179✔
412
    assert(prob->constraints->state->empty_rows->len == 0);
179✔
413
    assert(prob->constraints->state->empty_cols->len == 0);
179✔
414
    return UNCHANGED;
179✔
415
}
416

417
/* This function applies the fastest reductions, which are doubleton equality
418
   rows, singleton columns, and simple dual fix. It returns UNCHANGED even if
419
   the problem was reduced (provided that the problem does not seem infeasible or
420
   bounded). */
421
static inline PresolveStatus run_fast_explorers(Problem *prob, const Settings *stgs)
42✔
422
{
423
    assert(prob->constraints->state->ston_rows->len == 0);
42✔
424
    assert(prob->constraints->state->empty_rows->len == 0);
42✔
425
    assert(prob->constraints->state->empty_cols->len == 0);
42✔
426
    PresolveStatus status = UNCHANGED;
42✔
427

428
    if (stgs->ston_cols)
42✔
429
    {
430
        status |= remove_ston_cols(prob);
39✔
431

432
        // after removing singleton columns, there can be new empty columns
433
        // and singleton rows, but no empty rows
434
        assert(prob->constraints->state->empty_rows->len == 0);
39✔
435
        status |= run_trivial_explorers(prob, stgs);
39✔
436
    }
437

438
    if (stgs->dton_eq)
42✔
439
    {
440
        status |= remove_dton_eq_rows(prob, stgs->max_shift);
42✔
441
        // after removing doubleton equality rows, there can be new empty rows,
442
        // new singleton rows, and new empty columns
443
        status |= run_trivial_explorers(prob, stgs);
42✔
444
    }
445
    return status;
42✔
446
}
447

448
/* This function applies the medium complexity presolvers, which are domain
449
   propagation and parallel rows. It returns UNCHANGED even if the problem
450
   was reduced (provided that the problem is infeasible and bounded). */
451
static inline PresolveStatus
452
run_medium_explorers(Problem *prob, const Settings *stgs, PresolveStats *stats)
34✔
453
{
454
    assert(prob->constraints->state->ston_rows->len == 0);
34✔
455
    assert(prob->constraints->state->empty_rows->len == 0);
34✔
456
    assert(prob->constraints->state->empty_cols->len == 0);
34✔
457
    DEBUG(verify_no_duplicates_sort(prob->constraints->state->updated_activities));
34✔
458
    int nnz_before;
459
    int *nnz = &prob->constraints->A->nnz;
34✔
460
    PresolveStatus status = UNCHANGED;
34✔
461
    Timer timer;
462

463
    if (stgs->primal_propagation)
34✔
464
    {
465
        // stats
466
        clock_gettime(CLOCK_MONOTONIC, &timer.start);
12✔
467
        nnz_before = *nnz;
12✔
468

469
        // the actual reduction
470
        status |= check_activities(prob);
12✔
471
        status |= propagate_primal(prob, stgs->finite_bound_tightening);
12✔
472

473
        // after dom prop propagation there can be new empty and ston rows rows
474
        status |= run_trivial_explorers(prob, stgs);
12✔
475

476
        // stats
477
        stats->nnz_removed_primal_propagation += nnz_before - *nnz;
12✔
478
        clock_gettime(CLOCK_MONOTONIC, &timer.end);
12✔
479
        stats->ps_time_primal_propagation += GET_ELAPSED_SECONDS(timer);
12✔
480
    }
481

482
    if (stgs->parallel_rows)
34✔
483
    {
484
        // stats
485
        nnz_before = *nnz;
34✔
486
        clock_gettime(CLOCK_MONOTONIC, &timer.start);
34✔
487

488
        // the actual reduction (after removing parallel rows there can
489
        // be no new empty or ston rows so no need to run trivial presolvers)
490
        status |= remove_parallel_rows(prob->constraints);
34✔
491
        assert(prob->constraints->state->empty_rows->len == 0);
34✔
492
        assert(prob->constraints->state->ston_rows->len == 0);
34✔
493

494
        // stats
495
        stats->nnz_removed_parallel_rows += nnz_before - *nnz;
34✔
496
        clock_gettime(CLOCK_MONOTONIC, &timer.end);
34✔
497
        stats->ps_time_parallel_rows += GET_ELAPSED_SECONDS(timer);
34✔
498
    }
499

500
    if (stgs->parallel_cols)
34✔
501
    {
502
        // stats
503
        nnz_before = *nnz;
10✔
504
        clock_gettime(CLOCK_MONOTONIC, &timer.start);
10✔
505

506
        // the actual reduction (after removing parallel columns there can
507
        // be no new empty rows or empty cols but might be new stonrows, probably
508
        // although that's rare but it does happen
509
        status |= remove_parallel_cols(prob);
10✔
510
        assert(prob->constraints->state->empty_rows->len == 0);
10✔
511
        assert(prob->constraints->state->empty_cols->len == 0);
10✔
512
        status |= run_trivial_explorers(prob, stgs);
10✔
513
        assert(prob->constraints->state->ston_rows->len == 0);
10✔
514

515
        // stats
516
        stats->nnz_removed_parallel_cols += nnz_before - *nnz;
10✔
517
        clock_gettime(CLOCK_MONOTONIC, &timer.end);
10✔
518
        stats->ps_time_parallel_cols += GET_ELAPSED_SECONDS(timer);
10✔
519
    }
520

521
    return status;
34✔
522
}
523

524
void populate_presolved_problem(Presolver *presolver)
17✔
525
{
526
    PresolvedProblem *ps_prob = presolver->reduced_prob;
17✔
527
    Constraints *constraints = presolver->prob->constraints;
17✔
528
    Matrix *A = constraints->A;
17✔
529
    ps_prob->m = A->m;
17✔
530
    ps_prob->n = A->n;
17✔
531
    ps_prob->nnz = A->nnz;
17✔
532
    ps_prob->Ax = A->x;
17✔
533
    ps_prob->Ai = A->i;
17✔
534
    ps_prob->rhs = constraints->rhs;
17✔
535
    ps_prob->lhs = constraints->lhs;
17✔
536
    ps_prob->c = presolver->prob->obj->c;
17✔
537

538
    // create bounds arrays
539
    ps_prob->lbs = (double *) malloc(A->n * sizeof(double));
17✔
540
    ps_prob->ubs = (double *) malloc(A->n * sizeof(double));
17✔
541
    for (int i = 0; i < A->n; i++)
113✔
542
    {
543
        ps_prob->lbs[i] = constraints->bounds[i].lb;
96✔
544
        ps_prob->ubs[i] = constraints->bounds[i].ub;
96✔
545
    }
546

547
    // create row pointers
548
    ps_prob->Ap = (int *) malloc((A->m + 1) * sizeof(int));
17✔
549
    for (int i = 0; i < A->m + 1; i++)
95✔
550
    {
551
        ps_prob->Ap[i] = A->p[i].start;
78✔
552
    }
553
}
17✔
554

555
static inline void print_start_message(const PresolveStats *stats)
17✔
556
{
557
    printf("\n\t       PSLP v%s - LP presolver \n\t(c) Daniel "
17✔
558
           "Cederberg, Stanford University, 2025\n",
559
           PSLP_presolve_VERSION);
560
    printf("Original problem:  %d rows, %d columns, %d nnz\n",
17✔
561
           stats->n_rows_original, stats->n_cols_original, stats->nnz_original);
17✔
562
}
17✔
563

564
static inline void print_end_message(const Matrix *A, const PresolveStats *stats)
17✔
565
{
566
    printf("Presolved problem: %d rows, %d columns, %d nnz\n", A->m, A->n, A->nnz);
17✔
567

568
    printf("Presolver init & run time : %.3f seconds, %.3f \n", stats->ps_time_init,
17✔
569
           stats->presolve_total_time);
17✔
570
}
17✔
571

572
PresolveStatus run_presolver(Presolver *presolver)
17✔
573
{
574
    Timer inner_timer, outer_timer;
575
    int nnz_before_cycle, nnz_after_cycle;
576
    int nnz_before_phase, nnz_after_phase;
577
    int nnz_before_reduction;
578
    Problem *prob = presolver->prob;
17✔
579
    PresolveStats *stats = presolver->stats;
17✔
580
    Matrix *A = presolver->prob->constraints->A;
17✔
581
    const Settings *stgs = presolver->stgs;
17✔
582
    Complexity curr_complexity = FAST;
17✔
583
    bool terminate = false;
17✔
584
    PresolveStatus status = UNCHANGED;
17✔
585
    clock_gettime(CLOCK_MONOTONIC, &outer_timer.start);
17✔
586

587
    if (stgs->verbose)
17✔
588
    {
589
        print_start_message(stats);
17✔
590
    }
591

592
    DEBUG(run_debugger(prob->constraints, false));
17✔
593

594
    // ------------------------------------------------------------------------
595
    // Main loop for the presolver. The logic is organized into phases and
596
    // cycles. A phase is a sequence of presolvers belonging to a certain
597
    // complexity class. The cycle resets after the medium presolvers.
598
    // ------------------------------------------------------------------------
599
    nnz_before_cycle = A->nnz;
17✔
600
    while (!terminate)
93✔
601
    {
602
        // before each phase we run the trivial presolvers
603
        nnz_before_reduction = A->nnz;
76✔
604
        status = run_trivial_explorers(prob, stgs);
76✔
605
        stats->nnz_removed_trivial += nnz_before_reduction - A->nnz;
76✔
606
        RETURN_IF_NOT_UNCHANGED(status); // TODO: this name is misleading!
76✔
607
        DEBUG(run_debugger(prob->constraints, false));
76✔
608
        nnz_before_phase = A->nnz;
76✔
609

610
        // apply presolvers belonging to a certain complexity class
611
        if (curr_complexity == FAST)
76✔
612
        {
613
            nnz_before_reduction = A->nnz;
42✔
614
            RUN_AND_TIME(run_fast_explorers, inner_timer, stats->ps_time_fast,
42✔
615
                         status, prob, stgs);
616
            stats->nnz_removed_fast += nnz_before_reduction - A->nnz;
42✔
617
        }
618
        else if (curr_complexity == MEDIUM)
34✔
619
        {
620
            RUN_AND_TIME(run_medium_explorers, inner_timer, stats->ps_time_medium,
34✔
621
                         status, prob, stgs, stats);
622
            nnz_after_cycle = A->nnz;
34✔
623
        }
624

625
        nnz_after_phase = A->nnz;
76✔
626

627
        terminate =
628
            update_termination(nnz_after_cycle, nnz_before_cycle, curr_complexity,
76✔
629
                               status, stgs->max_time, outer_timer);
76✔
630

631
        if (curr_complexity == MEDIUM)
76✔
632
        {
633
            // a new cycle starts after the medium presolvers
634
            nnz_before_cycle = nnz_after_cycle;
34✔
635
        }
636

637
        curr_complexity =
638
            update_complexity(curr_complexity, nnz_before_phase, nnz_after_phase);
76✔
639
    }
640

641
    if (stgs->relax_bounds)
17✔
642
    {
643
        remove_redundant_bounds(prob->constraints);
17✔
644
    }
645

646
    problem_clean(prob, true);
17✔
647
    DEBUG(run_debugger(prob->constraints, true));
17✔
648

649
    clock_gettime(CLOCK_MONOTONIC, &outer_timer.end);
17✔
650
    stats->n_rows_reduced = A->m;
17✔
651
    stats->n_cols_reduced = A->n;
17✔
652
    stats->nnz_reduced = A->nnz;
17✔
653
    stats->presolve_total_time = GET_ELAPSED_SECONDS(outer_timer);
17✔
654
    DEBUG(run_debugger_stats_consistency_check(stats));
17✔
655
    populate_presolved_problem(presolver);
17✔
656

657
    if (stgs->verbose)
17✔
658
    {
659
        print_end_message(A, stats);
17✔
660
    }
661

662
    return status;
17✔
663
}
664

665
void postsolve(Presolver *presolver, const double *x, const double *y,
16✔
666
               const double *z, double obj)
667
{
668
    Timer timer;
669
    Solution *sol = presolver->sol;
16✔
670
    PresolveStats *stats = presolver->stats;
16✔
671
    State *data = presolver->prob->constraints->state;
16✔
672
    PostsolveInfo *postsolve_info = data->postsolve_info;
16✔
673
    clock_gettime(CLOCK_MONOTONIC, &timer.start);
16✔
674
    postsolver_update(postsolve_info, stats->n_cols_reduced, stats->n_rows_reduced,
16✔
675
                      data->work->mappings->cols, data->work->mappings->rows);
16✔
676
    postsolver_run(postsolve_info, sol, x, y, z);
16✔
677
    sol->obj = obj + presolver->prob->obj->offset;
16✔
678
    clock_gettime(CLOCK_MONOTONIC, &timer.end);
16✔
679
    stats->ps_time_post_solve = GET_ELAPSED_SECONDS(timer);
16✔
680

681
    if (presolver->stgs->verbose)
16✔
682
    {
683
        printf("Postsolve time: %.4f seconds\n", stats->ps_time_post_solve);
16✔
684
    }
685
}
16✔
686

687
void free_presolver(Presolver *presolver)
87✔
688
{
689
    if (presolver == NULL)
87✔
690
    {
691
        return;
×
692
    }
693

694
    free_problem(presolver->prob);
87✔
695
    PS_FREE(presolver->stats);
87✔
696

697
    if (presolver->reduced_prob)
87✔
698
    {
699
        PS_FREE(presolver->reduced_prob->Ap);
87✔
700
        PS_FREE(presolver->reduced_prob->lbs);
87✔
701
        PS_FREE(presolver->reduced_prob->ubs);
87✔
702
        PS_FREE(presolver->reduced_prob);
87✔
703
    }
704

705
    if (presolver->sol)
87✔
706
    {
707
        PS_FREE(presolver->sol->x);
87✔
708
        PS_FREE(presolver->sol->y);
87✔
709
        PS_FREE(presolver->sol->z);
87✔
710
        PS_FREE(presolver->sol);
87✔
711
    }
712

713
    PS_FREE(presolver);
87✔
714
}
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