• 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

92.24
/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 "PSLP_sol.h"
30
#include "PSLP_stats.h"
31
#include "PSLP_status.h"
32
#include "Parallel_cols.h"
33
#include "Parallel_rows.h"
34
#include "Postsolver.h"
35
#include "Primal_propagation.h"
36
#include "Problem.h"
37
#include "RowColViews.h"
38
#include "SimpleReductions.h"
39
#include "Simple_dual_fix.h"
40
#include "State.h"
41
#include "StonCols.h"
42
#include "Tags.h"
43
#include "Timer.h"
44
#include "Workspace.h"
45
#include "dVec.h"
46
#include "glbopts.h"
47
#include "iVec.h"
48
#include <pthread.h>
49
#include <stdio.h>
50
#include <stdlib.h>
51

52
#define NNZ_REMOVED_FAST 0.95
53
#define NNZ_REMOVED_CYCLE 0.95
54

55
typedef uint8_t Complexity;
56

57
enum Complexity
58
{
59
    FAST = 0,
60
    MEDIUM = 1 << 0
61
};
62

63
PresolveStats *init_stats(int n_rows, int n_cols, int nnz)
98✔
64
{
65
    PresolveStats *stats = (PresolveStats *) ps_calloc(1, sizeof(PresolveStats));
98✔
66
    stats->n_rows_original = n_rows;
98✔
67
    stats->n_cols_original = n_cols;
98✔
68
    stats->nnz_original = nnz;
98✔
69
    return stats;
98✔
70
}
71

72
void free_settings(Settings *stgs)
8✔
73
{
74
    PS_FREE(stgs);
8✔
75
}
8✔
76

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

96
// Helper struct for parallel initialization work
97
typedef struct
98
{
99
    Matrix *A;
100
    Work *work;
101
    int n_cols;
102
    int n_rows;
103
    const double *lbs;
104
    const double *ubs;
105
    double *lhs_copy;
106
    double *rhs_copy;
107
    Bound *bounds;
108
    ColTag *col_tags;
109
    RowTag *row_tags;
110
    Lock *locks;
111
    Activity *activities;
112
    int *row_sizes;
113
} ParallelInitData;
114

115
// thread function for initializing data
116
static void *init_thread_func(void *arg)
98✔
117
{
118
    ParallelInitData *data = (ParallelInitData *) arg;
98✔
119

120
    data->row_tags = new_rowtags(data->lhs_copy, data->rhs_copy, data->n_rows);
98✔
121

122
    for (int i = 0; i < data->n_cols; i++)
567✔
123
    {
124
        data->bounds[i].lb = data->lbs[i];
469✔
125
        data->bounds[i].ub = data->ubs[i];
469✔
126

127
        if (IS_NEG_INF(data->lbs[i]))
469✔
128
        {
129
            UPDATE_TAG(data->col_tags[i], C_TAG_LB_INF);
49✔
130
        }
131

132
        if (IS_POS_INF(data->ubs[i]))
469✔
133
        {
134

135
            UPDATE_TAG(data->col_tags[i], C_TAG_UB_INF);
282✔
136
        }
137
    }
138

139
    data->locks = new_locks(data->A, data->row_tags);
98✔
140
    data->activities = new_activities(data->A, data->col_tags, data->bounds);
98✔
141
    count_rows(data->A, data->row_sizes);
98✔
142

143
    return NULL;
98✔
144
}
145

146
void set_settings_true(Settings *stgs)
25✔
147
{
148
    stgs->ston_cols = true;
25✔
149
    stgs->dton_eq = true;
25✔
150
    stgs->parallel_rows = true;
25✔
151
    stgs->parallel_cols = true;
25✔
152
    stgs->primal_propagation = true;
25✔
153
    stgs->dual_fix = true;
25✔
154
    // stgs->clean_small_coeff = true;
155
    stgs->finite_bound_tightening = true;
25✔
156
    stgs->relax_bounds = true;
25✔
157
    stgs->verbose = true;
25✔
158
}
25✔
159

160
void set_settings_false(Settings *stgs)
17✔
161
{
162
    stgs->ston_cols = false;
17✔
163
    stgs->dton_eq = false;
17✔
164
    stgs->parallel_rows = false;
17✔
165
    stgs->parallel_cols = false;
17✔
166
    stgs->primal_propagation = false;
17✔
167
    stgs->dual_fix = false;
17✔
168
    // stgs->clean_small_coeff = false;
169
    stgs->finite_bound_tightening = false;
17✔
170
    stgs->relax_bounds = false;
17✔
171
    stgs->verbose = false;
17✔
172
}
17✔
173

174
typedef struct clean_up_scope
175
{
176
    Matrix *A, *AT;
177
    double *lhs_copy, *rhs_copy, *c_copy;
178
    int *row_sizes, *col_sizes;
179
    RowTag *row_tags;
180
    Lock *locks;
181
    Activity *activities;
182
    State *data;
183
    Constraints *constraints;
184
    Presolver *presolver;
185
    Objective *obj;
186
    ColTag *col_tags;
187
    Bound *bounds;
188
    Work *work;
189
} clean_up_scope;
190

191
void presolver_clean_up(clean_up_scope scope)
×
192
{
193
    free_matrix(scope.A);
×
194
    free_matrix(scope.AT);
×
195
    PS_FREE(scope.lhs_copy);
×
196
    PS_FREE(scope.rhs_copy);
×
197
    PS_FREE(scope.c_copy);
×
198
    PS_FREE(scope.row_sizes);
×
199
    PS_FREE(scope.col_sizes);
×
200
    PS_FREE(scope.row_tags);
×
201
    PS_FREE(scope.col_tags);
×
202
    PS_FREE(scope.bounds);
×
203
    free_activities(scope.activities);
×
204
    free_locks(scope.locks);
×
205
    free_work(scope.work);
×
206
    free_state(scope.data);
×
207
    free_constraints(scope.constraints);
×
208
    free_objective(scope.obj);
×
209
    free_presolver(scope.presolver);
×
210
}
×
211

212
Presolver *new_presolver(const double *Ax, const int *Ai, const int *Ap, int m,
98✔
213
                         int n, int nnz, const double *lhs, const double *rhs,
214
                         const double *lbs, const double *ubs, const double *c,
215
                         const Settings *stgs)
