Skip to content
Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7063060
fix(socp): canonicalize QC Q COO for rotated SOC detection (#1435)
yuwenchen95 Jun 15, 2026
5f0e06b
clarify require_symmetric_* for MPS symmetric-half input only, and tr…
yuwenchen95 Jun 16, 2026
9ff9b03
refactor(io): simplify QC COO canonicalization API and merge rules
yuwenchen95 Jun 17, 2026
b555adf
drop unused symmetric flag from canonicalize_quadratic_constraints
yuwenchen95 Jun 17, 2026
334bbb7
Move COO canonicalization into mps_parser; split QCMATRIX validation …
yuwenchen95 Jun 21, 2026
aa69c54
Add a python test to the problem in Issue 1435
yuwenchen95 Jun 21, 2026
3829701
Align the grpc data generation to the change of triplet form of quadr…
yuwenchen95 Jun 22, 2026
b9d16a3
Polish comment and tests
yuwenchen95 Jun 23, 2026
f15eac8
Replace the unordered_map-based QCMATRIX symmetry check and COO canon…
yuwenchen95 Jun 23, 2026
3912c48
Merge branch 'main' into fix-qcqp-canonical-coo
yuwenchen95 Jun 23, 2026
a516ff6
Update the guard of dimension for fast check of rotated socs and adju…
yuwenchen95 Jun 23, 2026
e17ede7
Polish comment and improve the sorting efficiency in canonicalization
yuwenchen95 Jun 25, 2026
5fcbcc6
Add automatic sorting selection for COO input format in canonicalization
yuwenchen95 Jun 29, 2026
c6fb7b1
Merge branch 'main' into fix-qcqp-canonical-coo
yuwenchen95 Jun 29, 2026
f1061c5
Merge branch 'main' into fix-qcqp-canonical-coo
yuwenchen95 Jun 29, 2026
a917131
Replace automatic_sort with the polished bucket sorting
yuwenchen95 Jul 1, 2026
afe9115
Merge branch 'main' into fix-qcqp-canonical-coo
yuwenchen95 Jul 2, 2026
7fdaf62
Merge branch 'main' into fix-qcqp-canonical-coo
yuwenchen95 Jul 3, 2026
470f170
Improving complexity of canonicalization by reusing workspace
yuwenchen95 Jul 3, 2026
fc12d4a
format update
yuwenchen95 Jul 3, 2026
0cdbfe1
code cleanup
yuwenchen95 Jul 7, 2026
fdc9601
code cleanup
yuwenchen95 Jul 7, 2026
6decb20
Hide reusable workspace class for all data input paths
yuwenchen95 Jul 7, 2026
4fa522b
Stylish fix
yuwenchen95 Jul 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#pragma once

#include <cuopt/mathematical_optimization/io/mps_data_model.hpp>
#include <cuopt/mathematical_optimization/optimization_problem_interface.hpp>

#include <raft/core/handle.hpp>
Expand Down Expand Up @@ -224,6 +225,9 @@ class cpu_optimization_problem_t : public optimization_problem_interface_t<i_t,
std::string problem_name_;
std::vector<std::string> var_names_{};
std::vector<std::string> row_names_{};

/** Reused while adding quadratic constraints. */
mutable io::coo_canonicalization_workspace_t<i_t, f_t> qc_coo_workspace_{};
};

} // namespace cuopt::mathematical_optimization
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,72 @@

