Skip to content

Commit 44cf86a

Browse files
committed
merge step into pdhg, fix primal feas and reorder processed matrix
1 parent 86e9eee commit 44cf86a

File tree

2 files changed

+91
-71
lines changed

2 files changed

+91
-71
lines changed

highs/pdlp/hipdlp/pdhg.cc

Lines changed: 90 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ void PDLPSolver::preprocessLp() {
3939
}
4040

4141
int num_new_cols = 0;
42-
int nEqs = 0; // Count of equality-like constraints (EQ, BOUND, FREE)
42+
int nEqs = 0;
4343
constraint_types_.resize(nRows_orig);
4444

4545
// 1. First pass: Classify constraints and count slack variables needed
@@ -53,16 +53,15 @@ void PDLPSolver::preprocessLp() {
5353
nEqs++;
5454
} else {
5555
constraint_types_[i] = BOUND;
56-
num_new_cols++; // Need one slack variable for each ranged constraint
56+
num_new_cols++;
5757
nEqs++;
5858
}
5959
} else if (has_lower) {
6060
constraint_types_[i] = GEQ;
6161
} else if (has_upper) {
6262
constraint_types_[i] = LEQ;
6363
} else {
64-
constraint_types_[i] =
65-
FREE; // Free rows become bounded equalities Ax=0, z=[-inf,inf]
64+
constraint_types_[i] = FREE;
6665
num_new_cols++;
6766
nEqs++;
6867
}
@@ -72,7 +71,7 @@ void PDLPSolver::preprocessLp() {
7271
HighsLp& processed_lp = lp_;
7372
processed_lp.num_col_ = nCols_orig + num_new_cols;
7473
processed_lp.num_row_ = nRows_orig;
75-
original_num_col_ = nCols_orig; // store for postsolve
74+
original_num_col_ = nCols_orig;
7675
constraint_new_idx_.resize(nRows_orig);
7776

7877
processed_lp.col_cost_.resize(processed_lp.num_col_);
@@ -93,7 +92,16 @@ void PDLPSolver::preprocessLp() {
9392
}
9493
}
9594

96-
// 4. Populate costs and bounds for original and new slack variables
95+
// 4. Create is_equality_row_ vector based on NEW ordering
96+
is_equality_row_.resize(nRows_orig, false);
97+
for (int i = 0; i < nRows_orig; ++i) {
98+
int new_row_idx = constraint_new_idx_[i];
99+
is_equality_row_[new_row_idx] = (constraint_types_[i] == EQ ||
100+
constraint_types_[i] == BOUND ||
101+
constraint_types_[i] == FREE);
102+
}
103+
104+
// 5. Populate costs and bounds for original and new slack variables
97105
for (int i = 0; i < nCols_orig; ++i) {
98106
processed_lp.col_cost_[i] = original_lp_->col_cost_[i];
99107
processed_lp.col_lower_[i] = original_lp_->col_lower_[i];
@@ -110,87 +118,100 @@ void PDLPSolver::preprocessLp() {
110118
}
111119
}
112120