216
{
217
    Timer timer;
218
    clock_gettime(CLOCK_MONOTONIC, &timer.start);
98✔
219
    Matrix *A = NULL, *AT = NULL;
98✔
220
    double *lhs_copy = NULL, *rhs_copy = NULL, *c_copy = NULL;
98✔
221
    int *row_sizes = NULL, *col_sizes = NULL;
98✔
222
    RowTag *row_tags = NULL;
98✔
223
    Lock *locks = NULL;
98✔
224
    Activity *activities = NULL;
98✔
225
    State *data = NULL;
98✔
226
    Constraints *constraints = NULL;
98✔
227
    Presolver *presolver = NULL;
98✔
228
    Objective *obj = NULL;
98✔
229
    ColTag *col_tags = NULL;
98✔
230
    Bound *bounds = NULL;
98✔
231
    Work *work = NULL;
98✔
232

233
    //  ---------------------------------------------------------------------------
234
    //   Copy data and allocate memory. For an exact specification of the
235
    //   workspace, see the workspace class. The problem object owns all the
236
    //   memory that is allocated in this block of code (and therefore frees it).
237
    //  ---------------------------------------------------------------------------
238
    int n_rows = m;
98✔
239
    int n_cols = n;
98✔
240
    lhs_copy = (double *) ps_malloc(n_rows, sizeof(double));
98✔
241
    rhs_copy = (double *) ps_malloc(n_rows, sizeof(double));
98✔
242
    c_copy = (double *) ps_malloc(n_cols, sizeof(double));
98✔
243
    col_tags = (ColTag *) ps_calloc(n_cols, sizeof(ColTag));
98✔
244
    bounds = (Bound *) ps_malloc(n_cols, sizeof(Bound));
98✔
245
    work = new_work(n_rows, n_cols);
98✔
246
    row_sizes = (int *) ps_malloc(n_rows, sizeof(int));
98✔
247
    col_sizes = (int *) ps_malloc(n_cols, sizeof(int));
98✔
248

249
    if (!lhs_copy || !rhs_copy || !c_copy || !col_tags || !bounds || !work ||
98✔
250
        !row_sizes || !col_sizes)
98✔
251
    {
252
        goto cleanup;
×
253
    }
254

255
    memcpy(lhs_copy, lhs, n_rows * sizeof(double));
98✔
256
    memcpy(rhs_copy, rhs, n_rows * sizeof(double));
98✔
257
    memcpy(c_copy, c, n_cols * sizeof(double));
98✔
258

259
    // ---------------------------------------------------------------------------
260
    //  Build bounds, row tags, A and AT. We first build A, then in parallel
261
    //  we build AT (main thread) and some other things (second thread).
262
    // ---------------------------------------------------------------------------
263
    A = matrix_new_no_extra_space(Ax, Ai, Ap, n_rows, n_cols, nnz);
98✔
264
    if (!A) goto cleanup;
98✔
265

266
    // commented out for now because does not seem helpful
267
    // if (stgs->clean_small_coeff)
268
    // {
269
    //     clean_small_coeff_A(A, bounds, row_tags, col_tags, rhs_copy, lhs_copy);
270
    // }
271

272
    pthread_t thread_id;
273
    ParallelInitData parallel_data = {A,    work,     n_cols,   n_rows,   lbs,
98✔
274
                                      ubs,  lhs_copy, rhs_copy, bounds,   col_tags,
275
                                      NULL, NULL,     NULL,     row_sizes};
276

277
    pthread_create(&thread_id, NULL, init_thread_func, &parallel_data);
98✔
278

279
    // Main thread: Transpose A and count rows
280
    AT = transpose(A, work->iwork_n_cols);
98✔
281
    if (!AT)
98✔
282
    {
283
        pthread_cancel(thread_id);
×
284
        goto cleanup;
×
285
    }
286
    count_rows(AT, col_sizes);
98✔
287

288
    // sync threads
289
    pthread_join(thread_id, NULL);
98✔
290

291
    row_tags = parallel_data.row_tags;
98✔
292
    if (!row_tags) goto cleanup;
98✔
293
    locks = parallel_data.locks;
98✔
294
    activities = parallel_data.activities;
98✔
295
    if (!locks || !activities) goto cleanup;
98✔
296

297
    // ---------------------------------------------------------------------------
298
    //  Initialize internal data and constraints
299
    // ---------------------------------------------------------------------------
300
    data = new_state(row_sizes, col_sizes, locks, n_rows, n_cols, activities, work,
98✔
301
                     row_tags);
302

303
    if (!data) goto cleanup;
98✔
304
    constraints =
305
        constraints_new(A, AT, lhs_copy, rhs_copy, bounds, data, row_tags, col_tags);
98✔
306
    if (!constraints) goto cleanup;
98✔
307

308
    // ---------------------------------------------------------------------------
309
    //             Allocate the actual presolver
310
    // ---------------------------------------------------------------------------
311
    presolver = (Presolver *) ps_malloc(1, sizeof(Presolver));
98✔
312
    obj = objective_new(c_copy);
98✔
313
    if (!presolver || !obj) goto cleanup;
98✔
314
    presolver->stgs = stgs;
98✔
315
    presolver->prob = new_problem(constraints, obj);
98✔
316
    presolver->stats = init_stats(A->m, A->n, nnz);
98✔
317
    // presolver->stats->nnz_removed_trivial = nnz - A->nnz; due to clean_small_coeff
318
    presolver->reduced_prob =
98✔
319
        (PresolvedProblem *) ps_calloc(1, sizeof(PresolvedProblem));
98✔
320
    DEBUG(run_debugger(constraints, false));
98✔
321

322
    // ---------------------------------------------------------------------------
323
    //           Allocate space for returning the solution
324
    // ---------------------------------------------------------------------------
325
    presolver->sol = (Solution *) ps_malloc(1, sizeof(Solution));
98✔
326
    if (!presolver->sol) goto cleanup;
98✔
327
    presolver->sol->x = (double *) ps_malloc(n_cols, sizeof(double));
98✔
328
    presolver->sol->y = (double *) ps_malloc(n_rows, sizeof(double));
98✔
329
    presolver->sol->z = (double *) ps_malloc(n_cols, sizeof(double));
98✔
330
    presolver->sol->dim_x = n_cols;
98✔
331
    presolver->sol->dim_y = n_rows;
98✔
332
    if (!presolver->sol->x || !presolver->sol->y || !presolver->sol->z)
98✔
333
    {
334
        goto cleanup;
×
335
    }
336

337
    clock_gettime(CLOCK_MONOTONIC, &timer.end);
98✔
338
    presolver->stats->time_init = GET_ELAPSED_SECONDS(timer);
98✔
339

340
    return presolver;
98✔
341

342
cleanup:
×
343
{
344
    struct clean_up_scope scope = {
×
345
        A,         AT,       lhs_copy, rhs_copy,   c_copy, row_sizes,
346
        col_sizes, row_tags, locks,    activities, data,   constraints,
347
        presolver, obj,      col_tags, bounds,     work};
348
    presolver_clean_up(scope);
×
349
}
350
    return NULL;
×
351
}
352

