@@ -26,6 +26,10 @@ TEST_CASE("test-col-stuffing", "[highs_test_presolve_rules]") {
2626 Highs h;
2727 h.setOptionValue (" output_flag" , dev_run);
2828 h.setOptionValue (" presolve_rule_test" , kPresolveRuleColStuffing );
29+ REQUIRE (h.setOptionValue (" presolve_rule_logging" , true ) == HighsStatus::kOk );
30+ // Initial sweep doesn't yield reductions, but switch it off for clarity
31+ REQUIRE (h.setOptionValue (" presolve_rule_off" ,
32+ 1 << kPresolveRuleInitialSweep ) == HighsStatus::kOk );
2933 const bool lp0 = true ;
3034 const bool lp1 = true ;
3135 const bool lp1a = true ;
@@ -90,6 +94,67 @@ TEST_CASE("test-col-stuffing", "[highs_test_presolve_rules]") {
9094 h.resetGlobalScheduler (true );
9195}
9296
97+ /*
98+ TEST_CASE("test-weakly-dominated-col-upper", "[highs_test_presolve_rules]") {
99+ Highs h;
100+ h.setOptionValue("output_flag", dev_run);
101+ REQUIRE(h.setOptionValue("presolve_rule_logging", true) == HighsStatus::kOk);
102+ // LP is
103+ //
104+ // min -y, subject to x+y <= 0, x >= 0; 0 <= x <= 1, y free
105+ //
106+ // Optimal solution is x = 1; y = -1, with x nonbasic with dual -1, and
107+ HighsLp lp;
108+ lp.num_col_ = 2;
109+ lp.num_row_ = 2;
110+ lp.col_lower_ = {-kHighsInf, -kHighsInf};
111+ lp.col_upper_ = {1, kHighsInf};
112+ lp.row_lower_ = {-kHighsInf, 1};
113+ lp.row_upper_ = { 0, kHighsInf};
114+ lp.a_matrix_.format_ = MatrixFormat::kRowwise;
115+ lp.a_matrix_.start_ = {0, 2, 3};
116+ lp.a_matrix_.index_ = {0, 1, 0};
117+ lp.a_matrix_.value_ = {1, 1, 1};
118+
119+ bool maximize_first = true;
120+ std::string sense_string = "";
121+ std::string test_string = "";
122+
123+ for (HighsInt k = 0; k < 2; k++) {
124+ // Passes are minimize c^Tx and maximize -c^Tx according to
125+ // maximize_first
126+ if (maximize_first) {
127+ lp.sense_ = ObjSense::kMaximize;
128+ sense_string = "maximize";
129+ lp.col_cost_ = {0, 1};
130+ } else {
131+ lp.sense_ = ObjSense::kMinimize;
132+ sense_string = "minimize";
133+ lp.col_cost_ = {0, -1};
134+ }
135+ // REQUIRE(h.setOptionValue("presolve_rule_test", 0) == HighsStatus::kOk);
136+ // test_string = "vanilla-presolve-" + sense_string;
137+ // presolveOffOn(test_string, lp, h);
138+
139+ REQUIRE(h.setOptionValue("presolve_rule_test",
140+ kPresolveRuleWeaklyDominatedColUpper) == HighsStatus::kOk);
141+
142+ // test_string = "initial-sweep+test-weakly-dominated-col-upper-" +
143+ sense_string;
144+ // presolveOffOn(test_string, lp, h, 1, 1, 1);
145+
146+ REQUIRE(h.setOptionValue("presolve_rule_off", 1 <<
147+ kPresolveRuleInitialSweep) == HighsStatus::kOk);
148+
149+ test_string = "test-weakly-dominated-col-upper-" + sense_string;
150+ presolveOffOn(test_string, lp, h, 1, 2, 1);
151+
152+ maximize_first = !maximize_first;
153+ }
154+ h.resetGlobalScheduler(true);
155+ }
156+ */
157+
93158TEST_CASE (" test-parallel-rows-cut-ordering" , " [highs_test_presolve_rules]" ) {
94159 // Rows 0 and 1 are parallel (both [1, 1]). Row 0 is marked as a
95160 // cut. detectParallelRowsAndCols must remove the cut row (0) and
@@ -113,6 +178,7 @@ TEST_CASE("test-parallel-rows-cut-ordering", "[highs_test_presolve_rules]") {
113178
114179 HighsOptions options;
115180 options.presolve_rule_test = kPresolveRuleParallelRowsAndCols ;
181+ options.presolve_rule_off = 1 << kPresolveRuleInitialSweep ;
116182 options.output_flag = dev_run;
117183
118184 HighsTimer timer;
@@ -137,6 +203,117 @@ TEST_CASE("test-parallel-rows-cut-ordering", "[highs_test_presolve_rules]") {
137203 REQUIRE (!postsolve_stack.isCutRow (0 ));
138204}
139205
206+ TEST_CASE (" test-effective-costs" , " [highs_test_presolve]" ) {
207+ // Debugging ZeroObjSingletonContinuousCol for germanrr highlighted
208+ // the deficiency in computing the active_cost_norm when the
209+ // objective is f = z, with z = c^Tx and z free. In
210+ // HighsSolution.cpp is the method getEffectiveCosts that
211+ // substitutes all free column singletons into the objective to get
212+ // the "effective costs".
213+ Highs h;
214+ h.setOptionValue (" output_flag" , dev_run);
215+ bool test_all = true ;
216+ bool test_lp0 = test_all;
217+ bool test_lp1 = test_all;
218+ bool test_lp2 = test_all;
219+
220+ if (test_lp0) {
221+ HighsLp lp;
222+ // First LP is
223+ //
224+ // min 4z
225+ //
226+ // -1 <= x + y - 2z <= 1
227+ //
228+ // -1 <= 201x + y <= 1
229+ //
230+ // 0 <= x <= 1, y, z free
231+ //
232+ // where the bounds on the two constraints and non-unit
233+ // coefficients of z in the objective and first contraint give
234+ // code coverage
235+ //
236+ // Aiming to minimize 4z, and bound is given by 2z >= x + y - 1,
237+ // so substitute z = (x+y-1)/2 into the objective to give
238+ //
239+ // min 2x + 2y - 2
240+ //
241+ // y is then minimized with bound is given by y >= -201x - 1, so
242+ // substitute y = -201x - 1 into the objective to give
243+ //
244+ // min 2x +(-402x-2) - 2 = -400x - 4
245+ //
246+ // This function is minimized when x = 1 to give y = -202 and z =
247+ // -101 with objective -404
248+ //
249+ // The optimal dual values are -400 for x, -2 for row 0 and 2 for
250+ // row 1. However, although this example tests code coverage on
251+ // identifying free column singletons and a double free column
252+ // singleton identified in getEffectiveCosts, the dual of -400 for
253+ // the only nonbasic column means that there are no active costs,
254+ // so active_cost_norm is zero (hence absolute and relative dual
255+ // infeasibility measures are identical).
256+ lp.model_name_ = " LP0" ;
257+ lp.num_col_ = 3 ;
258+ lp.num_row_ = 2 ;
259+ lp.col_cost_ = {0 , 0 , 4 };
260+ lp.col_lower_ = {0 , -kHighsInf , -kHighsInf };
261+ lp.col_upper_ = {1 , kHighsInf , kHighsInf };
262+ lp.a_matrix_ .format_ = MatrixFormat::kRowwise ;
263+ lp.a_matrix_ .start_ = {0 , 3 , 5 };
264+ lp.a_matrix_ .index_ = {0 , 1 , 2 , 0 , 1 };
265+ lp.a_matrix_ .value_ = {1 , 1 , -2 , 201 , 1 };
266+ lp.row_lower_ = {-1 , -1 };
267+ lp.row_upper_ = {1 , 1 };
268+ h.passModel (lp);
269+ h.setOptionValue (" log_dev_level" , 1 );
270+ h.setOptionValue (" presolve_rule_logging" , kHighsOnString );
271+ h.run ();
272+ REQUIRE (h.getInfo ().active_cost_norm == 0 );
273+ }
274+ if (test_lp1) {
275+ HighsLp lp;
276+ // Here's a simpler example that reflects the behaviour observed
277+ // with germanrr, where the cost row of the matrix introduced many
278+ // large costs. Hence the presolved model had a large value for
279+ // active_cost_norm but, after postsolve, the model had
280+ // active_cost_norm = 1.
281+
282+ double cost = 1e5 ;
283+ double eps = 1e-4 ;
284+ lp.model_name_ = " LP1" ;
285+ lp.num_col_ = 3 ;
286+ lp.num_row_ = 2 ;
287+ lp.col_cost_ = {0 , 0 , 1 };
288+ lp.col_lower_ = {0 , 0 , -kHighsInf };
289+ lp.col_upper_ = {1 , 1 , kHighsInf };
290+ lp.a_matrix_ .format_ = MatrixFormat::kRowwise ;
291+ lp.a_matrix_ .start_ = {0 , 3 , 5 };
292+ lp.a_matrix_ .index_ = {0 , 1 , 2 , 0 , 1 };
293+ lp.a_matrix_ .value_ = {cost, cost - eps, 1 , 1 , 1 , 1 };
294+ lp.row_lower_ = {0 , 1 };
295+ lp.row_upper_ = {0 , 1 };
296+ h.passModel (lp);
297+
298+ h.run ();
299+ REQUIRE (h.getInfo ().active_cost_norm == cost);
300+ }
301+ if (test_lp2) {
302+ // Finally gas11 has 61 free column singletons: 55 in the first
303+ // pass, and 6 in the second.
304+ const std::string model = " gas11" ;
305+ std::string model_file =
306+ std::string (HIGHS_DIR ) + " /check/instances/" + model + " .mps" ;
307+ REQUIRE (h.readModel (model_file) == HighsStatus::kWarning );
308+ REQUIRE (h.setOptionValue (kPresolveString , kHighsOffString ) ==
309+ HighsStatus::kOk );
310+ HighsStatus return_status = h.run ();
311+ REQUIRE (return_status == HighsStatus::kOk );
312+ double active_cost_norm = 2.000000001e+7 ;
313+ REQUIRE (std::fabs (h.getInfo ().active_cost_norm - active_cost_norm) <= 1e-8 );
314+ }
315+ }
316+
140317TEST_CASE (" test-fourier-motzkin" , " [highs_test_presolve_rules]" ) {
141318 Highs h;
142319 h.setOptionValue (" output_flag" , dev_run);
0 commit comments