Add lp format writer and rename mps references#1567
Conversation
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (17)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughChangesLP output support
Estimated code review effort: 4 (Complex) | ~60 minutes Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx (1)
519-527: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winReject variable-type vectors with inconsistent cardinality.
The helper only handles the empty case; a non-empty
variable_typesarray whose length differs fromn_varsis passed directly to the C++ view. Validate that the vector contains exactly one entry per variable before writing.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx` around lines 519 - 527, Update _prepare_data_model_view_for_write to validate that a non-empty variable_types array has exactly n_vars entries before calling set_data_model_view. Preserve the existing default creation for an empty vector with variables, and reject mismatched cardinality rather than passing it to the C++ view.
🧹 Nitpick comments (3)
cpp/include/cuopt/mathematical_optimization/io/writer.hpp (1)
26-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the
write_lpexecution and failure contract.The new public API documents format support and parameters, but not I/O failure behavior, thread-safety, or GPU/stream requirements. Add the actual contract from the implementation so callers know what errors and execution constraints to expect.
As per path instructions, public C++ headers should document thread-safety, GPU requirements, and numerical behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cpp/include/cuopt/mathematical_optimization/io/writer.hpp` around lines 26 - 36, Expand the write_lp documentation to state its I/O failure behavior, thread-safety guarantees or restrictions, GPU/stream requirements, and relevant numerical behavior, using the implementation’s actual contract. Keep the existing format-support and parameter documentation, and anchor the additions to the public write_lp declaration.Source: Path instructions
cpp/include/cuopt/mathematical_optimization/io/lp_writer.hpp (1)
42-54: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winDocument the lifetime requirement on both constructors.
create_view()buildsdata_model_view_tfrom raw pointers into the passed-inproblem's buffers (non-owning view), and the ctor at line 54 stores this inowned_view_. If callers constructlp_writer_tfrom a temporarydata_model_t/data_model_view_tand then callwrite()in a later statement, the referenced data is already destroyed — same hazard as the existingmps_writer_t. Worth a@noteon both ctors that the passed-inproblemmust outlive thelp_writer_tinstance (at least through thewrite()call).📝 Suggested doc addition
/** * `@brief` Ctor. Takes a data model as input and writes it out as an LP formatted file * * `@param`[in] problem Data model to write + * + * `@note` `problem` must outlive this lp_writer_t instance (or at least remain + * valid until write() is called), since the constructed view stores + * non-owning references into `problem`'s underlying buffers. */ lp_writer_t(const data_model_t<i_t, f_t>& problem);As per path instructions: "Suggest documenting thread-safety, GPU requirements, numerical behavior" for new public headers under
cpp/include/cuopt/**/*.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cpp/include/cuopt/mathematical_optimization/io/lp_writer.hpp` around lines 42 - 54, Add a `@note` to both lp_writer_t constructors documenting that the referenced problem must outlive the lp_writer_t instance, at least until write() completes, because the stored view is non-owning. Apply the same lifetime wording to the data_model_view_t and data_model_t overloads without changing constructor behavior.Source: Path instructions
cpp/include/cuopt/mathematical_optimization/optimization_problem_utils.hpp (1)
38-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRename the helper to match its generic input.
The changed signature accepts
data_model_t, while the function name and documentation still describe an MPS-only model. Rename it topopulate_from_data_modeland update its call sites so the API reflects the actual contract.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cpp/include/cuopt/mathematical_optimization/optimization_problem_utils.hpp` around lines 38 - 51, Rename populate_from_mps_data_model to populate_from_data_model, update its documentation to remove MPS-specific wording, and change every call site to use the new name while preserving the existing data_model_t behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cpp/include/cuopt/mathematical_optimization/io/data_model.hpp`:
- Around line 331-387: Add a private: access specifier before the model
data-member list containing maximize_, CSR buffers, bounds, metadata, solution
vectors, and quadratic-programming fields. Keep the existing validated accessor
methods public and do not alter field names, initialization, or validation
behavior.
- Line 51: Update the default constructor of data_model_t to initialize
maximize_ explicitly to false, ensuring default-constructed models use
minimization during conversion and writing while preserving existing
initialization behavior for other members.
In `@cpp/include/cuopt/mathematical_optimization/io/utilities/cython_parser.hpp`:
- Around line 17-21: Add a direct `#include` for the standard string header in
cython_parser.hpp before the declarations of call_read and call_parse_mps, so
their std::string parameters do not rely on transitive includes. Preserve the
existing function declarations unchanged.
In `@cpp/include/cuopt/mathematical_optimization/solve.hpp`:
- Around line 72-77: Update all affected solve_lp overload declarations and
Doxygen, including the batch overload, to consistently use data_model_t as the
data-model parameter name instead of mps_data_model or user_problem. Revise each
parameter description to accurately describe the io::data_model_t<i_t, f_t> type
and its actual behavior.
In `@cpp/tests/linear_programming/lp_writer_test.cpp`:
- Around line 149-150: Strengthen the quadratic comparison helper around
has_quadratic_objective() so it validates content rather than only presence and
count. Compare quadratic objective values, indices, and offsets, then inspect
every quadratic constraint’s row metadata, linear terms, RHS, and quadratic
triplets using tolerance for numeric values while preserving exact structural
comparisons.
In `@python/cuopt/cuopt/linear_programming/data_model/data_model.py`:
- Around line 761-764: Add type hints to both new public writeLP methods:
annotate DataModel.writeLP in
python/cuopt/cuopt/linear_programming/data_model/data_model.py at lines 761-764
with user_problem_file: str and a None return type, and annotate the
corresponding writeLP method in python/cuopt/cuopt/linear_programming/problem.py
at lines 2049-2059 with lp_file: str and a None return type.
In `@python/cuopt/cuopt/tests/linear_programming/test_parser.py`:
- Around line 259-304: Expand _assert_models_equivalent to compare
constraint-matrix structure, including column indices/offsets, and assert
get_variable_types() and get_row_names() match between models. Add a separate
test_write_lp_round_trip case using multiple constraints plus Integer and Binary
variables and their bounds sections so the variable-type writer/parser path is
exercised.
---
Outside diff comments:
In `@python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx`:
- Around line 519-527: Update _prepare_data_model_view_for_write to validate
that a non-empty variable_types array has exactly n_vars entries before calling
set_data_model_view. Preserve the existing default creation for an empty vector
with variables, and reject mismatched cardinality rather than passing it to the
C++ view.
---
Nitpick comments:
In `@cpp/include/cuopt/mathematical_optimization/io/lp_writer.hpp`:
- Around line 42-54: Add a `@note` to both lp_writer_t constructors documenting
that the referenced problem must outlive the lp_writer_t instance, at least
until write() completes, because the stored view is non-owning. Apply the same
lifetime wording to the data_model_view_t and data_model_t overloads without
changing constructor behavior.
In `@cpp/include/cuopt/mathematical_optimization/io/writer.hpp`:
- Around line 26-36: Expand the write_lp documentation to state its I/O failure
behavior, thread-safety guarantees or restrictions, GPU/stream requirements, and
relevant numerical behavior, using the implementation’s actual contract. Keep
the existing format-support and parameter documentation, and anchor the
additions to the public write_lp declaration.
In `@cpp/include/cuopt/mathematical_optimization/optimization_problem_utils.hpp`:
- Around line 38-51: Rename populate_from_mps_data_model to
populate_from_data_model, update its documentation to remove MPS-specific
wording, and change every call site to use the new name while preserving the
existing data_model_t behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 6d5ec415-b588-4d89-8468-00825bcc1b05
📒 Files selected for processing (63)
benchmarks/linear_programming/cuopt/benchmark_helper.hppbenchmarks/linear_programming/cuopt/initial_problem_check.hppbenchmarks/linear_programming/cuopt/run_mip.cppbenchmarks/linear_programming/cuopt/run_pdlp.cucpp/cuopt_cli.cppcpp/include/cuopt/mathematical_optimization/io/data_model.hppcpp/include/cuopt/mathematical_optimization/io/data_model_view.hppcpp/include/cuopt/mathematical_optimization/io/lp_writer.hppcpp/include/cuopt/mathematical_optimization/io/mps_data_model.hppcpp/include/cuopt/mathematical_optimization/io/mps_writer.hppcpp/include/cuopt/mathematical_optimization/io/parser.hppcpp/include/cuopt/mathematical_optimization/io/utilities/cython_parser.hppcpp/include/cuopt/mathematical_optimization/io/writer.hppcpp/include/cuopt/mathematical_optimization/optimization_problem_utils.hppcpp/include/cuopt/mathematical_optimization/solve.hppcpp/src/branch_and_bound/pseudo_costs.cppcpp/src/io/CMakeLists.txtcpp/src/io/data_model.cppcpp/src/io/data_model_view.cppcpp/src/io/lp_parser.cppcpp/src/io/lp_parser.hppcpp/src/io/lp_writer.cppcpp/src/io/mps_parser.cppcpp/src/io/mps_parser_internal.hppcpp/src/io/mps_writer.cppcpp/src/io/parser.cppcpp/src/io/utilities/cython_parser.cppcpp/src/io/writer.cppcpp/src/mip_heuristics/solve.cucpp/src/pdlp/cuopt_c.cppcpp/src/pdlp/solve.cucpp/src/pdlp/solve.cuhcpp/src/pdlp/utilities/cython_solve.cucpp/tests/linear_programming/CMakeLists.txtcpp/tests/linear_programming/lp_writer_test.cppcpp/tests/linear_programming/parser_test.cppcpp/tests/linear_programming/pdlp_test.cucpp/tests/linear_programming/unit_tests/optimization_problem_test.cucpp/tests/linear_programming/unit_tests/presolve_test.cucpp/tests/linear_programming/unit_tests/solution_interface_test.cucpp/tests/linear_programming/utilities/pdlp_test_utilities.cuhcpp/tests/mip/bounds_standardization_test.cucpp/tests/mip/cuts_test.cucpp/tests/mip/doc_example_test.cucpp/tests/mip/elim_var_remap_test.cucpp/tests/mip/feasibility_jump_tests.cucpp/tests/mip/incumbent_callback_test.cucpp/tests/mip/load_balancing_test.cucpp/tests/mip/mip_utils.cuhcpp/tests/mip/miplib_test.cucpp/tests/mip/multi_probe_test.cucpp/tests/mip/presolve_test.cucpp/tests/mip/problem_test.cucpp/tests/mip/semi_continuous_test.cucpp/tests/mip/server_test.cucpp/tests/mip/unit_test.cucpp/tests/utilities/inline_lp_test_utils.hppcpp/tests/utilities/inline_mps_test_utils.hpppython/cuopt/cuopt/linear_programming/data_model/data_model.pxdpython/cuopt/cuopt/linear_programming/data_model/data_model.pypython/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyxpython/cuopt/cuopt/linear_programming/problem.pypython/cuopt/cuopt/tests/linear_programming/test_parser.py
| static_assert(std::is_floating_point<f_t>::value, | ||
| "'data_model_t' accepts only floating point types for weights"); | ||
|
|
||
| data_model_t() = default; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Initialize the default optimization sense.
A default-constructed model reads an indeterminate maximize_ value, despite the documented default being minimization. This can select the wrong objective sense during conversion or writing.
Proposed fix
- bool maximize_;
+ bool maximize_{false};Also applies to: 331-332
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cpp/include/cuopt/mathematical_optimization/io/data_model.hpp` at line 51,
Update the default constructor of data_model_t to initialize maximize_
explicitly to false, ensuring default-constructed models use minimization during
conversion and writing while preserving existing initialization behavior for
other members.
| /** whether to maximize or minimize the objective function */ | ||
| bool maximize_; | ||
| /** | ||
| * the constraint matrix itself in the CSR format | ||
| * @{ | ||
| */ | ||
| std::vector<f_t> A_; | ||
| std::vector<i_t> A_indices_; | ||
| std::vector<i_t> A_offsets_; | ||
| /** @} */ | ||
| /** RHS of the constraints */ | ||
| std::vector<f_t> b_; | ||
| /** weights in the objective function */ | ||
| std::vector<f_t> c_; | ||
| /** scale factor of the objective function */ | ||
| f_t objective_scaling_factor_{1}; | ||
| /** offset of the objective function */ | ||
| f_t objective_offset_{0}; | ||
| /** lower bounds of the variables (primal part) */ | ||
| std::vector<f_t> variable_lower_bounds_; | ||
| /** upper bounds of the variables (primal part) */ | ||
| std::vector<f_t> variable_upper_bounds_; | ||
| /** types of variables can be 'C' or 'I' */ | ||
| std::vector<char> var_types_; | ||
| /** lower bounds of the constraint (dual part) */ | ||
| std::vector<f_t> constraint_lower_bounds_; | ||
| /** upper bounds of the constraint (dual part) */ | ||
| std::vector<f_t> constraint_upper_bounds_; | ||
| /** Type of each constraint */ | ||
| std::vector<char> row_types_; | ||
| /** name of the objective (only a single objective is currently allowed) */ | ||
| std::string objective_name_; | ||
| /** name of the problem */ | ||
| std::string problem_name_; | ||
| /** names of each of the variables in the OP */ | ||
| std::vector<std::string> var_names_{}; | ||
| /** names of linear constraint rows in exported MPS order. */ | ||
| std::vector<std::string> row_names_{}; | ||
| /** number of variables */ | ||
| i_t n_vars_{0}; | ||
| /** number of constraints in the LP representation */ | ||
| i_t n_constraints_{0}; | ||
| /** number of non-zero elements in the constraint matrix */ | ||
| i_t nnz_{0}; | ||
| /** Initial primal solution */ | ||
| std::vector<f_t> initial_primal_solution_; | ||
| /** Initial dual solution */ | ||
| std::vector<f_t> initial_dual_solution_; | ||
|
|
||
| // QPS-specific data members for quadratic programming support | ||
| /** Quadratic objective matrix in CSR format (for (1/2) * x^T * Q * x term) */ | ||
| std::vector<f_t> Q_objective_values_; | ||
| std::vector<i_t> Q_objective_indices_; | ||
| std::vector<i_t> Q_objective_offsets_; | ||
|
|
||
| /** One full quadratic constraint per QCMATRIX block, in order of appearance in the file */ | ||
| std::vector<quadratic_constraint_t> quadratic_constraints_; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Make the model state private.
These fields are publicly mutable, allowing callers to bypass model invariants and leave dimensions, CSR buffers, and metadata inconsistent. Add a private: section before the field list and retain validated accessors. As per coding guidelines, “keep data members private.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cpp/include/cuopt/mathematical_optimization/io/data_model.hpp` around lines
331 - 387, Add a private: access specifier before the model data-member list
containing maximize_, CSR buffers, bounds, metadata, solution vectors, and
quadratic-programming fields. Keep the existing validated accessor methods
public and do not alter field names, initialization, or validation behavior.
Source: Coding guidelines
| std::unique_ptr<cuopt::mathematical_optimization::io::data_model_t<int, double>> call_read( | ||
| const std::string& file_path, bool fixed_mps_format); | ||
|
|
||
| std::unique_ptr<cuopt::mathematical_optimization::io::mps_data_model_t<int, double>> call_parse_mps( | ||
| std::unique_ptr<cuopt::mathematical_optimization::io::data_model_t<int, double>> call_parse_mps( | ||
| const std::string& mps_file_path, bool fixed_mps_format); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Include <string> directly.
This header exposes std::string but relies on a transitive include from data_model.hpp; add #include <string> to keep it self-contained. As per coding guidelines, “C++ headers should be self-contained” and follow Include What You Use.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cpp/include/cuopt/mathematical_optimization/io/utilities/cython_parser.hpp`
around lines 17 - 21, Add a direct `#include` for the standard string header in
cython_parser.hpp before the declarations of call_read and call_parse_mps, so
their std::string parameters do not rely on transitive includes. Preserve the
existing function declarations unchanged.
Source: Coding guidelines
| optimization_problem_solution_t<i_t, f_t> solve_lp( | ||
| raft::handle_t const* handle_ptr, | ||
| const cuopt::mathematical_optimization::io::mps_data_model_t<i_t, f_t>& mps_data_model, | ||
| const cuopt::mathematical_optimization::io::data_model_t<i_t, f_t>& mps_data_model, | ||
| pdlp_solver_settings_t<i_t, f_t> const& settings = pdlp_solver_settings_t<i_t, f_t>{}, | ||
| bool problem_checking = true, | ||
| bool use_pdlp_solver_mode = true); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the renamed data-model parameter documentation.
These overloads now accept io::data_model_t, but the declarations and Doxygen still use the MPS-specific name mps_data_model; the batch overload also documents a different parameter name, user_problem. Update the parameter names and descriptions to consistently describe data_model_t<i_t, f_t>.
As per path instructions, C++ header parameter descriptions must match the actual types and behavior.
Also applies to: 107-113, 140-145
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cpp/include/cuopt/mathematical_optimization/solve.hpp` around lines 72 - 77,
Update all affected solve_lp overload declarations and Doxygen, including the
batch overload, to consistently use data_model_t as the data-model parameter
name instead of mps_data_model or user_problem. Revise each parameter
description to accurately describe the io::data_model_t<i_t, f_t> type and its
actual behavior.
Source: Path instructions
mlubin
left a comment
There was a problem hiding this comment.
Thanks for doing this! I'd prefer if the mps_data_model_t -> data_model_t rename was done in a separate PR to keep the review of the LP writer more focused.
| /** | ||
| * @brief Writes the problem to an LP formatted file | ||
| * | ||
| * Emits the (algebraic, CPLEX/Gurobi style) LP format understood by read_lp(). |
There was a problem hiding this comment.
| * Emits the (algebraic, CPLEX/Gurobi style) LP format understood by read_lp(). | |
| * Emits the LP format understood by read_lp(). |
| const auto& quadratic_constraints = problem_.get_quadratic_constraints(); | ||
|
|
||
| i_t n_variables = 0; | ||
| auto grow = [&](size_t s) { n_variables = std::max(n_variables, static_cast<i_t>(s)); }; |
There was a problem hiding this comment.
Please drop these static_casts, they're not needed. Also it would be good to know why the agents weren't influenced by #1505.
| auto grow = [&](size_t s) { n_variables = std::max(n_variables, static_cast<i_t>(s)); }; | ||
| grow(c_span.size()); | ||
| grow(lb_span.size()); | ||
| grow(ub_span.size()); |
There was a problem hiding this comment.
There's a lot of maneuvering here to accept arrays of different lengths. What if we assert that the arrays all have the same lengths to simplify the code.
| const std::vector<std::pair<i_t, f_t>>& row, | ||
| const char* rel, | ||
| f_t rhs) { | ||
| lp_file << " " << name << ":"; |
There was a problem hiding this comment.
Row names are not required, so if we don't have one we shouldn't print one.
| /* clang-format on */ | ||
|
|
||
| /** | ||
| * Round-trip tests for the LP writer. Each test parses LP text into a |
There was a problem hiding this comment.
It's ok to have a couple round-trip tests but this shouldn't be the primary way we test the writer. We should have a range of input problems where we test the exact strings outputted by the writer, not equivalence. The input problems themselves could be specified in LP format just for convenience.
| ) | ||
|
|
||
|
|
||
| def test_write_lp_round_trip(): |
There was a problem hiding this comment.
Instead of a round-trip test, construct a problem in the python interface and check the precise string that is written.
It might also be useful to consider other names over |
|
Yes, or we could leave mps_data_model_t as is until we make a decision on how to consolidate the problem representations. |
Implement lp_writer_t and write_lp(), expose writeLP from Python DataModel/Problem with round-trip tests, and extend cuOptWriteProblem to support CUOPT_FILE_FORMAT_LP via write_to_file().
Add file_format_from_path() and use it for user_problem_file and presolve_file so .lp paths emit LP and other extensions emit MPS. Document the behavior and add a Python e2e test.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py (1)
780-809: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert structural round-trip equivalence.
The test re-reads the LP but only checks section markers and one objective value. It can pass if constraints, bounds, names, or coefficients are lost without changing Afiro’s optimum. Compare the original and reread models, reusing the equivalence helper from
python/cuopt/cuopt/tests/linear_programming/test_parser.pyif appropriate.As per path instructions, round-trip tests should validate numerical and structural equivalence, not just successful re-solving.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py` around lines 780 - 809, Strengthen test_write_files_lp_extension to compare the original data_model_obj with the reread afiro model for structural and numerical equivalence, reusing the equivalence helper from test_parser.py if available. Retain the LP-format section-marker checks, but do not rely solely on the objective value and successful re-solving.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@cpp/include/cuopt/mathematical_optimization/optimization_problem_interface.hpp`:
- Around line 35-48: The output path dispatch in file_format_from_path must not
advertise .lp.gz or .lp.bz2 unless compression is implemented: update
cpp/include/cuopt/mathematical_optimization/optimization_problem_interface.hpp
lines 35-48 accordingly. In cpp/src/io/lp_writer.cpp lines 134-141, use
compression-aware output for any retained compressed formats. Update
docs/cuopt/source/convex-settings.rst line 51 and
docs/cuopt/source/mip-settings.rst line 51 to list only formats the LP writer
actually supports.
In `@cpp/src/io/lp_writer.cpp`:
- Around line 197-201: Update the var_name fallback generation in the LP writer
to reserve all non-empty explicit variable names before creating placeholders,
then choose unused generated names while preserving variable indices across
transformations. Add a round-trip regression test covering partial names such as
{"C1", ""} and verify distinct variables remain distinct after serialization and
parsing.
- Around line 376-380: Update the ranged-constraint branch in the LP writer,
specifically the logic around write_linear_row, to serialize finite lower and
upper bounds as one logical row using parser-supported ranged syntax or another
reversible representation. Preserve the original row name and constraint count
during round trips, and add a regression test that validates structural
equivalence after serialization and parsing.
- Around line 271-338: Apply problem_.get_objective_scaling_factor() to every
objective component serialized in the objective-writing block: scale linear
coefficients passed to emit_term, the objective offset emitted directly, and
quadratic coefficients including the off-diagonal factor. Preserve objective
sense, and add round-trip coverage for non-unit and negative scaling factors
that verifies objective sense and offset equivalence.
In `@python/cuopt/cuopt/linear_programming/problem.py`:
- Around line 2056-2057: The quadratic-constraint conversion in _to_data_model
must pass the keyword expected by DataModel.add_quadratic_constraint(),
replacing the incompatible sense argument with constraint_row_type so writeLP()
succeeds. Add a regression test covering writeLP() for a model containing a
quadratic constraint.
- Around line 2049-2058: Update the public Problem.writeLP method signature to
type lp_file as str and the return type as None. Expand its docstring with
parameter documentation describing the output path, a None return description,
and raises documentation stating that underlying file-writing failures
propagate.
---
Nitpick comments:
In `@python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py`:
- Around line 780-809: Strengthen test_write_files_lp_extension to compare the
original data_model_obj with the reread afiro model for structural and numerical
equivalence, reusing the equivalence helper from test_parser.py if available.
Retain the LP-format section-marker checks, but do not rely solely on the
objective value and successful re-solving.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 9ce3db6e-dd02-4417-a965-7a0e185e50b8
📒 Files selected for processing (27)
cpp/include/cuopt/mathematical_optimization/constants.hcpp/include/cuopt/mathematical_optimization/cpu_optimization_problem.hppcpp/include/cuopt/mathematical_optimization/cuopt_c.hcpp/include/cuopt/mathematical_optimization/io/lp_writer.hppcpp/include/cuopt/mathematical_optimization/io/writer.hppcpp/include/cuopt/mathematical_optimization/optimization_problem.hppcpp/include/cuopt/mathematical_optimization/optimization_problem_interface.hppcpp/src/io/CMakeLists.txtcpp/src/io/lp_writer.cppcpp/src/io/writer.cppcpp/src/mip_heuristics/solve.cucpp/src/pdlp/cpu_optimization_problem.cppcpp/src/pdlp/cuopt_c.cppcpp/src/pdlp/optimization_problem.cucpp/src/pdlp/solve.cucpp/tests/linear_programming/CMakeLists.txtcpp/tests/linear_programming/c_api_tests/c_api_tests.cppcpp/tests/linear_programming/lp_writer_test.cppcpp/tests/qp/unit_tests/mps_writer_test.cppdocs/cuopt/source/convex-settings.rstdocs/cuopt/source/mip-settings.rstpython/cuopt/cuopt/linear_programming/data_model/data_model.pxdpython/cuopt/cuopt/linear_programming/data_model/data_model.pypython/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyxpython/cuopt/cuopt/linear_programming/problem.pypython/cuopt/cuopt/tests/linear_programming/test_lp_solver.pypython/cuopt/cuopt/tests/linear_programming/test_parser.py
🚧 Files skipped from review as they are similar to previous changes (6)
- python/cuopt/cuopt/linear_programming/data_model/data_model.pxd
- cpp/include/cuopt/mathematical_optimization/io/writer.hpp
- cpp/include/cuopt/mathematical_optimization/io/lp_writer.hpp
- cpp/src/io/writer.cpp
- python/cuopt/cuopt/tests/linear_programming/test_parser.py
- python/cuopt/cuopt/linear_programming/data_model/data_model.py
| auto var_name = [&](i_t j) -> std::string { | ||
| if (static_cast<size_t>(j) < var_names_ref.size() && !var_names_ref[j].empty()) | ||
| return var_names_ref[j]; | ||
| return "C" + std::to_string(j); | ||
| }; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Generate collision-free fallback variable names.
A partial name list such as {"C1", ""} serializes both variables as C1, causing the parser to merge distinct indices. Reserve explicit names before generating placeholders and add a round-trip regression test.
As per path instructions, double-check variable index spaces across transformations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cpp/src/io/lp_writer.cpp` around lines 197 - 201, Update the var_name
fallback generation in the LP writer to reserve all non-empty explicit variable
names before creating placeholders, then choose unused generated names while
preserving variable indices across transformations. Add a round-trip regression
test covering partial names such as {"C1", ""} and verify distinct variables
remain distinct after serialization and parsing.
Source: Path instructions
| // --- Objective ---------------------------------------------------------- | ||
| lp_file << (problem_.get_sense() ? "Maximize\n" : "Minimize\n"); | ||
| { | ||
| std::string obj_name = | ||
| problem_.get_objective_name().empty() ? "obj" : problem_.get_objective_name(); | ||
| lp_file << " " << obj_name << ":"; | ||
|
|
||
| int terms_on_line = 0; | ||
| for (i_t j = 0; j < n_variables; ++j) { | ||
| if (c[j] != f_t(0)) { emit_term(c[j], var_name(j), terms_on_line); } | ||
| } | ||
| // A constant objective term is written directly; read_lp folds it into | ||
| // the objective offset. | ||
| const f_t offset = problem_.get_objective_offset(); | ||
| if (std::isfinite(offset) && offset != f_t(0)) { | ||
| lp_file << (offset < f_t(0) ? " - " : " + ") << fmt(std::abs(offset)); | ||
| } | ||
|
|
||
| // Quadratic objective: build the symmetric Hessian H = Q + Q^T (matching | ||
| // the MPS writer), then emit its upper triangle inside a '[ ... ] / 2' | ||
| // block. In the LP objective convention a bracket coefficient p on a term | ||
| // contributes 0.5*p to the objective, so for H = Q + Q^T the diagonal | ||
| // coefficient is H[i][i] and the off-diagonal coefficient is 2*H[i][j]. | ||
| if (problem_.has_quadratic_objective()) { | ||
| auto Qv = problem_.get_quadratic_objective_values(); | ||
| auto Qi = problem_.get_quadratic_objective_indices(); | ||
| auto Qo = problem_.get_quadratic_objective_offsets(); | ||
| std::vector<f_t> Q_values(Qv.begin(), Qv.end()); | ||
| std::vector<i_t> Q_indices(Qi.begin(), Qi.end()); | ||
| std::vector<i_t> Q_offsets(Qo.begin(), Qo.end()); | ||
|
|
||
| std::vector<f_t> H_values; | ||
| std::vector<i_t> H_indices; | ||
| std::vector<i_t> H_offsets; | ||
| if (problem_.is_Q_symmetrized()) { | ||
| H_values = std::move(Q_values); | ||
| H_indices = std::move(Q_indices); | ||
| H_offsets = std::move(Q_offsets); | ||
| } else { | ||
| cuopt::symmetrize_csr<i_t, f_t>( | ||
| Q_values, Q_indices, Q_offsets, H_values, H_indices, H_offsets); | ||
| } | ||
|
|
||
| // Collect the upper-triangular entries first so we only open the bracket | ||
| // when there is at least one nonzero quadratic term. | ||
| const i_t n_rows = static_cast<i_t>(H_offsets.size()) > 0 | ||
| ? static_cast<i_t>(H_offsets.size()) - 1 | ||
| : 0; | ||
| std::vector<std::tuple<i_t, i_t, f_t>> upper; | ||
| for (i_t i = 0; i < n_rows; ++i) { | ||
| for (i_t p = H_offsets[i]; p < H_offsets[i + 1]; ++p) { | ||
| const i_t j = H_indices[p]; | ||
| const f_t v = H_values[p]; | ||
| if (i <= j && v != f_t(0)) { upper.emplace_back(i, j, v); } | ||
| } | ||
| } | ||
| if (!upper.empty()) { | ||
| lp_file << " + ["; | ||
| int quad_terms = 0; | ||
| for (const auto& [i, j, v] : upper) { | ||
| if (i == j) { | ||
| emit_term(v, var_name(i) + " ^ 2", quad_terms); | ||
| } else { | ||
| emit_term(f_t(2) * v, var_name(i) + " * " + var_name(j), quad_terms); | ||
| } | ||
| } | ||
| lp_file << " ] / 2"; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Apply objective_scaling_factor to every serialized objective term.
The writer emits raw linear coefficients, offset, and quadratic coefficients, so any model with a scaling factor other than 1 changes objective after round-trip. Scale all three components and add coverage for non-unit and negative factors.
As per path instructions, round-trip tests must validate objective sense and offset equivalence.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cpp/src/io/lp_writer.cpp` around lines 271 - 338, Apply
problem_.get_objective_scaling_factor() to every objective component serialized
in the objective-writing block: scale linear coefficients passed to emit_term,
the objective offset emitted directly, and quadratic coefficients including the
off-diagonal factor. Preserve objective sense, and add round-trip coverage for
non-unit and negative scaling factors that verifies objective sense and offset
equivalence.
Source: Path instructions
| } else if (!std::isinf(lo) && !std::isinf(hi)) { | ||
| // Range row: the LP format cannot express two finite bounds on a single | ||
| // line, so split it into a '>=' row and a '<=' row. | ||
| write_linear_row(row_name(k) + "_lo", row, ">=", lo); | ||
| write_linear_row(row_name(k) + "_up", row, "<=", hi); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Preserve ranged constraints as one logical row.
Splitting one finite-bounded row into <name>_lo and <name>_up changes the constraint count and row names, so the serialized model fails structural round-trip equivalence. Use parser-supported ranged syntax or another reversible representation, with a regression test.
As per path instructions, round-trip tests must validate structural equivalence.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cpp/src/io/lp_writer.cpp` around lines 376 - 380, Update the
ranged-constraint branch in the LP writer, specifically the logic around
write_linear_row, to serialize finite lower and upper bounds as one logical row
using parser-supported ranged syntax or another reversible representation.
Preserve the original row name and constraint count during round trips, and add
a regression test that validates structural equivalence after serialization and
parsing.
Source: Path instructions
| if self.model is None: | ||
| self._to_data_model() |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Fix quadratic-constraint conversion before delegating.
When self.model is None, Line 2056 calls _to_data_model(). For quadratic constraints, that method passes sense=constr.Sense, but DataModel.add_quadratic_constraint() in python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx accepts constraint_row_type, so writeLP() raises TypeError instead of writing the model. Use the matching keyword and add a quadratic-constraint regression test.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@python/cuopt/cuopt/linear_programming/problem.py` around lines 2056 - 2057,
The quadratic-constraint conversion in _to_data_model must pass the keyword
expected by DataModel.add_quadratic_constraint(), replacing the incompatible
sense argument with constraint_row_type so writeLP() succeeds. Add a regression
test covering writeLP() for a model containing a quadratic constraint.
ramakrishnap-nv
left a comment
There was a problem hiding this comment.
Most of the major comments are already raised by coderabbit, it would good to resolve those.
Preserve unnamed constraints and validate model dimensions while making exact output the primary test contract. Add extension-based Python writes and deprecate the MPS-specific entry point.
Reject unsupported compressed output paths, clarify the Python write API, and initialize the default optimization sense. Signed-off-by: root <root@ipp1-3302.aselab.nvidia.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.lp_harness:
- Around line 1-2: Remove the committed ELF executable .lp_harness from version
control and add .lp_harness to .gitignore so the build artifact generated from
.lp_harness.cpp is excluded from future commits.
In @.lp_harness.cpp:
- Around line 21-26: Update the round function’s output path construction to
avoid the hard-coded developer-local /home/nfs/iroy/lp_writer/ directory; use a
portable temporary path mechanism, or remove this ad-hoc harness and rely on the
existing gtest coverage.
- Line 14: Remove the file-scope using-directive for
cuopt::mathematical_optimization::io in .lp_harness.cpp, and qualify each
affected cuOpt I/O symbol explicitly or replace it with narrow, symbol-specific
aliases.
In `@python/cuopt/cuopt/linear_programming/data_model/data_model.py`:
- Around line 805-809: Update the deprecation messages in DataModel.writeMPS
(python/cuopt/cuopt/linear_programming/data_model/data_model.py, lines 805-809)
and Problem.writeMPS (python/cuopt/cuopt/linear_programming/problem.py, lines
2063-2067) to replace “future release” with the same concrete planned cuOpt
removal version in both warnings.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: ea31907a-bb6d-485d-a260-579a4ac7e013
📒 Files selected for processing (17)
.lp_harness.lp_harness.cppcpp/include/cuopt/mathematical_optimization/io/lp_writer.hppcpp/include/cuopt/mathematical_optimization/io/mps_data_model.hppcpp/include/cuopt/mathematical_optimization/io/writer.hppcpp/include/cuopt/mathematical_optimization/optimization_problem_interface.hppcpp/src/io/lp_writer.cppcpp/tests/linear_programming/lp_writer_test.cppcpp/tests/linear_programming/unit_tests/optimization_problem_test.cudocs/cuopt/source/convex-settings.rstdocs/cuopt/source/mip-settings.rstpython/cuopt/cuopt/linear_programming/data_model/data_model.pypython/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyxpython/cuopt/cuopt/linear_programming/io/__init__.pypython/cuopt/cuopt/linear_programming/io/format.pypython/cuopt/cuopt/linear_programming/problem.pypython/cuopt/cuopt/tests/linear_programming/test_parser.py
🚧 Files skipped from review as they are similar to previous changes (4)
- cpp/include/cuopt/mathematical_optimization/io/writer.hpp
- docs/cuopt/source/mip-settings.rst
- python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx
- cpp/include/cuopt/mathematical_optimization/io/lp_writer.hpp
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.lp_harness:
- Around line 1-2: Remove the committed ELF executable .lp_harness from version
control and add .lp_harness to .gitignore so the build artifact generated from
.lp_harness.cpp is excluded from future commits.
In @.lp_harness.cpp:
- Around line 21-26: Update the round function’s output path construction to
avoid the hard-coded developer-local /home/nfs/iroy/lp_writer/ directory; use a
portable temporary path mechanism, or remove this ad-hoc harness and rely on the
existing gtest coverage.
- Line 14: Remove the file-scope using-directive for
cuopt::mathematical_optimization::io in .lp_harness.cpp, and qualify each
affected cuOpt I/O symbol explicitly or replace it with narrow, symbol-specific
aliases.
In `@python/cuopt/cuopt/linear_programming/data_model/data_model.py`:
- Around line 805-809: Update the deprecation messages in DataModel.writeMPS
(python/cuopt/cuopt/linear_programming/data_model/data_model.py, lines 805-809)
and Problem.writeMPS (python/cuopt/cuopt/linear_programming/problem.py, lines
2063-2067) to replace “future release” with the same concrete planned cuOpt
removal version in both warnings.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: ea31907a-bb6d-485d-a260-579a4ac7e013
📒 Files selected for processing (17)
.lp_harness.lp_harness.cppcpp/include/cuopt/mathematical_optimization/io/lp_writer.hppcpp/include/cuopt/mathematical_optimization/io/mps_data_model.hppcpp/include/cuopt/mathematical_optimization/io/writer.hppcpp/include/cuopt/mathematical_optimization/optimization_problem_interface.hppcpp/src/io/lp_writer.cppcpp/tests/linear_programming/lp_writer_test.cppcpp/tests/linear_programming/unit_tests/optimization_problem_test.cudocs/cuopt/source/convex-settings.rstdocs/cuopt/source/mip-settings.rstpython/cuopt/cuopt/linear_programming/data_model/data_model.pypython/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyxpython/cuopt/cuopt/linear_programming/io/__init__.pypython/cuopt/cuopt/linear_programming/io/format.pypython/cuopt/cuopt/linear_programming/problem.pypython/cuopt/cuopt/tests/linear_programming/test_parser.py
🚧 Files skipped from review as they are similar to previous changes (4)
- cpp/include/cuopt/mathematical_optimization/io/writer.hpp
- docs/cuopt/source/mip-settings.rst
- python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx
- cpp/include/cuopt/mathematical_optimization/io/lp_writer.hpp
🛑 Comments failed to post (4)
.lp_harness (1)
1-2: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Compiled binary executable committed to the repository.
This file is not source code — it is a raw ELF binary (executable machine code, symbol tables, section headers) dumped as text. This looks like a build artifact from
.lp_harness.cpp(referenced in the PR's test layer) that was accidentally committed instead of being excluded via.gitignore.Committing binaries is problematic: it bloats the git history irreversibly, cannot be code-reviewed, and introduces an unreviewed executable into version control. Please remove this file from the commit and add the corresponding build output path (e.g.
.lp_harness) to.gitignore.🛠️ Suggested fix
- (remove binary file `.lp_harness` from the commit) + echo ".lp_harness" >> .gitignore🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.lp_harness around lines 1 - 2, Remove the committed ELF executable .lp_harness from version control and add .lp_harness to .gitignore so the build artifact generated from .lp_harness.cpp is excluded from future commits..lp_harness.cpp (2)
14-14: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Remove the file-scope
using namespace.Qualify the cuOpt I/O symbols or use narrow aliases instead. As per coding guidelines: “Do not use
using namespaceat file scope.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.lp_harness.cpp at line 14, Remove the file-scope using-directive for cuopt::mathematical_optimization::io in .lp_harness.cpp, and qualify each affected cuOpt I/O symbol explicitly or replace it with narrow, symbol-specific aliases.Source: Coding guidelines
21-26: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Do not write to a developer-local absolute path.
/home/nfs/iroy/lp_writer/will not exist in CI or other developer environments, so this harness cannot run portably. Use a temporary path or remove this ad-hoc harness in favor of the gtest coverage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.lp_harness.cpp around lines 21 - 26, Update the round function’s output path construction to avoid the hard-coded developer-local /home/nfs/iroy/lp_writer/ directory; use a portable temporary path mechanism, or remove this ad-hoc harness and rely on the existing gtest coverage.python/cuopt/cuopt/linear_programming/data_model/data_model.py (1)
805-809: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Declare a concrete removal version in both deprecation warnings.
“Future release” does not give callers a migration deadline. State the planned cuOpt removal version consistently.
python/cuopt/cuopt/linear_programming/data_model/data_model.py#L805-L809: add the removal version toDataModel.writeMPS.python/cuopt/cuopt/linear_programming/problem.py#L2063-L2067: add the same removal version toProblem.writeMPS.As per coding guidelines: “When changing a public Python API signature, emit a
DeprecationWarningwith a removal version before breaking the old signature.”📍 Affects 2 files
python/cuopt/cuopt/linear_programming/data_model/data_model.py#L805-L809(this comment)python/cuopt/cuopt/linear_programming/problem.py#L2063-L2067🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/cuopt/cuopt/linear_programming/data_model/data_model.py` around lines 805 - 809, Update the deprecation messages in DataModel.writeMPS (python/cuopt/cuopt/linear_programming/data_model/data_model.py, lines 805-809) and Problem.writeMPS (python/cuopt/cuopt/linear_programming/problem.py, lines 2063-2067) to replace “future release” with the same concrete planned cuOpt removal version in both warnings.Sources: Coding guidelines, Path instructions
Description
Summary
Adds an LP-format writer to cuOpt (complementing the existing MPS writer),
and — since the writer surfaced a long-standing naming issue — renames the
shared, format-neutral problem model from
mps_data_model_ttodata_model_t(the type is used by both the LP and MPS readers/writers and is not
MPS-specific). The LP writer is exposed through the C, C++, and Python APIs.
Changes
LP writer (C++)
lp_writer_t(cpp/src/io/lp_writer.cpp,cpp/include/cuopt/mathematical_optimization/io/lp_writer.hpp), mirroring the MPS writer: objective (incl. quadratic[ ... ] / 2), linear + quadratic constraints, range-constraint splitting, variable bounds, andGenerals/Binaries/Semi-Continuoussections.write_lp(...)added toio/writer.hpp+src/io/writer.cpp.cpp/src/io/CMakeLists.txt.Rename:
mps_data_model_t→data_model_tgit mvio/mps_data_model.hpp→io/data_model.hppandsrc/io/mps_data_model.cpp→src/io/data_model.cpp; class renamed.solve/pdlp/mip, C API, CLI, tests, benchmarks).Rename:
mps_data_model_to_optimization_problem→data_model_to_optimization_problemsolve.hpp/solve.cuhdeclarations, callers, and all test call sites. No deprecation shim (the name is now consistent withdata_model_t).Python bindings for the LP writer
write_lpCython extern andDataModel.writeLP(...)/Problem.writeLP(...), mirroringwriteMPS.Tests
LP_WRITER_TEST— 6 round-trip cases (simple LP, maximize+offset, MIP generals/binaries, free/fixed bounds, quadratic objective, quadratic constraint).test_write_lp_round_tripintest_parser.py(parse LP →writeLP→ read back → assert model equivalence).Issue
Checklist