353
/* This function updates the termination criterion for the presolver.
354
   We terminate if one of the following conditions is satisfied:
355
   1. The problem is infeasible or unbounded.
356
   2. A cycle reduced the number of non-zeros by less than
357
     (1 - NNZ_REMOVED_CYCLE)%.
358
   3. A maximum time limit is reached.
359
*/
360
static inline bool update_termination(int nnz_after_cycle, int nnz_before_cycle,
87✔
361
                                      Complexity complexity, PresolveStatus status,
362
                                      double max_time, Timer outer_timer)
363
{
364
    if (HAS_STATUS(status, UNBNDORINFEAS))
87✔
365
    {
366
        return true;
2✔
367
    }
368

369
    if (complexity == MEDIUM &&
85✔
370
        nnz_after_cycle >= NNZ_REMOVED_CYCLE * nnz_before_cycle)
38✔
371
    {
372
        return true;
20✔
373
    }
374

375
    clock_gettime(CLOCK_MONOTONIC, &outer_timer.end);
65✔
376

377
    if (GET_ELAPSED_SECONDS(outer_timer) >= max_time)
65✔
378
    {
379
        printf("Maximum time limit of %.2f seconds reached.\n", max_time);
×
380
        return true;
×
381
    }
382

383
    return false;
65✔
384
}
385

386
/* This function updates the complexity class according to
387
   the following rules:
388
    * If the current complexity is fast, the next complexity is
389
      fast if sufficiently many non-zeros were removed, otherwise
390
      it is medium.
391
    * If the current complexity is medium, the next complexity is
392
      fast.
393
*/
394
static inline Complexity update_complexity(Complexity curr_complexity,
87✔
395
                                           int nnz_before_phase, int nnz_after_phase)
396
{
397
    if (curr_complexity == FAST)
87✔
398
    {
399
        if (nnz_after_phase < NNZ_REMOVED_FAST * nnz_before_phase)
48✔
400
        {
401
            return FAST;
9✔
402
        }
403

404
        return MEDIUM;
39✔
405
    }
406
    else if (curr_complexity == MEDIUM)
39✔
407
    {
408
        return FAST;
39✔
409
    }
410

411
    // should never reach here
412
    assert(false);
×
413
    return FAST;
414
}
415

416
/* This function exhaustively removes singleton rows from the problem. It
417
   also eliminates empty rows and empty columns. If the problem is feasible
418
   and bounded, it returns UNCHANGED (even if the problem was reduced).
419
*/
420
static inline PresolveStatus run_trivial_explorers(Problem *prob,
211✔
421
                                                   const Settings *stgs)