namespace cuopt::mathematical_optimization::io {

/**
* @brief Reusable workspace for COO canonicalization.
*
* Lookup vectors grow to fit the largest variable index seen so far. Each call resets only
* entries that were touched. A workspace must not be shared across concurrent calls.
*/
template <typename i_t, typename f_t>
class coo_canonicalization_workspace_t {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is an implementation detail and doesn't really belong in a header file. The use of "friend" below hints at this. Most of the call sites are using canonicalize_quadratic_constraints so they don't need to see coo_canonicalization_workspace_t. Perhaps non-performance-sensitive sites can call a version that allocates a new coo_canonicalization_workspace_t?

Alternatively find a way to make this helper opaque so the body can be moved to a cpp file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class is exposed due to the existence of append_quadratic_constraint where we add quadratic constraint incrementally while want to keep reusing workspace. Dropping reusing workspace will slow the speed a lot when using add_quadratic_constraint.

I made a new commit trying to keep reusing workspace without exposing the class details in mps_data_model.hpp.

public:
static_assert(std::is_integral_v<i_t> && std::is_signed_v<i_t>,
"coo_canonicalization_workspace_t: i_t must be signed integral");

[[nodiscard]] i_t max_index_capacity() const noexcept { return row_perm_.size(); }

private:
static constexpr i_t invalid_index = -1;

struct active_row_t {
i_t row;
i_t count;
};

struct entry_t {
i_t row;
i_t col;
f_t val;
};

std::vector<i_t> row_perm_;
std::vector<i_t> col_position_;
std::vector<active_row_t> active_rows_;
std::vector<i_t> offsets_;
std::vector<i_t> cursor_;
std::vector<i_t> permutation_;
std::vector<entry_t> entries_;
std::vector<i_t> output_rows_;
std::vector<i_t> output_cols_;
std::vector<f_t> output_vals_;

void ensure_index_capacity(i_t max_index)
{
const size_t needed = static_cast<size_t>(max_index) + 1;
if (row_perm_.size() < needed) {
row_perm_.resize(needed, invalid_index);
col_position_.resize(needed, invalid_index);
}
}

template <typename index_t, typename value_t>
friend void canonicalize_coo_matrix(
std::vector<index_t>& rows,
std::vector<index_t>& cols,
std::vector<value_t>& vals,
coo_canonicalization_workspace_t<index_t, value_t>& workspace);
};

/**
* @brief Canonicalize a symmetric matrix in COO form to upper-triangular storage
* using reusable workspace storage. Defined in mps_parser.cpp.
*/
template <typename i_t, typename f_t>
void canonicalize_coo_matrix(std::vector<i_t>& rows,
Comment thread
yuwenchen95 marked this conversation as resolved.
std::vector<i_t>& cols,
std::vector<f_t>& vals,
coo_canonicalization_workspace_t<i_t, f_t>& workspace);

/**
* @brief A representation of a linear programming (LP) optimization problem
*
Expand Down Expand Up @@ -278,7 +344,8 @@ class mps_data_model_t {
f_t rhs_value,
std::span<const f_t> vals,
std::span<const i_t> rows,
std::span<const i_t> cols);
std::span<const i_t> cols,
coo_canonicalization_workspace_t<i_t, f_t>& workspace);

const std::vector<quadratic_constraint_t>& get_quadratic_constraints() const;

Expand Down Expand Up @@ -397,8 +464,15 @@ class mps_data_model_t {
*
* @param constraints Quadratic constraints whose rows, cols, and vals are updated.
*/
template <typename i_t, typename f_t>
void canonicalize_quadratic_constraints(
std::vector<typename mps_data_model_t<i_t, f_t>::quadratic_constraint_t>& constraints);
template <typename qc_t>
void canonicalize_quadratic_constraints(std::vector<qc_t>& constraints)
{
using i_t = typename decltype(qc_t::rows)::value_type;
using f_t = typename decltype(qc_t::vals)::value_type;
coo_canonicalization_workspace_t<i_t, f_t> workspace;
for (auto& qc : constraints) {
canonicalize_coo_matrix(qc.rows, qc.cols, qc.vals, workspace);
}
}

} // namespace cuopt::mathematical_optimization::io
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#pragma once

#include <cuopt/mathematical_optimization/io/mps_data_model.hpp>
#include <cuopt/mathematical_optimization/optimization_problem_interface.hpp>
#include <cuopt/mathematical_optimization/utilities/internals.hpp>

Expand Down Expand Up @@ -423,6 +424,9 @@ class optimization_problem_t : public optimization_problem_interface_t<i_t, f_t>
std::string problem_name_;
std::vector<std::string> var_names_{};
std::vector<std::string> row_names_{};

/** Reused while adding quadratic constraints. */
mutable io::coo_canonicalization_workspace_t<i_t, f_t> qc_coo_workspace_{};
};

} // namespace cuopt::mathematical_optimization
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ void populate_from_data_model_view(
// Raw Q COO from data_model_view is canonicalized here before solver storage.
if (data_model->has_quadratic_constraints()) {
auto qcs = data_model->get_quadratic_constraints();
io::canonicalize_quadratic_constraints<i_t, f_t>(qcs);
io::canonicalize_quadratic_constraints(qcs);
problem->set_quadratic_constraints(std::move(qcs));
}
}
Expand Down
12 changes: 6 additions & 6 deletions cpp/src/grpc/codegen/generate_conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2215,12 +2215,12 @@ def _gen_proto_to_problem(registry, indent=" "):
f'{b["name"]} size mismatch");'
)
lines.append(f"{ind} }}")
lines.append(f"{ind} _entries.push_back(std::move(_entry));")
lines.append(f"{ind} }}")
if name == "quadratic_constraints":
lines.append(
f"{ind} io::canonicalize_coo_matrix(_entry.rows, _entry.cols, _entry.vals);"
f"{ind} io::canonicalize_quadratic_constraints(_entries);"
)
lines.append(f"{ind} _entries.push_back(std::move(_entry));")
lines.append(f"{ind} }}")
lines.append(f"{ind} cpu_problem.{setter}(std::move(_entries));")
lines.append(f"{ind}}}")

