@@ -3192,6 +3192,10 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack,
31923192 HPRESOLVE_CHECKED_CALL (dualFixing (postsolve_stack, col));
31933193 if (colDeleted[col]) return Result::kOk ;
31943194
3195+ // singleton column stuffing
3196+ HPRESOLVE_CHECKED_CALL (singletonColStuffing (postsolve_stack, col));
3197+ if (colDeleted[col]) return Result::kOk ;
3198+
31953199 // update column implied bounds
31963200 updateColImpliedBounds (row, col, colCoef);
31973201
@@ -4335,6 +4339,10 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack,
43354339 HPRESOLVE_CHECKED_CALL (dualFixing (postsolve_stack, col));
43364340 if (colDeleted[col]) return Result::kOk ;
43374341
4342+ // singleton column stuffing
4343+ HPRESOLVE_CHECKED_CALL (singletonColStuffing (postsolve_stack, col));
4344+ if (colDeleted[col]) return Result::kOk ;
4345+
43384346 // update dual implied bounds of all rows in given column
43394347 if (model->integrality_ [col] != HighsVarType::kInteger )
43404348 updateRowDualImpliedBounds (col);
@@ -4460,6 +4468,35 @@ HPresolve::Result HPresolve::detectDominatedCol(
44604468 return Result::kOk ;
44614469}
44624470
4471+ void HPresolve::computeLocks (
4472+ HighsInt col, bool considerObjective,
4473+ std::function<bool (HighsInt, bool )> lockCallback) const {
4474+ // if the callback returns true, we stop examining the locks
4475+ if (considerObjective) {
4476+ // consider objective function
4477+ if (model->col_cost_ [col] < 0 ) {
4478+ // downlock
4479+ if (lockCallback (-1 , false )) return ;
4480+ } else if (model->col_cost_ [col] > 0 ) {
4481+ // uplock
4482+ if (lockCallback (-1 , true )) return ;
4483+ }
4484+ }
4485+
4486+ // check coefficients
4487+ for (const auto & nz : getColumnVector (col)) {
4488+ // implied lower bound -> downlock
4489+ if (yieldsImpliedLowerBound (nz.index (), nz.value ()) &&
4490+ lockCallback (nz.index (), false ))
4491+ break ;
4492+
4493+ // implied upper bound -> uplock
4494+ if (yieldsImpliedUpperBound (nz.index (), nz.value ()) &&
4495+ lockCallback (nz.index (), true ))
4496+ break ;
4497+ }
4498+ }
4499+
44634500HPresolve::Result HPresolve::dualFixing (HighsPostsolveStack& postsolve_stack,
44644501 HighsInt col) {
44654502 // fix variables or tighten bounds using dual arguments
@@ -4471,43 +4508,6 @@ HPresolve::Result HPresolve::dualFixing(HighsPostsolveStack& postsolve_stack,
44714508 // return if variable is already fixed
44724509 if (model->col_lower_ [col] == model->col_upper_ [col]) return Result::kOk ;
44734510
4474- // lambda for computing locks
4475- auto computeLocks = [&](HighsInt col, HighsInt& numDownLocks,
4476- HighsInt& numUpLocks, HighsInt& downLockRow,
4477- HighsInt& upLockRow) {
4478- // initialise
4479- numDownLocks = 0 ;
4480- numUpLocks = 0 ;
4481- downLockRow = -1 ;
4482- upLockRow = -1 ;
4483-
4484- // consider objective function
4485- if (model->col_cost_ [col] > 0 )
4486- numUpLocks++;
4487- else if (model->col_cost_ [col] < 0 )
4488- numDownLocks++;
4489-
4490- // check coefficients
4491- for (const auto & nz : getColumnVector (col)) {
4492- // update number of locks
4493- if (yieldsImpliedLowerBound (nz.index (), nz.value ())) {
4494- // implied lower bound -> downlock
4495- numDownLocks++;
4496- downLockRow = nz.index ();
4497- }
4498-
4499- if (yieldsImpliedUpperBound (nz.index (), nz.value ())) {
4500- // implied upper bound -> uplock
4501- numUpLocks++;
4502- upLockRow = nz.index ();
4503- }
4504-
4505- // stop early if there are locks in both directions, since the variable
4506- // cannot be fixed in this case.
4507- if (numDownLocks > 1 && numUpLocks > 1 ) break ;
4508- }
4509- };
4510-
45114511 // lambda for variable substitution
45124512 auto substituteCol = [&](HighsInt col, HighsInt row, HighsInt direction,
45134513 double colBound, double otherColBound) {
@@ -4596,12 +4596,26 @@ HPresolve::Result HPresolve::dualFixing(HighsPostsolveStack& postsolve_stack,
45964596 return true ;
45974597 };
45984598
4599- // compute locks
4599+ // lambda callback for lock computation
46004600 HighsInt numDownLocks = 0 ;
46014601 HighsInt numUpLocks = 0 ;
46024602 HighsInt downLockRow = -1 ;
46034603 HighsInt upLockRow = -1 ;
4604- computeLocks (col, numDownLocks, numUpLocks, downLockRow, upLockRow);
4604+ auto lockCallback = [&](HighsInt row, bool isUpLock) {
4605+ // count locks and remember row index
4606+ if (isUpLock) {
4607+ numUpLocks++;
4608+ upLockRow = row;
4609+ } else {
4610+ numDownLocks++;
4611+ downLockRow = row;
4612+ }
4613+ // stop early if there are locks in both directions, since the variable
4614+ // cannot be fixed in this case.
4615+ return numDownLocks > 1 && numUpLocks > 1 ;
4616+ };
4617+ // compute locks
4618+ computeLocks (col, true , lockCallback);
46054619
46064620 // check if variable can be fixed
46074621 if (numDownLocks == 0 || numUpLocks == 0 ) {
@@ -4655,6 +4669,137 @@ HPresolve::Result HPresolve::dualFixing(HighsPostsolveStack& postsolve_stack,
46554669 return Result::kOk ;
46564670}
46574671
4672+ HPresolve::Result HPresolve::singletonColStuffing (
4673+ HighsPostsolveStack& postsolve_stack, HighsInt col) {
4674+ // singleton column stuffing
4675+ // see Gamrath, G., Koch, T., Martin, A. et al., Progress in presolving
4676+ // for mixed integer programming, Math. Prog. Comp. 7, 367–398 (2015).
4677+ auto isContSingleton = [&](HighsInt col) {
4678+ return (!colDeleted[col] && colsize[col] == 1 &&
4679+ model->integrality_ [col] != HighsVarType::kInteger &&
4680+ model->col_lower_ [col] != model->col_upper_ [col]);
4681+ };
4682+
4683+ auto sortCols =
4684+ [&](std::vector<std::tuple<HighsInt, double , HighsInt>>& vec) {
4685+ pdqsort (
4686+ vec.begin (), vec.end (),
4687+ [&](const std::tuple<HighsInt, double , HighsInt>& col1,
4688+ const std::tuple<HighsInt, double , HighsInt>& col2) {
4689+ return model->col_cost_ [std::get<0 >(col1)] / std::get<1 >(col1) <
4690+ model->col_cost_ [std::get<0 >(col2)] / std::get<1 >(col2);
4691+ });
4692+ };
4693+
4694+ // lambda for updating row activity bounds
4695+ auto updateActivityBounds = [&](HighsCDouble& sumLower,
4696+ HighsCDouble& sumUpper, bool & sumLowerFinite,
4697+ bool & sumUpperFinite, double aj,
4698+ double lowerSumBound, double upperSumBound) {
4699+ sumLowerFinite = sumLowerFinite && std::abs (lowerSumBound) != kHighsInf ;
4700+ sumUpperFinite = sumUpperFinite && std::abs (upperSumBound) != kHighsInf ;
4701+ if (sumLowerFinite)
4702+ sumLower += aj * static_cast <HighsCDouble>(lowerSumBound);
4703+ if (sumUpperFinite)
4704+ sumUpper += aj * static_cast <HighsCDouble>(upperSumBound);
4705+ };
4706+
4707+ // lambda for actual stuffing
4708+ auto checkRow = [&](HighsInt row, double rhs, HighsInt direction) {
4709+ // skip row if rhs is not finite
4710+ if (direction * rhs == kHighsInf ) return Result::kOk ;
4711+
4712+ // vectors for candidates and activity bounds
4713+ std::vector<std::tuple<HighsInt, double , HighsInt>> candidates;
4714+ HighsCDouble sumLower = 0.0 ;
4715+ HighsCDouble sumUpper = 0.0 ;
4716+ bool sumLowerFinite = true ;
4717+ bool sumUpperFinite = true ;
4718+
4719+ for (auto & nz : getRowVector (row)) {
4720+ // get column index, coefficient, cost and bounds
4721+ HighsInt j = nz.index ();
4722+ double aj = direction * nz.value ();
4723+ double cj = model->col_cost_ [j];
4724+ double sumLowerBound = model->col_lower_ [j];
4725+ double sumUpperBound = model->col_upper_ [j];
4726+ if (isContSingleton (j)) {
4727+ // check singleton
4728+ if (aj > 0 ) {
4729+ // use lower bound
4730+ sumUpperBound = sumLowerBound;
4731+ // candidate for stuffing?
4732+ if (cj < 0 ) candidates.push_back (std::make_tuple (j, aj, HighsInt{1 }));
4733+ } else {
4734+ // use upper bound
4735+ sumLowerBound = sumUpperBound;
4736+ // candidate for stuffing? multiply column with -1
4737+ if (cj > 0 )
4738+ candidates.push_back (std::make_tuple (j, aj, HighsInt{-1 }));
4739+ }
4740+ } else if (aj < 0 )
4741+ std::swap (sumLowerBound, sumUpperBound);
4742+ // update activities
4743+ updateActivityBounds (sumLower, sumUpper, sumLowerFinite, sumUpperFinite,
4744+ aj, sumLowerBound, sumUpperBound);
4745+ if (!sumLowerFinite && !sumUpperFinite) return Result::kOk ;
4746+ }
4747+
4748+ // sort candidates
4749+ sortCols (candidates);
4750+
4751+ // check candidates
4752+ for (const auto & t : candidates) {
4753+ // get variable index, coefficient and multiplier (-1 if sign was flipped)
4754+ HighsInt j = std::get<0 >(t);
4755+ double aj = std::get<1 >(t);
4756+ HighsInt multiplier = std::get<2 >(t);
4757+ // both bounds have to be finite
4758+ if (model->col_lower_ [j] == -kHighsInf ||
4759+ model->col_upper_ [j] == kHighsInf )
4760+ break ;
4761+ // compute delta (bound difference)
4762+ HighsCDouble delta = multiplier * aj *
4763+ (static_cast <HighsCDouble>(model->col_upper_ [j]) -
4764+ static_cast <HighsCDouble>(model->col_lower_ [j]));
4765+ // check if variable can be fixed
4766+ if (sumUpperFinite &&
4767+ delta <= direction * rhs - sumUpper + primal_feastol) {
4768+ if (multiplier < 0 )
4769+ HPRESOLVE_CHECKED_CALL (fixColToLower (postsolve_stack, j));
4770+ else
4771+ HPRESOLVE_CHECKED_CALL (fixColToUpper (postsolve_stack, j));
4772+ } else if (sumLowerFinite &&
4773+ direction * rhs <= sumLower + primal_feastol) {
4774+ if (multiplier < 0 )
4775+ HPRESOLVE_CHECKED_CALL (fixColToUpper (postsolve_stack, j));
4776+ else
4777+ HPRESOLVE_CHECKED_CALL (fixColToLower (postsolve_stack, j));
4778+ }
4779+ // update row activities
4780+ if (sumLowerFinite) sumLower += delta;
4781+ if (sumUpperFinite) sumUpper += delta;
4782+ }
4783+
4784+ return Result::kOk ;
4785+ };
4786+
4787+ // consider only non-fixed singleton continuous columns
4788+ if (!isContSingleton (col)) return Result::kOk ;
4789+
4790+ // get row index
4791+ HighsInt row = Arow[colhead[col]];
4792+
4793+ // return if we have an empty or singleton row or row is ranged
4794+ if (rowsize[row] <= 1 || isRanged (row)) return Result::kOk ;
4795+
4796+ // check row
4797+ HPRESOLVE_CHECKED_CALL (checkRow (row, model->row_upper_ [row], HighsInt{1 }));
4798+ HPRESOLVE_CHECKED_CALL (checkRow (row, model->row_lower_ [row], HighsInt{-1 }));
4799+
4800+ return Result::kOk ;
4801+ }
4802+
46584803double HPresolve::computeImpliedLowerBound (HighsInt col, HighsInt boundCol,
46594804 double boundColValue,
46604805 HighsInt boundColCoeffPattern) {
0 commit comments