422
{
423
    PresolveStatus status = UNCHANGED;
211✔
424

425
    // TODO: should we just run this once in the beginning of presolve?
426
    // Very important question.
427
    status = remove_variables_with_close_bounds(prob);
211✔
428
    RETURN_IF_INFEASIBLE(status);
211✔
429

430
    if (stgs->dual_fix)
209✔
431
    {
432
        //   after simple dual fix there can be new empty rows and singleton rows,
433
        //   and empty columns (if rows are marked as inactive due to a variable
434
        //   being set to inf)
435
        status |= remove_empty_cols(prob);
157✔
436
        status |= simple_dual_fix(prob);
157✔
437
        RETURN_IF_UNBNDORINFEAS(status);
157✔
438
    }
439

440
    do
441
    {
442
        status = remove_ston_rows(prob);
217✔
443
        RETURN_IF_INFEASIBLE(status);
217✔
444
    } while (status == REDUCED);
216✔
445

446
    status = remove_empty_rows(prob->constraints);
206✔
447
    RETURN_IF_INFEASIBLE(status);
206✔
448

449
    status = remove_empty_cols(prob);
206✔
450
    RETURN_IF_UNBNDORINFEAS(status);
206✔
451

452
    assert(prob->constraints->state->ston_rows->len == 0);
206✔
453
    assert(prob->constraints->state->empty_rows->len == 0);
206✔
454
    assert(prob->constraints->state->empty_cols->len == 0);
206✔
455
    return UNCHANGED;
206✔
456
}
457

458
/* This function applies the fastest reductions, which are doubleton equality
459
   rows, singleton columns, and simple dual fix. It returns UNCHANGED even if
460
   the problem was reduced (provided that the problem does not seem infeasible or
461
   bounded). */
462
static inline PresolveStatus run_fast_explorers(Problem *prob, const Settings *stgs)
48✔
463
{
464
    assert(prob->constraints->state->ston_rows->len == 0);
48✔
465
    assert(prob->constraints->state->empty_rows->len == 0);
48✔
466
    assert(prob->constraints->state->empty_cols->len == 0);
48✔
467
    PresolveStatus status = UNCHANGED;
48✔
468

469
    if (stgs->ston_cols)
48✔
470
    {
471
        status |= remove_ston_cols(prob);
45✔
472

473
        // after removing singleton columns, there can be new empty columns
474
        // and singleton rows, but no empty rows
475
        assert(prob->constraints->state->empty_rows->len == 0);
45✔
476
        status |= run_trivial_explorers(prob, stgs);
45✔
477
    }
478

479
    if (stgs->dton_eq)
48✔
480
    {
481
        status |= remove_dton_eq_rows(prob, stgs->max_shift);
48✔
482
        // after removing doubleton equality rows, there can be new empty rows,
483
        // new singleton rows, and new empty columns
484
        status |= run_trivial_explorers(prob, stgs);
48✔
485
    }
486
    return status;
48✔
487
}
488

489
/* This function applies the medium complexity presolvers, which are domain
490
   propagation and parallel rows. It returns UNCHANGED even if the problem
491
   was reduced (provided that the problem is infeasible and bounded). */