Expand Down Expand Up @@ -2815,12 +2815,12 @@ def _gen_chunked_arrays_to_problem(registry, indent=" "):
f'{b["name"]} size mismatch");'
)
lines.append(f"{ind} }}")
lines.append(f"{ind} _entries.push_back(std::move(_entry));")
lines.append(f"{ind} }}")
if name == "quadratic_constraints":
lines.append(
f"{ind} io::canonicalize_coo_matrix(_entry.rows, _entry.cols, _entry.vals);"
f"{ind} io::canonicalize_quadratic_constraints(_entries);"
)
lines.append(f"{ind} _entries.push_back(std::move(_entry));")
lines.append(f"{ind} }}")
lines.append(f"{ind} cpu_problem.{setter}(std::move(_entries));")
lines.append(f"{ind}}}")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@
if (_entry.cols.size() != _entry.vals.size()) {
throw std::invalid_argument("set_quadratic_constraints: cols/vals size mismatch");
}
io::canonicalize_coo_matrix(_entry.rows, _entry.cols, _entry.vals);
_entries.push_back(std::move(_entry));
}
io::canonicalize_quadratic_constraints(_entries);
cpu_problem.set_quadratic_constraints(std::move(_entries));
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@
if (_entry.cols.size() != _entry.vals.size()) {
throw std::invalid_argument("set_quadratic_constraints: cols/vals size mismatch");
}
io::canonicalize_coo_matrix(_entry.rows, _entry.cols, _entry.vals);
_entries.push_back(std::move(_entry));
}
io::canonicalize_quadratic_constraints(_entries);
cpu_problem.set_quadratic_constraints(std::move(_entries));
}
4 changes: 3 additions & 1 deletion cpp/src/io/lp_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,7 @@ void flush_quadratic_constraints(mps_data_model_t<i_t, f_t>& problem,
const lp_parser_t<i_t, f_t>& parser)
{
const i_t linear_row_count = static_cast<i_t>(parser.row_names.size());
coo_canonicalization_workspace_t<i_t, f_t> qc_workspace;
for (i_t k = 0; k < static_cast<i_t>(parser.quadratic_constraint_blocks.size()); k++) {
const auto& block = parser.quadratic_constraint_blocks[k];
problem.append_quadratic_constraint(linear_row_count + k,
Expand All @@ -1481,7 +1482,8 @@ void flush_quadratic_constraints(mps_data_model_t<i_t, f_t>& problem,
block.rhs_value,
block.quad_triples.vals,
block.quad_triples.rows,
block.quad_triples.cols);
block.quad_triples.cols,
qc_workspace);
}
}

Expand Down
35 changes: 12 additions & 23 deletions cpp/src/io/mps_data_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,17 @@ void mps_data_model_t<i_t, f_t>::set_quadratic_objective_matrix(std::span<const
}

template <typename i_t, typename f_t>
void mps_data_model_t<i_t, f_t>::append_quadratic_constraint(i_t constraint_row_index,
const std::string& constraint_row_name,
char constraint_row_type,
std::span<const f_t> linear_values,
std::span<const i_t> linear_indices,
f_t rhs_value,
std::span<const f_t> vals,
std::span<const i_t> rows,
std::span<const i_t> cols)
void mps_data_model_t<i_t, f_t>::append_quadratic_constraint(
i_t constraint_row_index,
const std::string& constraint_row_name,
char constraint_row_type,
std::span<const f_t> linear_values,
std::span<const i_t> linear_indices,
f_t rhs_value,
std::span<const f_t> vals,
std::span<const i_t> rows,
std::span<const i_t> cols,
coo_canonicalization_workspace_t<i_t, f_t>& workspace)
{
mps_parser_expects(constraint_row_index >= 0,
error_type_t::ValidationError,
Expand Down Expand Up @@ -199,7 +201,7 @@ void mps_data_model_t<i_t, f_t>::append_quadratic_constraint(i_t constraint_row_
qc.rows.assign(rows.begin(), rows.end());
qc.cols.assign(cols.begin(), cols.end());
qc.vals.assign(vals.begin(), vals.end());
canonicalize_coo_matrix(qc.rows, qc.cols, qc.vals);
canonicalize_coo_matrix(qc.rows, qc.cols, qc.vals, workspace);
}

quadratic_constraints_.push_back(std::move(qc));
Expand Down Expand Up @@ -459,24 +461,11 @@ bool mps_data_model_t<i_t, f_t>::has_quadratic_constraints() const noexcept
return !quadratic_constraints_.empty();
}

template <typename i_t, typename f_t>
void canonicalize_quadratic_constraints(
std::vector<typename mps_data_model_t<i_t, f_t>::quadratic_constraint_t>& constraints)
{
for (auto& qc : constraints) {
canonicalize_coo_matrix(qc.rows, qc.cols, qc.vals);
}
}

// NOTE: Explicitly instantiate all types here in order to avoid linker error
template class mps_data_model_t<int, float>;

template class mps_data_model_t<int, double>;

template void canonicalize_quadratic_constraints<int, float>(
std::vector<mps_data_model_t<int, float>::quadratic_constraint_t>&);
template void canonicalize_quadratic_constraints<int, double>(
std::vector<mps_data_model_t<int, double>::quadratic_constraint_t>&);
// TODO current raft to cusparse wrappers only support int64_t
// can be CUSPARSE_INDEX_16U, CUSPARSE_INDEX_32I, CUSPARSE_INDEX_64I

Expand Down
Loading
Loading