113-
// 5. Formulate the new constraints matrix (A') and RHS (b')
114-
//
115-
// Take a copy of the original LP's constraint matrix since we
116-
// need it rowwise and it's const
117-
HighsSparseMatrix original_matrix = original_lp_->a_matrix_;
118-
original_matrix.ensureRowwise();
119-
// Set up the processed constraint matrix as an empty row-wise
120-
// matrix that can have nCols_orig columns
121-
HighsSparseMatrix& processed_matrix = processed_lp.a_matrix_;
122-
processed_matrix.clear();
123-
processed_matrix.num_col_ = nCols_orig;
124-
processed_matrix.format_ = MatrixFormat::kRowwise;
125-
HighsSparseMatrix row;
126-
const double negative_one = -1.0;
127-
const double* negative_one_p = &negative_one;
128-
for (int i = 0; i < nRows_orig; ++i) {
129-
// Get the row from the original constraint matrix
130-
original_matrix.getRow(i, row);
131-
// Scale the row by -1 if it's a LEQ
132-
if (constraint_types_[i] == LEQ) row.scaleRows(negative_one_p);
133-
// Add the row to the processed constraint matrix
134-
processed_matrix.addRows(row);
135-
}
136-
// Convert the processed constraint matrix to column-wise orientation
137-
processed_matrix.ensureColwise();
138-
139-
// Add slack variable entries (-1) for BOUND and FREE constraints
140-
current_slack_col = nCols_orig;
141-
// Set up a negated identity column as a column-wise matrix with
142-
// nRows_orig rows and a single column containing -1 in row 0 (for
143-
// now)
144-
HighsSparseMatrix col;
145-
assert(col.isColwise());
146-
col.num_col_ = 1;
147-
col.num_row_ = nRows_orig;
148-
col.start_.push_back(1);
149-
col.index_.push_back(0);
150-
col.value_.push_back(-1);
151-
for (int iRow = 0; iRow < nRows_orig; ++iRow) {
152-
if (constraint_types_[iRow] == BOUND || constraint_types_[iRow] == FREE) {
153-
// Put the -1 in row iRow, and add the column to the matrix
154-
col.index_[0] = iRow;
155-
processed_matrix.addCols(col);
156-
current_slack_col++;
157-
}
158-
}
159-
160-
// 6. Set the new RHS (row bounds)
121+
// 6. Set the new RHS (row bounds) in PERMUTED order
161122
for (int i = 0; i < nRows_orig; ++i) {
162-
const int new_idx = constraint_new_idx_[i];
123+
int new_row_idx = constraint_new_idx_[i];
163124
switch (constraint_types_[i]) {
164125
case EQ:
165-
processed_lp.row_lower_[i] = original_lp_->row_lower_[i];
166-
processed_lp.row_upper_[i] = original_lp_->row_upper_[i];
126+
processed_lp.row_lower_[new_row_idx] = original_lp_->row_lower_[i];
127+
processed_lp.row_upper_[new_row_idx] = original_lp_->row_upper_[i];
167128
break;
168129
case GEQ:
169-
processed_lp.row_lower_[i] = original_lp_->row_lower_[i];
170-
processed_lp.row_upper_[i] = kHighsInf;
130+
processed_lp.row_lower_[new_row_idx] = original_lp_->row_lower_[i];
131+
processed_lp.row_upper_[new_row_idx] = kHighsInf;
171132
break;
172133
case LEQ:
173134
// Becomes -Ax >= -b
174-
processed_lp.row_lower_[i] = -original_lp_->row_upper_[i];
175-
processed_lp.row_upper_[i] = kHighsInf;
135+
processed_lp.row_lower_[new_row_idx] = -original_lp_->row_upper_[i];
136+
processed_lp.row_upper_[new_row_idx] = kHighsInf;
176137
break;
177138
case BOUND:
178139
case FREE:
179140
// Becomes Ax - z = 0
180-
processed_lp.row_lower_[i] = 0.0;
181-
processed_lp.row_upper_[i] = 0.0;
141+
processed_lp.row_lower_[new_row_idx] = 0.0;
142+
processed_lp.row_upper_[new_row_idx] = 0.0;
182143
break;
183144
}
184145
}
146+
147+
// 7. Build the constraint matrix with proper row ordering
148+
// First, get the original matrix in column-wise format
149+
HighsSparseMatrix original_matrix = original_lp_->a_matrix_;
150+
original_matrix.ensureColwise();
151+
152+
// Create new matrix
153+
HighsSparseMatrix& processed_matrix = processed_lp.a_matrix_;
154+
processed_matrix.clear();
155+
processed_matrix.format_ = MatrixFormat::kColwise;
156+
processed_matrix.num_col_ = processed_lp.num_col_;
157+
processed_matrix.num_row_ = processed_lp.num_row_;
158+
159+
// Reserve space
160+
processed_matrix.start_.resize(processed_lp.num_col_ + 1);
161+
processed_matrix.index_.reserve(original_matrix.numNz() + num_new_cols);
162+
processed_matrix.value_.reserve(original_matrix.numNz() + num_new_cols);
163+
164+
// Build column by column
165+
processed_matrix.start_[0] = 0;
166+
for (int col = 0; col < nCols_orig; ++col) {
167+
// For each column, add entries in the new row order
168+
std::vector<std::pair<int, double>> col_entries;
169+
170+
for (int el = original_matrix.start_[col]; el < original_matrix.start_[col + 1]; ++el) {
171+
int old_row = original_matrix.index_[el];
172+
int new_row = constraint_new_idx_[old_row];
173+
double val = original_matrix.value_[el];
174+
175+
// Apply scaling for LEQ constraints
176+
if (constraint_types_[old_row] == LEQ) {
177+
val = -val;
178+
}
179+
180+
col_entries.push_back({new_row, val});
181+
}
182+
183+
// Sort by row index to maintain proper sparse matrix format
184+
std::sort(col_entries.begin(), col_entries.end());
185+
186+
// Add to matrix
187+
for (const auto& entry : col_entries) {
188+
processed_matrix.index_.push_back(entry.first);
189+
processed_matrix.value_.push_back(entry.second);
190+
}
191+
192+
processed_matrix.start_[col + 1] = processed_matrix.index_.size();
193+
}
194+
195+
// Add slack variable columns
196+
current_slack_col = nCols_orig;
197+
for (int i = 0; i < nRows_orig; ++i) {
198+
if (constraint_types_[i] == BOUND || constraint_types_[i] == FREE) {
199+
int row_idx = constraint_new_idx_[i];
200+
processed_matrix.index_.push_back(row_idx);
201+
processed_matrix.value_.push_back(-1.0);
202+
processed_matrix.start_[current_slack_col + 1] = processed_matrix.index_.size();
203+
current_slack_col++;
204+
}
205+
}
206+
185207
num_eq_rows_ = nEqs;
186208
scaling_.passLp(&processed_lp);
187-
unscaled_processed_lp_ = processed_lp; // store for postsolve
209+
unscaled_processed_lp_ = processed_lp;
188210