492
static inline PresolveStatus
493
run_medium_explorers(Problem *prob, const Settings *stgs, PresolveStats *stats)
39✔
494
{
495
    assert(prob->constraints->state->ston_rows->len == 0);
39✔
496
    assert(prob->constraints->state->empty_rows->len == 0);
39✔
497
    assert(prob->constraints->state->empty_cols->len == 0);
39✔
498
    DEBUG(verify_no_duplicates_sort(prob->constraints->state->updated_activities));
39✔
499
    int nnz_before;
500
    int *nnz = &prob->constraints->A->nnz;
39✔
501
    PresolveStatus status = UNCHANGED;
39✔
502
    Timer timer;
503

504
    if (stgs->primal_propagation)
39✔
505
    {
506
        // stats
507
        clock_gettime(CLOCK_MONOTONIC, &timer.start);
15✔
508
        nnz_before = *nnz;
15✔
509

510
        // the actual reduction
511
        status |= check_activities(prob);
15✔
512
        status |= propagate_primal(prob, stgs->finite_bound_tightening);
15✔
513

514
        // after dom prop propagation there can be new empty and ston rows
515
        status |= run_trivial_explorers(prob, stgs);
15✔
516
        assert(!(status & REDUCED));
15✔
517
        RETURN_IF_NOT_UNCHANGED(status);
15✔
518

519
        // stats
520
        stats->nnz_removed_primal_propagation += nnz_before - *nnz;
14✔
521
        clock_gettime(CLOCK_MONOTONIC, &timer.end);
14✔
522
        stats->time_primal_propagation += GET_ELAPSED_SECONDS(timer);
14✔
523
    }
524

525
    if (stgs->parallel_rows)
38✔
526
    {
527
        // stats
528
        nnz_before = *nnz;
38✔
529
        clock_gettime(CLOCK_MONOTONIC, &timer.start);
38✔
530

531
        // the actual reduction (after removing parallel rows there can
532
        // be no new empty or ston rows so no need to run trivial presolvers)
533
        status |= remove_parallel_rows(prob->constraints);
38✔
534
        assert(prob->constraints->state->empty_rows->len == 0);
38✔
535
        assert(prob->constraints->state->ston_rows->len == 0);
38✔
536
        assert(!(status & REDUCED));
38✔
537
        RETURN_IF_NOT_UNCHANGED(status);
38✔
538

539
        // stats
540
        stats->nnz_removed_parallel_rows += nnz_before - *nnz;
38✔
541
        clock_gettime(CLOCK_MONOTONIC, &timer.end);
38✔
542
        stats->time_parallel_rows += GET_ELAPSED_SECONDS(timer);
38✔
543
    }
544

545
    if (stgs->parallel_cols)
38✔
546
    {
547
        // stats
548
        nnz_before = *nnz;
12✔
549
        clock_gettime(CLOCK_MONOTONIC, &timer.start);
12✔
550

551
        // the actual reduction (after removing parallel columns there can
552
        // be no new empty rows or empty cols but might be new stonrows, probably
553
        // although that's rare but it does happen
554
        status |= remove_parallel_cols(prob);
12✔
555
        assert(prob->constraints->state->empty_rows->len == 0);
12✔
556
        assert(prob->constraints->state->empty_cols->len == 0);
12✔
557
        status |= run_trivial_explorers(prob, stgs);
12✔
558
        assert(prob->constraints->state->ston_rows->len == 0);
12✔
559
        assert(!(status & REDUCED));
12✔
560
        RETURN_IF_NOT_UNCHANGED(status);
12✔
561

562
        // stats
563
        stats->nnz_removed_parallel_cols += nnz_before - *nnz;
12✔
564
        clock_gettime(CLOCK_MONOTONIC, &timer.end);
12✔
565
        stats->time_parallel_cols += GET_ELAPSED_SECONDS(timer);
12✔
566
    }
567

568
    return status;
38✔
569
}
570

571
void populate_presolved_problem(Presolver *presolver)
20✔
572
{
573
    PresolvedProblem *reduced_prob = presolver->reduced_prob;
20✔
574
    Constraints *constraints = presolver->prob->constraints;
20✔
575
    Matrix *A = constraints->A;
20✔
576
    reduced_prob->m = A->m;
20✔
577
    reduced_prob->n = A->n;
20✔
578
    reduced_prob->nnz = A->nnz;
20✔
579
    reduced_prob->Ax = A->x;
20✔
580
    reduced_prob->Ai = A->i;
20✔
581
    reduced_prob->rhs = constraints->rhs;
20✔
582
    reduced_prob->lhs = constraints->lhs;
20✔
583
    reduced_prob->c = presolver->prob->obj->c;
20✔
584
    reduced_prob->obj_offset = presolver->prob->obj->offset;
20✔
585

586
    // create bounds arrays
587
    reduced_prob->lbs = (double *) malloc(A->n * sizeof(double));
20✔
588
    reduced_prob->ubs = (double *) malloc(A->n * sizeof(double));
20✔
589
    for (int i = 0; i < A->n; i++)
118✔
590
    {
591
        reduced_prob->lbs[i] = constraints->bounds[i].lb;
98✔
592
        reduced_prob->ubs[i] = constraints->bounds[i].ub;
98✔
593
    }
594

595
    // create row pointers
596
    reduced_prob->Ap = (int *) malloc((A->m + 1) * sizeof(int));
20✔
597
    for (int i = 0; i < A->m + 1; i++)
103✔
598
    {
599
        reduced_prob->Ap[i] = A->p[i].start;
83✔
600
    }
601
}
20✔
602

603
static inline void print_start_message(const PresolveStats *stats)
26✔
604
{
605
    printf("\n\t       PSLP v%s - LP presolver \n\t(c) Daniel "
26✔
606
           "Cederberg, Stanford University, 2025\n",
607
           PSLP_VERSION);
608
    printf("Original problem:  %d rows, %d columns, %d nnz\n",
26✔
609
           stats->n_rows_original, stats->n_cols_original, stats->nnz_original);
26✔
610
}
26✔
611

