@@ -593,28 +593,40 @@ std::vector<double> PDLPSolver::ComputeLambda(
593593std::pair<double , double > PDLPSolver::ComputePrimalFeasibility (
594594 const std::vector<double >& x, const std::vector<double >& Ax_vector) {
595595 double primal_feasibility_squared = 0.0 ;
596- std::vector<double > Ax_minus_b (lp_.num_row_ , 0.0 );
596+ std::vector<double > primal_residual (lp_.num_row_ , 0.0 );
597+
598+ // Compute Ax - rhs (where rhs is row_lower_ for our formulation)
597599 for (HighsInt i = 0 ; i < lp_.num_row_ ; ++i) {
598- double Ax = Ax_vector[i];
599- if (lp_. row_lower_ [i] == lp_. row_upper_ [i]) {
600- // Equality constraint
601- Ax_minus_b [i] = Ax - lp_.row_lower_ [i];
602- } else { // Inequality constraint
603- Ax_minus_b [i] = std::max (0.0 , Ax - lp_. row_upper_ [i]);
600+ primal_residual[i] = Ax_vector[i] - lp_. row_lower_ [i];
601+
602+ // For inequality constraints (Ax >= b), project to negative part
603+ if (lp_. row_lower_ [i] != lp_.row_upper_ [i]) {
604+ // This is an inequality constraint
605+ primal_residual [i] = std::min (0.0 , primal_residual [i]);
604606 }
605- primal_feasibility_squared += Ax_minus_b[i] * Ax_minus_b[i];
607+ // For equality constraints, keep the full residual
608+
609+ primal_feasibility_squared += primal_residual[i] * primal_residual[i];
606610 }
607-
611+
612+ // Apply scaling if needed
613+ if (scaling_.IsScaled ()) {
614+ const auto & row_scale = scaling_.GetRowScaling ();
615+ for (HighsInt i = 0 ; i < lp_.num_row_ ; ++i) {
616+ primal_residual[i] *= row_scale[i];
617+ }
618+ }
619+
608620 double primal_feasibility = sqrt (primal_feasibility_squared);
609-
610- // Compute norm of q
611- double q_norm = 0.0 ;
621+
622+ // Compute norm of rhs for relative tolerance
623+ double rhs_norm = 0.0 ;
612624 for (HighsInt i = 0 ; i < lp_.num_row_ ; ++i) {
613- q_norm += lp_.row_lower_ [i] * lp_.row_lower_ [i];
625+ rhs_norm += lp_.row_lower_ [i] * lp_.row_lower_ [i];
614626 }
615- q_norm = sqrt (q_norm );
616-
617- return std::make_pair (primal_feasibility, q_norm );
627+ rhs_norm = sqrt (rhs_norm );
628+
629+ return std::make_pair (primal_feasibility, rhs_norm );
618630}
619631
620632void PDLPSolver::ComputeDualSlacks (const std::vector<double >& ATy_vector) {
@@ -638,21 +650,33 @@ void PDLPSolver::ComputeDualSlacks(const std::vector<double>& ATy_vector) {
638650
639651std::pair<double , double > PDLPSolver::ComputeDualFeasibility (
640652 const std::vector<double >& ATy_vector) {
653+ ComputeDualSlacks (ATy_vector); // This updates dSlackPos_ and dSlackNeg_
654+
641655 double dual_feasibility_squared = 0.0 ;
642656 std::vector<double > dual_residual (lp_.num_col_ );
643-
657+
644658 for (HighsInt i = 0 ; i < lp_.num_col_ ; ++i) {
645- // The dual feasibility residual is c - A'y - (dSlackPos - dSlackNeg)
646- // where (dSlackPos - dSlackNeg) is the dual variable for the box
647- // constraints.
648- dual_residual[i] =
649- lp_.col_cost_ [i] - ATy_vector[i] - (dSlackPos_[i] - dSlackNeg_[i]);
650-
651- dual_feasibility_squared += dual_residual[i] * dual_residual[i];
659+ // Compute c - A'y - (slackPos - slackNeg)
660+ double residual = lp_.col_cost_ [i] - ATy_vector[i];
661+
662+ // Subtract the projection onto the bound constraints
663+ residual = residual - dSlackPos_[i] + dSlackNeg_[i];
664+
665+ dual_residual[i] = residual;
666+ dual_feasibility_squared += residual * residual;
652667 }
653-
668+
669+ // Apply scaling if needed
670+ if (scaling_.IsScaled ()) {
671+ const auto & col_scale = scaling_.GetColScaling ();
672+ for (HighsInt i = 0 ; i < lp_.num_col_ ; ++i) {
673+ dual_residual[i] *= col_scale[i];
674+ }
675+ }
676+
654677 double dual_feasibility = sqrt (dual_feasibility_squared);
655678 double c_norm = linalg::vector_norm (lp_.col_cost_ );
679+
656680 return std::make_pair (dual_feasibility, c_norm);
657681}
658682
@@ -703,43 +727,73 @@ PDLPSolver::ComputeDualityGap(const std::vector<double>& x,
703727 cTx);
704728}
705729
730+ double PDLPSolver::ComputeDualObjective (const std::vector<double >& y) {
731+ double dual_obj = 0.0 ;
732+
733+ // Compute b'y (or rhs'y in cuPDLP notation)
734+ for (int i = 0 ; i < lp_.num_row_ ; ++i) {
735+ dual_obj += lp_.row_lower_ [i] * y[i];
736+ }
737+
738+ // Add contribution from lower bounds: l'*slackPos
739+ for (int i = 0 ; i < lp_.num_col_ ; ++i) {
740+ if (lp_.col_lower_ [i] > -kHighsInf ) {
741+ dual_obj += lp_.col_lower_ [i] * dSlackPos_[i];
742+ }
743+ }
744+
745+ // Subtract contribution from upper bounds: u'*slackNeg
746+ for (int i = 0 ; i < lp_.num_col_ ; ++i) {
747+ if (lp_.col_upper_ [i] < kHighsInf ) {
748+ dual_obj -= lp_.col_upper_ [i] * dSlackNeg_[i];
749+ }
750+ }
751+
752+ return dual_obj;
753+ }
754+
706755bool PDLPSolver::CheckConvergence (const std::vector<double >& x,
707756 const std::vector<double >& y,
708757 const std::vector<double >& ax_vector,
709758 const std::vector<double >& aty_vector,
710759 double epsilon, SolverResults& results) {
760+ // Compute dual slacks first
711761 ComputeDualSlacks (aty_vector);
712- std::vector< double > lambda = ComputeLambda (y, aty_vector);
713-
714- double primal_feasibility, q_norm ;
715- std::tie (primal_feasibility, q_norm ) = ComputePrimalFeasibility (x, ax_vector);
762+
763+ // Compute primal feasibility
764+ double primal_feasibility, rhs_norm ;
765+ std::tie (primal_feasibility, rhs_norm ) = ComputePrimalFeasibility (x, ax_vector);
716766 results.primal_feasibility = primal_feasibility;
717-
767+
768+ // Compute dual feasibility
718769 double dual_feasibility, c_norm;
719770 std::tie (dual_feasibility, c_norm) = ComputeDualFeasibility (aty_vector);
720771 results.dual_feasibility = dual_feasibility;
721-
722- double duality_gap, qTy, lTlambda_plus, uTlambda_minus, cTx;
723- std::tie (duality_gap, qTy, lTlambda_plus, uTlambda_minus, cTx) =
724- ComputeDualityGap (x, y, lambda);
725- results.duality_gap = duality_gap;
726-
727- results.relative_obj_gap =
728- std::abs (duality_gap) /
729- (1.0 + std::abs (cTx) + std::abs (qTy) + std::abs (lTlambda_plus) +
730- std::abs (uTlambda_minus));
731-
732- bool primal_feasible = primal_feasibility <= epsilon * (1 + q_norm);
733- bool dual_feasible = dual_feasibility <= epsilon * (1 + c_norm);
734-
735- double dual_objective_value = qTy + lTlambda_plus - uTlambda_minus;
736- double denominator = 1 + abs (dual_objective_value);
737- if (denominator == 0 ) {
738- logger_.info (" Denominator is zero, cannot check duality gap." );
772+
773+ // Compute objectives
774+ double primal_obj = 0.0 ;
775+ for (int i = 0 ; i < lp_.num_col_ ; ++i) {
776+ primal_obj += lp_.col_cost_ [i] * x[i];
739777 }
740- bool duality_gap_small = duality_gap <= epsilon * denominator;
741-
742- return primal_feasible && dual_feasible && duality_gap_small;
778+ results.primal_obj = primal_obj;
779+
780+ double dual_obj = ComputeDualObjective (y);
781+ results.dual_obj = dual_obj;
782+
783+ // Compute duality gap
784+ double duality_gap = primal_obj - dual_obj;
785+ results.duality_gap = std::abs (duality_gap);
786+
787+ // Compute relative gap (matching cuPDLP formula)
788+ results.relative_obj_gap = std::abs (duality_gap) /
789+ (1.0 + std::abs (primal_obj) + std::abs (dual_obj));
790+
791+ // Check convergence criteria (matching cuPDLP)
792+ bool primal_feasible = primal_feasibility < epsilon * (1.0 + rhs_norm);
793+ bool dual_feasible = dual_feasibility < epsilon * (1.0 + c_norm);
794+ bool gap_small = results.relative_obj_gap < epsilon;
795+
796+ return primal_feasible && dual_feasible && gap_small;
743797}
744798
745799double PDLPSolver::PowerMethod () {
0 commit comments