189-
// 7. Compute and store norms of unscaled cost and rhs
211+
// 8. Compute and store norms of unscaled cost and rhs
190212
unscaled_c_norm_ = linalg::vector_norm(processed_lp.col_cost_);
191213
unscaled_rhs_norm_ = linalg::vector_norm(processed_lp.row_lower_);
192214

193-
// Already achieved by construction
194215
logger_.info("Preprocessing complete. New dimensions: " +
195216
std::to_string(processed_lp.num_row_) + " rows, " +
196217
std::to_string(processed_lp.num_col_) + " cols.");
@@ -683,8 +704,8 @@ double PDLPSolver::ComputePrimalFeasibility(
683704
for (HighsInt i = 0; i < lp_.num_row_; ++i) {
684705
primal_residual[i] = Ax_vector[i] - lp_.row_lower_[i];
685706

686-
// For inequality constraints (Ax >= b), project to negative part
687-
if (i >= num_eq_rows_) {
707+
// For inequality constraints, project to negative part
708+
if (!is_equality_row_[i]) {
688709
primal_residual[i] = std::min(0.0, primal_residual[i]);
689710
}
690711
}
@@ -697,9 +718,7 @@ double PDLPSolver::ComputePrimalFeasibility(
697718
}
698719
}
699720

700-
double primal_feasibility = linalg::vector_norm(primal_residual);
701-
702-
return primal_feasibility;
721+
return linalg::vector_norm(primal_residual);
703722
}
704723

705724
void PDLPSolver::ComputeDualSlacks(const std::vector<double>& ATy_vector) {

highs/pdlp/hipdlp/pdhg.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class PDLPSolver {
107107
SolverResults results_;
108108
int original_num_col_;
109109
int num_eq_rows_;
110+
std::vector<bool> is_equality_row_;
110111
std::vector<int> constraint_new_idx_;
111112
std::vector<ConstraintType> constraint_types_;
112113

0 commit comments

Comments
 (0)