612
static inline void print_end_message(const Matrix *A, const PresolveStats *stats)
20✔
613
{
614
    printf("Presolved problem: %d rows, %d columns, %d nnz\n", A->m, A->n, A->nnz);
20✔
615
    printf("PSLP init & run time : %.3f seconds, %.3f \n", stats->time_init,
20✔
616
           stats->time_presolve);
20✔
617
}
20✔
618

619
static inline void print_infeas_or_unbnd_message(PresolveStatus status)
6✔
620
{
621
    if (status == INFEASIBLE)
6✔
622
    {
623
        printf("PSLP declares problem as infeasible.\n");
4✔
624
        return;
4✔
625
    }
626
    else if (status == UNBNDORINFEAS)
2✔
627
    {
628
        printf("PSLP declares problem as infeasible or unbounded.\n");
2✔
629
        return;
2✔
630
    }
631

632
    // should never reach here
NEW
633
    assert(false);
×
634
}
635

636
static inline PresolveStatus final_status(PresolveStats *stats)
20✔
637
{
638
    if (stats->n_cols_original == stats->n_cols_reduced &&
20✔
639
        stats->n_rows_original == stats->n_rows_reduced &&
2✔
NEW
640
        stats->nnz_original == stats->nnz_reduced)
×
641
    {
NEW
642
        return UNCHANGED;
×
643
    }
644
    else
645
    {
646
        return REDUCED;
20✔
647
    }
648
}
649

650
PresolveStatus run_presolver(Presolver *presolver)
26✔
651
{
652
    Timer inner_timer, outer_timer;
653
    int nnz_before_cycle, nnz_after_cycle;
654
    int nnz_before_phase, nnz_after_phase;
655
    int nnz_before_reduction;
656
    Problem *prob = presolver->prob;
26✔
657
    PresolveStats *stats = presolver->stats;
26✔
658
    Matrix *A = presolver->prob->constraints->A;
26✔
659
    const Settings *stgs = presolver->stgs;
26✔
660
    Complexity curr_complexity = FAST;
26✔
661
    bool terminate = false;
26✔
662
    PresolveStatus status = UNCHANGED;
26✔
663
    clock_gettime(CLOCK_MONOTONIC, &outer_timer.start);
26✔
664

665
    if (stgs->verbose)
26✔
666
    {
667
        print_start_message(stats);
26✔
668
    }
669

670
    DEBUG(run_debugger(prob->constraints, false));
26✔
671

672
    // ------------------------------------------------------------------------
673
    // Main loop for the presolver. The logic is organized into phases and
674
    // cycles. A phase is a sequence of presolvers belonging to a certain
675
    // complexity class. The cycle resets after the medium presolvers.
676
    // ------------------------------------------------------------------------
677
    nnz_before_cycle = A->nnz;
26✔
678
    while (!terminate)
113✔
679
    {
680
        // before each phase we run the trivial presolvers
681
        nnz_before_reduction = A->nnz;
91✔
682
        status = run_trivial_explorers(prob, stgs);
91✔
683

684
        // run_trivial_explorers returns something else than UNCHANGED only
685
        // if the problem is detected to be infeasible or unbounded
686
        if (status != UNCHANGED)
91✔
687
        {
688
            break;
4✔
689
        }
690

691
        stats->nnz_removed_trivial += nnz_before_reduction - A->nnz;
87✔
692
        DEBUG(run_debugger(prob->constraints, false));
87✔
693
        nnz_before_phase = A->nnz;
87✔
694

695
        // apply presolvers belonging to a certain complexity class
696
        if (curr_complexity == FAST)
87✔
697
        {
698
            nnz_before_reduction = A->nnz;
48✔
699
            RUN_AND_TIME(run_fast_explorers, inner_timer,
48✔
700
                         stats->time_fast_reductions, status, prob, stgs);
701
            stats->nnz_removed_fast += nnz_before_reduction - A->nnz;
48✔
702
        }
703
        else if (curr_complexity == MEDIUM)
39✔
704
        {
705
            RUN_AND_TIME(run_medium_explorers, inner_timer,
39✔
706
                         stats->time_medium_reductions, status, prob, stgs, stats);
707
            nnz_after_cycle = A->nnz;
39✔
708
        }
709

710
        nnz_after_phase = A->nnz;
87✔
711

712
        terminate =
713
            update_termination(nnz_after_cycle, nnz_before_cycle, curr_complexity,
87✔
714
                               status, stgs->max_time, outer_timer);
87✔
715

716
        if (curr_complexity == MEDIUM)
87✔
717
        {
718
            // a new cycle starts after the medium presolvers
719
            nnz_before_cycle = nnz_after_cycle;
39✔
720
        }
721

722
        curr_complexity =
723
            update_complexity(curr_complexity, nnz_before_phase, nnz_after_phase);
87✔
724
    }
725

726
    if (status != UNCHANGED)
26✔
727
    {
728
        // problem detected to be infeasible or unbounded
729
        print_infeas_or_unbnd_message(status);
6✔
730
        return status;
6✔
731
    }
732

733
    if (stgs->relax_bounds)
20✔
734
    {
735
        remove_redundant_bounds(prob->constraints);
20✔
736
    }
737

738
    problem_clean(prob, true);
20✔
739
    DEBUG(run_debugger(prob->constraints, true));
20✔
740
    stats->n_rows_reduced = A->m;
20✔
741
    stats->n_cols_reduced = A->n;
20✔
742
    stats->nnz_reduced = A->nnz;
20✔
743
    DEBUG(run_debugger_stats_consistency_check(stats));
20✔
744
    populate_presolved_problem(presolver);
20✔
745
    clock_gettime(CLOCK_MONOTONIC, &outer_timer.end);
20✔
746
    stats->time_presolve = GET_ELAPSED_SECONDS(outer_timer);
20✔
747

748
    if (stgs->verbose)
20✔
749
    {
750
        print_end_message(A, stats);
20✔
751
    }
752

753
    return final_status(stats);
20✔
754
}
755

