@@ -565,7 +565,7 @@ bool HighsTransformedLp::untransform(std::vector<double>& vals,
565565// Create a single node flow relaxation (SNFR) from an aggregated
566566// mixed-integer row and find a valid flow cover.
567567// Turn \sum c_i x_i + \sum a_i y_i <= a_0 (x_i binary, y_i real non-neg)
568- // into \sum_{j \in N+} y_j - \sum_{j \in N-} y_j <= b, where y_j <= u_j x_j
568+ // into \sum_{j \in N+} y'_j - \sum_{j \in N-} y'_j <= b, where y'_j <= u_j x_j
569569bool HighsTransformedLp::transformSNFRelaxation (
570570 std::vector<HighsInt>& inds, std::vector<double >& vals, double & rhs,
571571 HighsCutGeneration::SNFRelaxation& snfr) {
@@ -626,6 +626,26 @@ bool HighsTransformedLp::transformSNFRelaxation(
626626 auto checkValidityVB = [&](HighsInt bincol, HighsImplications::VarBound vb,
627627 double coef, double origbincoef, double lb,
628628 double ub, bool isVub) {
629+ // a_j coefficient of y_j in row. c_j coefficient of x_j in row.
630+ // u_j, l_j, closest simple bounds imposed on y_j.
631+ // y_j <= l'_j x_j + d_j || y_j >= u'_j x_j + d_j
632+ // variable bound can only be used if following properties respected:
633+ // |l'_j| <= 1e6 && |u'_j| <= 1e6
634+ // if a_j > 0
635+ // !isVub: (1) u_j <= d_j
636+ // (2) a_j (u_j - d_j) + c_j <= 0
637+ // (3) a_j l'_j + c_j <= 0
638+ // isVub: (1) l_j >= d_j
639+ // (2) a_j (l_j - d_j) + c_j >= 0
640+ // (3) a_j u'_j + c_j >= 0
641+ // if a_j < 0
642+ // !isVub: (1) u_j <= d_j
643+ // (2) a_j (u_j - d_j) + c_j >= 0
644+ // (3) a_j l'_j + c_j >= 0
645+ // isVub: (1) l_j >= d_j
646+ // (2) a_j (l_j - d_j) + c_j <= 0
647+ // (3) a_j u'_j + c_j <= 0
648+ // Note: c_j may change during transform if two columns use same bin col
629649 if (bincol == -1 ) return false ;
630650 if (abs (vb.coef ) >= 1e+6 ) return false ;
631651 if (isVub && lb < vb.constant ) return false ;
@@ -664,7 +684,8 @@ bool HighsTransformedLp::transformSNFRelaxation(
664684 snfr.numNnzs ++;
665685 };
666686
667- // Place the non-binary variables to the front (all general ints relaxed)
687+ // Place the non-binary columns to the front (all general ints relaxed)
688+ // Use vectorsum to track original row coefficients of binary columns.
668689 HighsInt i = 0 ;
669690 while (i < numNz - numBinCols) {
670691 HighsInt col = inds[i];
@@ -737,6 +758,7 @@ bool HighsTransformedLp::transformSNFRelaxation(
737758
738759 // Transform entry into the SNFR
739760 if (colIsBinary (col, lb, ub)) {
761+ // Binary columns can be added directly to the SNFR
740762 if (vals[i] >= 0 ) {
741763 addSNFRentry (col, -1 , getLpSolution (col),
742764 getLpSolution (col) * vals[i], 1 , vals[i], 0 , vals[i], 0 );
@@ -746,6 +768,7 @@ bool HighsTransformedLp::transformSNFRelaxation(
746768 0 );
747769 }
748770 } else {
771+ // Decide whether to use {simple, variable} {lower, upper} bound
749772 if (lbDist[col] < ubDist[col] - mip.mipdata_ ->feastol ) {
750773 if (!checkValidityVB (bestVlb[col].first , bestVlb[col].second , vals[i],
751774 vectorsum.getValue (bestVlb[col].first ), lb, ub,
@@ -789,6 +812,10 @@ bool HighsTransformedLp::transformSNFRelaxation(
789812 HighsInt vbcol;
790813 switch (boundTypes[col]) {
791814 case BoundType::kSimpleLb :
815+ // Case (1) -> a_j > 0, y'_j -> N- Case (2) -> a_j < 0, y'_j ->N+
816+ // (1) y'_j = -a_j(y_j - u_j), 0 <= y'_j <= a_j(u_j - l_j)x_j, x_j = 1
817+ // (2) y'_j = a_j(y_j - u_j), 0 <= y'_j <= -a_j(u_j - l_j)x_j, x_j = 1
818+ // rhs -= a_j * u_j
792819 substsolval = static_cast <double >(
793820 vals[i] * (HighsCDouble (getLpSolution (col)) - ub));
794821 vbcoef = static_cast <double >(vals[i] * (HighsCDouble (ub) - lb));
@@ -803,6 +830,11 @@ bool HighsTransformedLp::transformSNFRelaxation(
803830 tmpSnfrRhs -= aggrconstant;
804831 break ;
805832 case BoundType::kSimpleUb :
833+ // Case (1) -> a_j > 0, y'_j -> N+ Case (2) -> a_j < 0, y'_j ->N-
834+ // (1) y'_j = a_j(y_j - l_j), 0 <= y'_j <= a_j(u_j - l_j)x_j, x_j = 1
835+ // (2) y'_j = -a_j(y_j - l_j), 0 <= y'_j <= -a_j(u_j - l_j)x_j,
836+ // x_j = 1
837+ // rhs -= a_j * l_j
806838 substsolval = static_cast <double >(
807839 vals[i] * (HighsCDouble (getLpSolution (col)) - lb));
808840 vbcoef = static_cast <double >(vals[i] * (HighsCDouble (ub) - lb));
@@ -817,6 +849,13 @@ bool HighsTransformedLp::transformSNFRelaxation(
817849 tmpSnfrRhs -= aggrconstant;
818850 break ;
819851 case BoundType::kVariableLb :
852+ // vlb: l'_j x_j + d_j <= y_j. c_j is the coefficient of x_j in row
853+ // Case (1) -> a_j > 0, y'_j -> N- Case (2) -> a_j < 0, y'_j ->N+
854+ // (1) y'_j = -(a_j(y_j - d_j) + c_j * x_j),
855+ // 0 <= y'_j <= -(a_j l'_j + c_j)x_j
856+ // (2) y'_j = a_j(y_j - d_j) + c_j * x_j,
857+ // 0 <= y'_j <= (a_j l'_j + c_j)x_j
858+ // rhs -= a_j * d_j
820859 vbcol = bestVlb[col].first ;
821860 substsolval = static_cast <double >(
822861 vals[i] * (HighsCDouble (getLpSolution (col)) -
@@ -841,6 +880,13 @@ bool HighsTransformedLp::transformSNFRelaxation(
841880 tmpSnfrRhs -= aggrconstant;
842881 break ;
843882 case BoundType::kVariableUb :
883+ // vub: y_j >= u'_j x_j + d_j. c_j is the coefficient of x_j in row
884+ // Case (1) -> a_j > 0, y'_j -> N+ Case (2) -> a_j < 0, y'_j ->N-
885+ // (1) y'_j = a_j(y_j - d_j) + c_j * x_j),
886+ // 0 <= y'_j <= (a_j u'_j + c_j)x_j
887+ // (2) y'_j = -(a_j(y_j - d_j) + c_j * x_j),
888+ // 0 <= y'_j <= -(a_j u'_j + c_j)x_j
889+ // rhs -= a_j * d_j
844890 vbcol = bestVub[col].first ;
845891 substsolval = static_cast <double >(
846892 vals[i] * (HighsCDouble (getLpSolution (col)) -
0 commit comments