Skip to content

Commit 4791deb

Browse files
authored
better numerics stoncols + parallel cols postsolve for non-vertex sol… (#49)
* better numerics stoncols + parallel cols postsolve for non-vertex solution * test * formatter
1 parent bc260a4 commit 4791deb

7 files changed

Lines changed: 112 additions & 56 deletions

File tree

‎.github/workflows/formatting.yml‎

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,9 @@ jobs:
2828
# Check formatting
2929
- name: Check C/C++ formatting
3030
run: |
31-
misformatted=$(find . -name '*.c' -o -name '*.h' -print0 | xargs -0 clang-format -style=file -output-replacements-xml | grep "<replacement " || true)
32-
if [ -n "$misformatted" ]; then
33-
echo "ERROR: Some files are not properly formatted. Run clang-format -i."
31+
if ! git ls-files '*.c' '*.h' | xargs clang-format --dry-run -Werror; then
32+
echo "ERROR: Some files are not properly formatted."
33+
echo "Fix with: git ls-files '*.c' '*.h' | xargs clang-format -i"
3434
exit 1
35-
else
36-
echo "All files are properly formatted."
37-
exit 0
38-
fi
35+
fi
36+
echo "All files are properly formatted."

‎include/core/Numerics.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#define FEAS_TOL 1e-6
2525
#define BOUND_MARGINAL 0.5 * FEAS_TOL
2626
#define ZERO_TOL 1e-10
27+
#define CANCEL_TOL_REL 1e-12
2728
#define ZERO_TOL_DUAL_POSTSOLVE 1e-6
2829
#define HUGE_VAL_PS 1e7
2930

‎src/core/Postsolver.c‎

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -270,46 +270,7 @@ static void retrieve_parallel_col(Solution *sol, const int *indices,
270270
sol->x[k] = xk_val;
271271

272272
// dual postsolve
273-
bool is_xj_at_bound =
274-
(!HAS_TAG(cTag_j, C_TAG_LB_INF) && IS_EQUAL_FEAS_TOL(xj_val, lb_j)) ||
275-
(!HAS_TAG(cTag_j, C_TAG_UB_INF) && IS_EQUAL_FEAS_TOL(xj_val, ub_j));
276-
bool is_xk_at_bound =
277-
(!HAS_TAG(cTag_k, C_TAG_LB_INF) && IS_EQUAL_FEAS_TOL(xk_val, lb_k)) ||
278-
(!HAS_TAG(cTag_k, C_TAG_UB_INF) && IS_EQUAL_FEAS_TOL(xk_val, ub_k));
279-
280-
if (is_xj_at_bound && is_xk_at_bound)
281-
{
282-
sol->z[k] = ratio * sol->z[j];
283-
}
284-
else
285-
{
286-
sol->z[k] = 0.0;
287-
}
288-
289-
#ifndef NDEBUG
290-
SET_ZERO_IF_SMALL_DUAL_POSTSOLVE(sol->z[j]);
291-
292-
// if the multiplier is positive the variable should be at its lower bound
293-
if (sol->z[j] > 0)
294-
{
295-
assert(!HAS_TAG(cTag_j, C_TAG_LB_INF) && IS_EQUAL_FEAS_TOL(sol->x[j], lb_j));
296-
}
297-
// if the multiplier is negative the variable should be at its upper bound
298-
else if (sol->z[j] < 0)
299-
{
300-
assert(!HAS_TAG(cTag_j, C_TAG_UB_INF) && IS_EQUAL_FEAS_TOL(sol->x[j], ub_j));
301-
}
302-
303-
// similar checks for variable k
304-
if (sol->z[k] > 0)
305-
{
306-
assert(!HAS_TAG(cTag_k, C_TAG_LB_INF) && IS_EQUAL_FEAS_TOL(sol->x[k], lb_k));
307-
}
308-
else if (sol->z[k] < 0)
309-
{
310-
assert(!HAS_TAG(cTag_k, C_TAG_UB_INF) && IS_EQUAL_FEAS_TOL(sol->x[k], ub_k));
311-
}
312-
#endif
273+
sol->z[k] = ratio * sol->z[j];
313274
}
314275

315276
void retrieve_deleted_row(Solution *sol, int row, double val)

‎src/core/Problem.c‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "Constraints.h"
2121
#include "Matrix.h"
2222
#include "Memory_wrapper.h"
23+
#include "Numerics.h"
2324
#include "State.h"
2425
#include "Workspace.h"
2526
#include "utils.h"
@@ -103,7 +104,17 @@ void sub_var_in_obj(Objective *obj, const double *vals, const int *cols, int len
103104
double ratio = obj->c[k] / aik;
104105
for (int i = 0; i < len; ++i)
105106
{
106-
obj->c[cols[i]] -= ratio * vals[i];
107+
double upd = ratio * vals[i];
108+
double new_c = obj->c[cols[i]] - upd;
109+
110+
/* Exact cancellation leaves a residue whose sign is rounding noise;
111+
downstream sign tests (e.g. the unboundedness check in
112+
process_colston_ineq) must never see it. */
113+
if (ABS(new_c) <= CANCEL_TOL_REL * MAX(ABS(obj->c[cols[i]]), ABS(upd)))
114+
{
115+
new_c = 0.0;
116+
}
117+
obj->c[cols[i]] = new_c;
107118
}
108119

109120
obj->offset += rhs * ratio;

‎src/core/radix_sort.c‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,7 @@ void radix_sort_rows(int *rows, size_t n, const int *sparsity_IDs,
140140

141141
// --- Single-key radix sort ---
142142

143-
static void insertion_sort_by_key(int *indices, size_t n,
144-
const int *keys)
143+
static void insertion_sort_by_key(int *indices, size_t n, const int *keys)
145144
{
146145
for (size_t i = 1; i < n; i++)
147146
{
@@ -157,8 +156,7 @@ static void insertion_sort_by_key(int *indices, size_t n,
157156
}
158157
}
159158

160-
void radix_sort_by_key(int *indices, size_t n,
161-
const int *keys, int *aux)
159+
void radix_sort_by_key(int *indices, size_t n, const int *keys, int *aux)
162160
{
163161
if (n < 256)
164162
{
@@ -177,8 +175,7 @@ void radix_sort_by_key(int *indices, size_t n,
177175
memset(counts, 0, 256 * sizeof(size_t));
178176
for (size_t i = 0; i < n; i++)
179177
{
180-
unsigned byte =
181-
((uint32_t) keys[src[i]] >> shift) & 0xFF;
178+
unsigned byte = ((uint32_t) keys[src[i]] >> shift) & 0xFF;
182179
counts[byte]++;
183180
}
184181

@@ -212,8 +209,7 @@ void radix_sort_by_key(int *indices, size_t n,
212209
// scatter (forward — stable)
213210
for (size_t i = 0; i < n; i++)
214211
{
215-
unsigned byte =
216-
((uint32_t) keys[src[i]] >> shift) & 0xFF;
212+
unsigned byte = ((uint32_t) keys[src[i]] >> shift) & 0xFF;
217213
dst[counts[byte]++] = src[i];
218214
}
219215

‎tests/test_postsolve.h‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,54 @@ static char *test_fix_col_inf()
993993
return 0;
994994
}
995995

996+
/* Parallel-column dual postsolve: z_k = ratio * z_j is an exact identity.
997+
998+
min. [1 2 0.1] x
999+
s.t. -20 <= [1 2 1] x <= 20
1000+
-40 <= [3 6 1] x <= 40
1001+
x1 in [0, 3], x2 in [0, 5], x3 in [-10, 10] */
1002+
static char *test_parallel_col_dual_identity()
1003+
{
1004+
double Ax[] = {1, 2, 1, 3, 6, 1};
1005+
int Ai[] = {0, 1, 2, 0, 1, 2};
1006+
int Ap[] = {0, 3, 6};
1007+
int nnz = 6;
1008+
int n_rows = 2;
1009+
int n_cols = 3;
1010+
1011+
double lhs[] = {-20, -40};
1012+
double rhs[] = {20, 40};
1013+
double lbs[] = {0, 0, -10};
1014+
double ubs[] = {3, 5, 10};
1015+
double c[] = {1, 2, 0.1};
1016+
1017+
Settings *stgs = default_settings();
1018+
set_settings_false(stgs);
1019+
stgs->parallel_cols = true;
1020+
Presolver *presolver =
1021+
new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, lbs, ubs, c, stgs);
1022+
1023+
run_presolver(presolver);
1024+
1025+
// reduced problem: the merged column (box [0, 13]) and x3
1026+
double x[] = {7, 10};
1027+
double y[] = {0.3, 0.1};
1028+
double z[] = {0.4, -0.3};
1029+
postsolve(presolver, x, y, z);
1030+
1031+
double correct_x[] = {0, 3.5, 10};
1032+
double correct_y[] = {0.3, 0.1};
1033+
double correct_z[] = {0.4, 0.8, -0.3};
1034+
1035+
mu_assert("parallel col dual identity error",
1036+
is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y,
1037+
correct_y, presolver->sol->z, correct_z, n_rows,
1038+
n_cols, POSTSOLVE_TOL_FEAS));
1039+
PS_FREE(stgs);
1040+
free_presolver(presolver);
1041+
return 0;
1042+
}
1043+
9961044
static const char *all_tests_postsolve()
9971045
{
9981046
mu_run_test(test_0_postsolve, counter_postsolve);
@@ -1017,6 +1065,7 @@ static const char *all_tests_postsolve()
10171065
mu_run_test(test_pathological_ston_one, counter_postsolve);
10181066
mu_run_test(test_pathological_ston_two, counter_postsolve);
10191067
mu_run_test(test_fix_col_inf, counter_postsolve);
1068+
mu_run_test(test_parallel_col_dual_identity, counter_postsolve);
10201069
// all tests above pass
10211070

10221071
return 0;

‎tests/test_ston.h‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,45 @@ static char *test_16_ston()
12001200
return 0;
12011201
}
12021202

1203+
/* Test 17: exact cost cancellation must snap to zero, not flip the
1204+
unboundedness check.
1205+
1206+
min. -0.1125*x1 + 0.0375*x2 + x3
1207+
s.t. [-0.3 0.1 0] x = 0.6
1208+
[ 0 0.1 1] x >= 0
1209+
x1 free, x2 >= 0, x3 >= 0 */
1210+
static char *test_17_ston()
1211+
{
1212+
double Ax[] = {-0.3, 0.1, 0.1, 1};
1213+
int Ai[] = {0, 1, 1, 2};
1214+
int Ap[] = {0, 2, 4};
1215+
int nnz = 4;
1216+
int n_rows = 2;
1217+
int n_cols = 3;
1218+
1219+
double lhs[] = {0.6, 0};
1220+
double rhs[] = {0.6, INF};
1221+
double lbs[] = {-INF, 0, 0};
1222+
double ubs[] = {INF, INF, INF};
1223+
double c[] = {-0.1125, 0.0375, 1};
1224+
1225+
Settings *stgs = default_settings();
1226+
Presolver *presolver =
1227+
new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, lbs, ubs, c, stgs);
1228+
1229+
Problem *prob = presolver->prob;
1230+
1231+
PresolveStatus status = remove_ston_cols(prob);
1232+
1233+
mu_assert("cancellation residue misread as unbounded", status != UNBNDORINFEAS);
1234+
mu_assert("partner cost must snap to exact zero", prob->obj->c[1] == 0.0);
1235+
1236+
PS_FREE(stgs);
1237+
free_presolver(presolver);
1238+
1239+
return 0;
1240+
}
1241+
12031242
static const char *all_tests_ston()
12041243
{
12051244
mu_run_test(test_01_ston, counter_ston); // (✓)
@@ -1217,6 +1256,7 @@ static const char *all_tests_ston()
12171256
mu_run_test(test_14_ston, counter_ston); // (✓)
12181257
mu_run_test(test_15_ston, counter_ston); // (✓)
12191258
mu_run_test(test_16_ston, counter_ston); // (✓)
1259+
mu_run_test(test_17_ston, counter_ston); // (✓)
12201260
return 0;
12211261
}
12221262

0 commit comments

Comments
 (0)