Closed rolfvdhulst closed 2 weeks ago
In your comparison the SCIP versions differ. I have compared PaPILO 2.1.4 with latest develop PaPILO 2.4.0 in latest master SCIP 10.0.0 where presolving with the new version indeed takes 19.3 % longer while some more implications are found. Due to the instance size it is hard to tell whether they help afterwards at some point.
The time difference is significant enough to look into it but do you have encountered an instance where the difference reaches your claim when comparing to the latest develop PaPILO?
It seems like we perform one more useless round of all presolvers now:
Old
presolver nb calls success calls(%) nb transactions tsx applied(%) execution time(s)
colsingleton 5 0.0 0 0.0 0.000
coefftightening 5 0.0 0 0.0 0.000
propagation 5 60.0 352 100.0 0.004
simpleprobing 2 0.0 0 0.0 0.000
parallelrows 2 0.0 0 0.0 0.004
stuffing 2 0.0 0 0.0 0.000
dualfix 2 0.0 0 0.0 0.002
fixcontinuous 2 0.0 0 0.0 0.000
simplifyineq 2 0.0 0 0.0 0.000
doubletoneq 2 0.0 0 0.0 0.000
implint 2 0.0 0 0.0 0.000
dualinfer 2 0.0 0 0.0 0.002
probing 3 33.3 50091 100.0 43.382
domcol 2 0.0 0 0.0 0.574
substitution 4 100.0 103 1.9 0.036
New
presolver nb calls success calls(%) nb transactions tsx applied(%) execution time(s)
colsingleton 6 0.0 0 0.0 0.000
coefftightening 6 0.0 0 0.0 0.000
propagation 6 50.0 352 100.0 0.015
simpleprobing 3 0.0 0 0.0 0.000
parallelrows 3 0.0 0 0.0 0.004
stuffing 3 0.0 0 0.0 0.000
dualfix 3 0.0 0 0.0 0.002
fixcontinuous 3 0.0 0 0.0 0.000
simplifyineq 3 0.0 0 0.0 0.000
doubletoneq 3 0.0 0 0.0 0.000
implint 3 0.0 0 0.0 0.000
dualinfer 3 0.0 0 0.0 0.002
probing 4 25.0 50091 100.0 59.318
domcol 3 0.0 0 0.0 0.566
substitution 5 80.0 103 1.9 0.035
The additional round is already introduced in PaPILO 2.2.0 but without overhead.
The overhead is actually introduced afterwards in PaPILO 2.2.1.
A fix in single row propagation causes this slowdown and it seems like the computational effort can be reduced without losing the fix. I consider this as a bugfix since it should have been part of the original fix and therefore target it to main. But it is not clear when this will be merged since I have no permissions and @alexhoen is not there.
It's still strange that Papilo needs probing at all for this particular instance; Highs can fix the columns without any probing. I'll highlight how below;
For example, for fixing 'X_INTRODUCED_108', the reasoning needed is as follows.
I shorten the variable names to 'x_100' here, all variables are integer/binary.
First, objective constraint 'p_lin_0' is of the form x_obj = x11 + x_12 + ...
with lb(x_obj) = 0, ub(x_obj) = 39062 and lb(x_11) = 0, ub(x_11)= 39063. Then, ub(x_obj) = 39062 implies that ub(x_11) can be strengthened to ub(x_11) = 39062 (using the objective cutoff, essentially) Second, constraint 'p_lin_2' is of the form x_11 = 39063 x_108 + ... where all ... terms are binaries with positive integral coefficients. Because ub(x_11) = 39062, we can fix x_108 = 0 using bound implications, since this implies that x_108 <= 39602/39603 < 1.
These are definitely reductions that I would expect papilo to find, without probing. The only thing I can think off here is that numerical tolerances prevent one of the reductions. However, in an ideal world we should detect that this is not an issue here, as integrality of both the coefficients and the variables ensures validity.
The fix is actually on this type of reduction (single row propagation), which seems to be used in probing as well. Numerics do not look too bad here. Do you mean that this is done in probing instead of propagation or that it does no longer happen at all?
Highs finds the fixings using propagation, and does not perform any probing at all, whereas Papilo does not find the reductions in propagation and needs to use probing. So likely, Papilo is leaving some (obvious) performance on the table during propagation or is not doing it fully.
When switching off PaPILOs probing, I get
presolver nb calls success calls(%) nb transactions tsx applied(%) execution time(s)
colsingleton 6 0.0 0 0.0 0.000
coefftightening 6 0.0 0 0.0 0.000
propagation 6 50.0 100534 100.0 0.007
simpleprobing 3 0.0 0 0.0 0.000
parallelrows 3 0.0 0 0.0 0.004
stuffing 3 0.0 0 0.0 0.000
dualfix 3 0.0 0 0.0 0.002
fixcontinuous 3 0.0 0 0.0 0.000
simplifyineq 3 0.0 0 0.0 0.000
doubletoneq 3 0.0 0 0.0 0.000
implint 3 0.0 0 0.0 0.000
dualinfer 3 0.0 0 0.0 0.002
domcol 3 0.0 0 0.0 0.957
substitution 5 80.0 259 0.8 0.043
and t_X_INTRODUCED108 is still fixed to zero. So you are definitely right that there is something to win here but it rather seems like probing kicks in too early by default since propagation is able to find the exposed reduction. Was this default probing behavior different in a previous version of PaPILO?
There is also a difference between single-threaded and multithreaded runs.
Single-threaded calling Papilo's executable:
presolver nb calls success calls(%) nb transactions tsx applied(%) execution time(s)
colsingleton 11 18.2 115 50.4 0.000
coefftightening 11 0.0 0 0.0 0.000
propagation 11 45.5 652 100.0 0.033
fixcontinuous 1 0.0 0 0.0 0.000
simpleprobing 4 0.0 0 0.0 0.000
parallelrows 3 0.0 0 0.0 0.006
parallelcols 3 0.0 0 0.0 0.022
stuffing 4 0.0 0 0.0 0.000
dualfix 4 0.0 0 0.0 0.006
simplifyineq 4 0.0 0 0.0 0.000
doubletoneq 4 0.0 0 0.0 0.000
implint 1 0.0 0 0.0 0.000
domcol 3 0.0 0 0.0 1.317
dualinfer 1 0.0 0 0.0 0.000
probing 5 20.0 50091 100.0 547.793
substitution 8 87.5 162 4.3 0.044
sparsify 3 33.3 66 100.0 0.031
Papilo executable, Threads = 2:
presolver nb calls success calls(%) nb transactions tsx applied(%) execution time(s)
colsingleton 9 22.2 58 100.0 0.000
coefftightening 9 0.0 0 0.0 0.000
propagation 9 55.6 100708 100.0 0.010
fixcontinuous 1 0.0 0 0.0 0.000
simpleprobing 3 0.0 0 0.0 0.000
parallelrows 3 0.0 0 0.0 0.004
parallelcols 3 0.0 0 0.0 0.015
stuffing 3 0.0 0 0.0 0.000
dualfix 3 0.0 0 0.0 0.003
simplifyineq 3 0.0 0 0.0 0.000
doubletoneq 3 0.0 0 0.0 0.000
implint 1 0.0 0 0.0 0.000
domcol 3 0.0 0 0.0 0.445
dualinfer 1 0.0 0 0.0 0.000
probing 3 0.0 0 0.0 0.174
substitution 6 83.3 235 3.0 0.035
sparsify 3 33.3 66 100.0 0.032
SCIP uses the single-threaded version (with some tweaks). Debugging the code, the issue seems to be that somehow the rows whose activity is modified by the first reduction are not properly tracked. Papilo even calls the constraint propagation presolver again but then with an empty 'changed_activities' vector. The issue seems to only affect the implementation of the non-multithreaded code, because this uses a different logic (for some reason...).
This is quite a big issue that could potentially also affect the performance of other plugins in Papilo that are not triggering from reductions, as it seems like Papilo is not correctly telling the plugins what rows have been modified.
Sounds like a good catch. Indeed, at the top of Presolve
The computational overhead in propagation during probing shown in https://github.com/scipopt/papilo/issues/56#issuecomment-2266725673 should be reduced again by 944ddf3, whereas the fundamentally inefficient evaluation sequence described in https://github.com/scipopt/papilo/issues/56#issuecomment-2268989256 persists.
This is resolved with b4051e3002abdc6a9a3042c985c1e6978437b0e3 when applying PaPILO directly, however, through SCIP the setting presolve.removeslackvars = 0
still avoids a significant performance improvement here.
I recently performed some tests for Presolving on SCIP's master branch and observed a significant performance regression for Papilo from 2.1 to 2.3. In general, the Papilo plugin seems to be anywhere between 40 to 100% slower without finding many new reductions. I checked and SCIP does indeed spend this time in Papilo, so the issue lies within Papilo.
Below is an example for the relatively large instance
proteindesign121pgb11p9
, where Papilo 2.1 takes 45 seconds but Papilo 2.3 takes 125 seconds.Papilo 2.1:
Papilo 2.3: