Skip to content

Commit 49388d0

Browse files
committed
Merge branch 'hi-pdlp' of https://github.com/ERGO-Code/HiGHS into hi-pdlp
2 parents dba442a + 46e8d7e commit 49388d0

File tree

2 files changed

+106
-51
lines changed

2 files changed

+106
-51
lines changed

highs/pdlp/hipdlp/pdhg.cc

Lines changed: 105 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -593,28 +593,40 @@ std::vector<double> PDLPSolver::ComputeLambda(
593593
std::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

620632
void PDLPSolver::ComputeDualSlacks(const std::vector<double>& ATy_vector) {
@@ -638,21 +650,33 @@ void PDLPSolver::ComputeDualSlacks(const std::vector<double>& ATy_vector) {
638650

639651
std::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+
706755
bool 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

745799
double PDLPSolver::PowerMethod() {

highs/pdlp/hipdlp/pdhg.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class PDLPSolver {
114114
const std::vector<double>& y2, double omega);
115115
std::vector<double> ComputeLambda(const std::vector<double>& y,
116116
const std::vector<double>& ATy_vector);
117+
double ComputeDualObjective(const std::vector<double>& y);
117118
std::pair<double, double> ComputePrimalFeasibility(
118119
const std::vector<double>& x, const std::vector<double>& Ax_vector);
119120
void ComputeDualSlacks(const std::vector<double>& ATy_vector);

0 commit comments

Comments
 (0)