756
void postsolve(Presolver *presolver, const double *x, const double *y,
18✔
757
               const double *z, double obj)
758
{
759
    Timer timer;
760
    Solution *sol = presolver->sol;
18✔
761
    PresolveStats *stats = presolver->stats;
18✔
762
    State *data = presolver->prob->constraints->state;
18✔
763
    PostsolveInfo *postsolve_info = data->postsolve_info;
18✔
764
    clock_gettime(CLOCK_MONOTONIC, &timer.start);
18✔
765
    postsolver_update(postsolve_info, stats->n_cols_reduced, stats->n_rows_reduced,
18✔
766
                      data->work->mappings->cols, data->work->mappings->rows);
18✔
767
    postsolver_run(postsolve_info, sol, x, y, z);
18✔
768
    sol->obj = obj + presolver->prob->obj->offset;
18✔
769
    clock_gettime(CLOCK_MONOTONIC, &timer.end);
18✔
770
    stats->time_postsolve = GET_ELAPSED_SECONDS(timer);
18✔
771

772
    if (presolver->stgs->verbose)
18✔
773
    {
774
        printf("PSLP postsolve time: %.4f seconds\n", stats->time_postsolve);
18✔
775
    }
776
}
18✔
777

778
void free_presolver(Presolver *presolver)
98✔
779
{
780
    if (presolver == NULL)
98✔
781
    {
782
        return;
×
783
    }
784

785
    free_problem(presolver->prob);
98✔
786
    PS_FREE(presolver->stats);
98✔
787

788
    if (presolver->reduced_prob)
98✔
789
    {
790
        PS_FREE(presolver->reduced_prob->Ap);
98✔
791
        PS_FREE(presolver->reduced_prob->lbs);
98✔
792
        PS_FREE(presolver->reduced_prob->ubs);
98✔
793
        PS_FREE(presolver->reduced_prob);
98✔
794
    }
795

796
    if (presolver->sol)
98✔
797
    {
798
        PS_FREE(presolver->sol->x);
98✔
799
        PS_FREE(presolver->sol->y);
98✔
800
        PS_FREE(presolver->sol->z);
98✔
801
        PS_FREE(presolver->sol);
98✔
802
    }
803

804
    PS_FREE(presolver);
98✔
805
}
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